mirror of
https://github.com/inspec/inspec
synced 2024-12-03 18:09:32 +00:00
overhaul rule structure
* rename VulcanoBaseRule -> Vulcano::Rule * initialize rule inside the ProfileContext * attach all resources to ProfileContext and all rules created within * rename rule.rb -> dsl.rb, now only containing DSL information * rename base_rule.rb -> rule.rb, now containing everything for rule Signed-off-by: Dominik Richter <dominik.richter@gmail.com>
This commit is contained in:
parent
ec7a743f21
commit
3fe0c90733
4 changed files with 280 additions and 279 deletions
|
@ -1,92 +0,0 @@
|
|||
# encoding: utf-8
|
||||
# copyright: 2015, Dominik Richter
|
||||
# license: All rights reserved
|
||||
|
||||
class VulcanoBaseRule
|
||||
def initialize(id, _opts, &block)
|
||||
@id = id
|
||||
@impact = nil
|
||||
@__code = ''
|
||||
@__block = block
|
||||
@title = nil
|
||||
@desc = nil
|
||||
# not changeable by the user:
|
||||
@profile_id = nil
|
||||
@checks = []
|
||||
# evaluate the given definition
|
||||
instance_eval(&block) if block_given?
|
||||
end
|
||||
|
||||
def id(*_)
|
||||
# never overwrite the ID
|
||||
@id
|
||||
end
|
||||
|
||||
def impact(v = nil)
|
||||
@impact = v unless v.nil?
|
||||
@impact
|
||||
end
|
||||
|
||||
def title(v = nil)
|
||||
@title = v unless v.nil?
|
||||
@title
|
||||
end
|
||||
|
||||
def desc(v = nil)
|
||||
@desc = v unless v.nil?
|
||||
@desc
|
||||
end
|
||||
|
||||
def self.merge(dst, src)
|
||||
if src.id != dst.id
|
||||
# TODO: register an error, this case should not happen
|
||||
return
|
||||
end
|
||||
sp = src.instance_variable_get(:@profile_id)
|
||||
dp = dst.instance_variable_get(:@profile_id)
|
||||
if sp != dp
|
||||
# TODO: register an error, this case should not happen
|
||||
return
|
||||
end
|
||||
# merge all fields
|
||||
dst.impact(src.impact) unless src.impact.nil?
|
||||
dst.title(src.title) unless src.title.nil?
|
||||
dst.desc(src.desc) unless src.desc.nil?
|
||||
# merge indirect fields
|
||||
# checks defined in the source will completely eliminate
|
||||
# all checks that were defined in the destination
|
||||
sc = src.instance_variable_get(:@checks)
|
||||
unless sc.nil? || sc.empty?
|
||||
dst.instance_variable_set(:@checks, sc)
|
||||
end
|
||||
end
|
||||
|
||||
# Get the full id consisting of profile id + rule id
|
||||
# for the rule. If the rule's profile id is empty,
|
||||
# the given profile_id will be used instead and also
|
||||
# set for the rule.
|
||||
def self.full_id(profile_id, rule)
|
||||
if rule.is_a?(String) or rule.nil?
|
||||
rid = rule
|
||||
else
|
||||
# As the profile context is exclusively pulled with a
|
||||
# profile ID, attach it to the rule if necessary.
|
||||
rid = rule.instance_variable_get(:@id)
|
||||
if rid.nil?
|
||||
# TODO: Message about skipping this rule
|
||||
# due to missing ID
|
||||
return nil
|
||||
end
|
||||
end
|
||||
pid = rule.instance_variable_get(:@profile_id)
|
||||
if pid.nil?
|
||||
rule.instance_variable_set(:@profile_id, profile_id)
|
||||
pid = profile_id
|
||||
end
|
||||
if pid.nil? or pid.empty?
|
||||
return rid
|
||||
else
|
||||
return "#{pid}/#{rid}"
|
||||
end
|
||||
end
|
||||
end
|
151
lib/vulcano/dsl.rb
Normal file
151
lib/vulcano/dsl.rb
Normal file
|
@ -0,0 +1,151 @@
|
|||
# encoding: utf-8
|
||||
# copyright: 2015, Dominik Richter
|
||||
# license: All rights reserved
|
||||
|
||||
module Vulcano::DSL
|
||||
def require_rules(id, &block)
|
||||
::Vulcano::DSL.load_spec_files_for_profile self, id, false, &block
|
||||
end
|
||||
|
||||
def include_rules(id, &block)
|
||||
::Vulcano::DSL.load_spec_files_for_profile self, id, true, &block
|
||||
end
|
||||
|
||||
# Register a given rule with RSpec and
|
||||
# let it run. This happens after everything
|
||||
# else is merged in.
|
||||
def self.execute_rule(r, profile_id)
|
||||
checks = r.instance_variable_get(:@checks)
|
||||
fid = VulcanoBaseRule.full_id(r, profile_id)
|
||||
checks.each do |m, a, b|
|
||||
# check if the resource is skippable and skipped
|
||||
if a.is_a?(Array) && !a.empty? &&
|
||||
a[0].respond_to?(:resource_skipped) &&
|
||||
!a[0].resource_skipped.nil?
|
||||
cres = ::Vulcano::Rule.__send__(m, *a) do
|
||||
it a[0].resource_skipped
|
||||
end
|
||||
else
|
||||
# execute the method
|
||||
cres = ::Vulcano::Rule.__send__(m, *a, &b)
|
||||
end
|
||||
if m == 'describe'
|
||||
set_rspec_ids(cres, fid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# merge two rules completely; all defined
|
||||
# fields from src will be overwritten in dst
|
||||
def self.merge_rules(dst, src)
|
||||
VulcanoBaseRule.merge dst, src
|
||||
end
|
||||
|
||||
# Attach an ID attribute to the
|
||||
# metadata of all examples
|
||||
# TODO: remove this once IDs are in rspec-core
|
||||
def self.set_rspec_ids(obj, id)
|
||||
obj.examples.each {|ex|
|
||||
ex.metadata[:id] = id
|
||||
}
|
||||
obj.children.each {|c|
|
||||
set_rspec_ids(c, id)
|
||||
}
|
||||
end
|
||||
|
||||
def self.load_spec_file_for_profile(profile_id, file, rule_registry, only_ifs)
|
||||
raw = File.read(file)
|
||||
# TODO: error-handling
|
||||
|
||||
ctx = Vulcano::ProfileContext.new(profile_id, rule_registry, only_ifs)
|
||||
ctx.instance_eval(raw, file, 1)
|
||||
end
|
||||
|
||||
def self.load_spec_files_for_profile(bind_context, profile_id, include_all, &block)
|
||||
# get all spec files
|
||||
files = get_spec_files_for_profile profile_id
|
||||
# load all rules from spec files
|
||||
rule_registry = {}
|
||||
# TODO: handling of only_ifs
|
||||
only_ifs = []
|
||||
files.each do |file|
|
||||
load_spec_file_for_profile(profile_id, file, rule_registry, only_ifs)
|
||||
end
|
||||
|
||||
# interpret the block and create a set of rules from it
|
||||
block_registry = {}
|
||||
if block_given?
|
||||
ctx = Vulcano::ProfileContext.new(profile_id, block_registry, only_ifs)
|
||||
ctx.instance_eval(&block)
|
||||
end
|
||||
|
||||
# if all rules are not included, select only the ones
|
||||
# that were defined in the block
|
||||
unless include_all
|
||||
remove = rule_registry.keys - block_registry.keys
|
||||
remove.each { |key| rule_registry.delete(key) }
|
||||
end
|
||||
|
||||
# merge the rules in the block_registry (adjustments) with
|
||||
# the rules in the rule_registry (included)
|
||||
block_registry.each do |id, r|
|
||||
org = rule_registry[id]
|
||||
if org.nil?
|
||||
# TODO: print error because we write alter a rule that doesn't exist
|
||||
elsif r.nil?
|
||||
rule_registry.delete(id)
|
||||
else
|
||||
merge_rules(org, r)
|
||||
end
|
||||
end
|
||||
|
||||
# finally register all combined rules
|
||||
rule_registry.each do |_id, rule|
|
||||
bind_context.__register_rule rule
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_spec_files_for_profile(id)
|
||||
base_path = '/etc/vulcanosec/tests'
|
||||
path = File.join(base_path, id)
|
||||
# find all files to be included
|
||||
files = []
|
||||
if File.directory? path
|
||||
# include all library paths, if they exist
|
||||
libdir = File.join(path, 'lib')
|
||||
if File.directory? libdir and !$LOAD_PATH.include?(libdir)
|
||||
$LOAD_PATH.unshift(libdir)
|
||||
end
|
||||
files = Dir[File.join(path, 'spec', '*_spec.rb')]
|
||||
end
|
||||
files
|
||||
end
|
||||
end
|
||||
|
||||
module Vulcano::GlobalDSL
|
||||
def __register_rule(r)
|
||||
# make sure the profile id is attached to the rule
|
||||
::Vulcano::DSL.execute_rule(r, __profile_id)
|
||||
end
|
||||
|
||||
def __unregister_rule(_id)
|
||||
end
|
||||
end
|
||||
|
||||
module Vulcano::DSLHelper
|
||||
def self.bind_dsl(scope)
|
||||
# rubocop:disable Lint/NestedMethodDefinition
|
||||
(class << scope; self; end).class_exec do
|
||||
include Vulcano::DSL
|
||||
include Vulcano::GlobalDSL
|
||||
def __profile_id
|
||||
ENV['VULCANOSEC_PROFILE_ID']
|
||||
end
|
||||
end
|
||||
# rubocop:enable all
|
||||
end
|
||||
end
|
||||
|
||||
::Vulcano::DSLHelper.bind_dsl(self)
|
|
@ -1,5 +1,7 @@
|
|||
# encoding: utf-8
|
||||
require 'vulcano/backend'
|
||||
require 'vulcano/rule'
|
||||
require 'vulcano/dsl'
|
||||
|
||||
module Vulcano
|
||||
class ProfileContext
|
||||
|
@ -15,11 +17,54 @@ module Vulcano
|
|||
@only_ifs = only_ifs
|
||||
profile_context_owner = self
|
||||
|
||||
dsl = Module.new do
|
||||
Vulcano::Resource.registry.each do |id, r|
|
||||
define_method id.to_sym do |*args|
|
||||
r.new(backend, *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rule_class = Class.new(Vulcano::Rule) do
|
||||
include RSpec::Core::DSL
|
||||
include dsl
|
||||
end
|
||||
|
||||
outer_dsl = Class.new do
|
||||
include dsl
|
||||
|
||||
define_method :rule do |*args, &block|
|
||||
id = args[0]
|
||||
opts = args[1] || {}
|
||||
return if @skip_profile
|
||||
__register_rule rule_class.new(id, opts, &block)
|
||||
end
|
||||
|
||||
define_method :describe do |*args, &block|
|
||||
path = block.source_location[0]
|
||||
line = block.source_location[1]
|
||||
id = "#{File.basename(path)}:#{line}"
|
||||
rule = rule_class.new(id, {}) do
|
||||
describe(*args, &block)
|
||||
end
|
||||
__register_rule rule, &block
|
||||
end
|
||||
|
||||
def skip_rule(id)
|
||||
__unregister_rule id
|
||||
end
|
||||
|
||||
def only_if(&block)
|
||||
return unless block_given?
|
||||
@skip_profile = !block.call
|
||||
end
|
||||
end
|
||||
|
||||
# This is the heart of the profile context
|
||||
# An instantiated object which has all resources registered to it
|
||||
# and exposes them to the a test file.
|
||||
# rubocop:disable Lint/NestedMethodDefinition
|
||||
ctx = Class.new do
|
||||
ctx = Class.new(outer_dsl) do
|
||||
include Vulcano::DSL
|
||||
|
||||
define_method :__register_rule do |*args|
|
||||
|
@ -29,12 +74,6 @@ module Vulcano
|
|||
profile_context_owner.unregister_rule(*args)
|
||||
end
|
||||
|
||||
Vulcano::Resource.registry.each do |id, r|
|
||||
define_method id.to_sym do |*args|
|
||||
r.new(backend, *args)
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
'Profile Context Run'
|
||||
end
|
||||
|
@ -49,13 +88,13 @@ module Vulcano
|
|||
end
|
||||
|
||||
def unregister_rule(id)
|
||||
full_id = VulcanoBaseRule.full_id(@profile_id, id)
|
||||
full_id = Vulcano::Rule.full_id(@profile_id, id)
|
||||
@rules[full_id] = nil
|
||||
end
|
||||
|
||||
def register_rule(r)
|
||||
# get the full ID
|
||||
full_id = VulcanoBaseRule.full_id(@profile_id, r)
|
||||
full_id = Vulcano::Rule.full_id(@profile_id, r)
|
||||
if full_id.nil?
|
||||
# TODO: error
|
||||
return
|
||||
|
@ -65,7 +104,7 @@ module Vulcano
|
|||
if existing.nil?
|
||||
@rules[full_id] = r
|
||||
else
|
||||
VulcanoBaseRule.merge(existing, r)
|
||||
Vulcano::Rule.merge(existing, r)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,195 +1,98 @@
|
|||
# encoding: utf-8
|
||||
# copyright: 2015, Dominik Richter
|
||||
# license: All rights reserved
|
||||
require 'vulcano/base_rule'
|
||||
|
||||
module Vulcano
|
||||
class Rule < VulcanoBaseRule
|
||||
include RSpec::Core::DSL
|
||||
class Rule
|
||||
def initialize(id, _opts, &block)
|
||||
@id = id
|
||||
@impact = nil
|
||||
@__code = ''
|
||||
@__block = block
|
||||
@title = nil
|
||||
@desc = nil
|
||||
# not changeable by the user:
|
||||
@profile_id = nil
|
||||
@checks = []
|
||||
# evaluate the given definition
|
||||
instance_eval(&block) if block_given?
|
||||
end
|
||||
|
||||
def id(*_)
|
||||
# never overwrite the ID
|
||||
@id
|
||||
end
|
||||
|
||||
def impact(v = nil)
|
||||
@impact = v unless v.nil?
|
||||
@impact
|
||||
end
|
||||
|
||||
def title(v = nil)
|
||||
@title = v unless v.nil?
|
||||
@title
|
||||
end
|
||||
|
||||
def desc(v = nil)
|
||||
@desc = v unless v.nil?
|
||||
@desc
|
||||
end
|
||||
|
||||
# Override RSpec methods to add
|
||||
# IDs to each example group
|
||||
# TODO: remove this once IDs are in rspec-core
|
||||
def describe(sth, &block)
|
||||
@checks.push(['describe', [sth], block])
|
||||
end
|
||||
|
||||
# redirect all regular method calls to the
|
||||
# core DSL (which is attached to the class)
|
||||
def method_missing(m, *a, &b)
|
||||
Vulcano::Rule.__send__(m, *a, &b)
|
||||
def self.merge(dst, src)
|
||||
if src.id != dst.id
|
||||
# TODO: register an error, this case should not happen
|
||||
return
|
||||
end
|
||||
sp = src.instance_variable_get(:@profile_id)
|
||||
dp = dst.instance_variable_get(:@profile_id)
|
||||
if sp != dp
|
||||
# TODO: register an error, this case should not happen
|
||||
return
|
||||
end
|
||||
# merge all fields
|
||||
dst.impact(src.impact) unless src.impact.nil?
|
||||
dst.title(src.title) unless src.title.nil?
|
||||
dst.desc(src.desc) unless src.desc.nil?
|
||||
# merge indirect fields
|
||||
# checks defined in the source will completely eliminate
|
||||
# all checks that were defined in the destination
|
||||
sc = src.instance_variable_get(:@checks)
|
||||
unless sc.nil? || sc.empty?
|
||||
dst.instance_variable_set(:@checks, sc)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Vulcano::DSL
|
||||
def rule(id, opts = {}, &block)
|
||||
return if @skip_profile
|
||||
__register_rule Vulcano::Rule.new(id, opts, &block)
|
||||
end
|
||||
|
||||
def describe(*args, &block)
|
||||
path = block.source_location[0]
|
||||
line = block.source_location[1]
|
||||
id = "#{File.basename(path)}:#{line}"
|
||||
rule = Vulcano::Rule.new(id, {}) do
|
||||
describe(*args, &block)
|
||||
end
|
||||
__register_rule rule, &block
|
||||
end
|
||||
|
||||
def skip_rule(id)
|
||||
__unregister_rule id
|
||||
end
|
||||
|
||||
def only_if(&block)
|
||||
return unless block_given?
|
||||
@skip_profile = !block.call
|
||||
end
|
||||
|
||||
def require_rules(id, &block)
|
||||
::Vulcano::DSL.load_spec_files_for_profile self, id, false, &block
|
||||
end
|
||||
|
||||
def include_rules(id, &block)
|
||||
::Vulcano::DSL.load_spec_files_for_profile self, id, true, &block
|
||||
end
|
||||
|
||||
# Register a given rule with RSpec and
|
||||
# let it run. This happens after everything
|
||||
# else is merged in.
|
||||
def self.execute_rule(r, profile_id)
|
||||
checks = r.instance_variable_get(:@checks)
|
||||
fid = VulcanoBaseRule.full_id(r, profile_id)
|
||||
checks.each do |m, a, b|
|
||||
# check if the resource is skippable and skipped
|
||||
if a.is_a?(Array) && !a.empty? &&
|
||||
a[0].respond_to?(:resource_skipped) &&
|
||||
!a[0].resource_skipped.nil?
|
||||
cres = ::Vulcano::Rule.__send__(m, *a) do
|
||||
it a[0].resource_skipped
|
||||
# Get the full id consisting of profile id + rule id
|
||||
# for the rule. If the rule's profile id is empty,
|
||||
# the given profile_id will be used instead and also
|
||||
# set for the rule.
|
||||
def self.full_id(profile_id, rule)
|
||||
if rule.is_a?(String) or rule.nil?
|
||||
rid = rule
|
||||
else
|
||||
# As the profile context is exclusively pulled with a
|
||||
# profile ID, attach it to the rule if necessary.
|
||||
rid = rule.instance_variable_get(:@id)
|
||||
if rid.nil?
|
||||
# TODO: Message about skipping this rule
|
||||
# due to missing ID
|
||||
return nil
|
||||
end
|
||||
end
|
||||
pid = rule.instance_variable_get(:@profile_id)
|
||||
if pid.nil?
|
||||
rule.instance_variable_set(:@profile_id, profile_id)
|
||||
pid = profile_id
|
||||
end
|
||||
if pid.nil? or pid.empty?
|
||||
return rid
|
||||
else
|
||||
# execute the method
|
||||
cres = ::Vulcano::Rule.__send__(m, *a, &b)
|
||||
end
|
||||
if m == 'describe'
|
||||
set_rspec_ids(cres, fid)
|
||||
return "#{pid}/#{rid}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# merge two rules completely; all defined
|
||||
# fields from src will be overwritten in dst
|
||||
def self.merge_rules(dst, src)
|
||||
VulcanoBaseRule.merge dst, src
|
||||
end
|
||||
|
||||
# Attach an ID attribute to the
|
||||
# metadata of all examples
|
||||
# TODO: remove this once IDs are in rspec-core
|
||||
def self.set_rspec_ids(obj, id)
|
||||
obj.examples.each {|ex|
|
||||
ex.metadata[:id] = id
|
||||
}
|
||||
obj.children.each {|c|
|
||||
set_rspec_ids(c, id)
|
||||
}
|
||||
end
|
||||
|
||||
def self.load_spec_file_for_profile(profile_id, file, rule_registry, only_ifs)
|
||||
raw = File.read(file)
|
||||
# TODO: error-handling
|
||||
|
||||
ctx = Vulcano::ProfileContext.new(profile_id, rule_registry, only_ifs)
|
||||
ctx.instance_eval(raw, file, 1)
|
||||
end
|
||||
|
||||
def self.load_spec_files_for_profile(bind_context, profile_id, include_all, &block)
|
||||
# get all spec files
|
||||
files = get_spec_files_for_profile profile_id
|
||||
# load all rules from spec files
|
||||
rule_registry = {}
|
||||
# TODO: handling of only_ifs
|
||||
only_ifs = []
|
||||
files.each do |file|
|
||||
load_spec_file_for_profile(profile_id, file, rule_registry, only_ifs)
|
||||
end
|
||||
|
||||
# interpret the block and create a set of rules from it
|
||||
block_registry = {}
|
||||
if block_given?
|
||||
ctx = Vulcano::ProfileContext.new(profile_id, block_registry, only_ifs)
|
||||
ctx.instance_eval(&block)
|
||||
end
|
||||
|
||||
# if all rules are not included, select only the ones
|
||||
# that were defined in the block
|
||||
unless include_all
|
||||
remove = rule_registry.keys - block_registry.keys
|
||||
remove.each { |key| rule_registry.delete(key) }
|
||||
end
|
||||
|
||||
# merge the rules in the block_registry (adjustments) with
|
||||
# the rules in the rule_registry (included)
|
||||
block_registry.each do |id, r|
|
||||
org = rule_registry[id]
|
||||
if org.nil?
|
||||
# TODO: print error because we write alter a rule that doesn't exist
|
||||
elsif r.nil?
|
||||
rule_registry.delete(id)
|
||||
else
|
||||
merge_rules(org, r)
|
||||
end
|
||||
end
|
||||
|
||||
# finally register all combined rules
|
||||
rule_registry.each do |_id, rule|
|
||||
bind_context.__register_rule rule
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_spec_files_for_profile(id)
|
||||
base_path = '/etc/vulcanosec/tests'
|
||||
path = File.join(base_path, id)
|
||||
# find all files to be included
|
||||
files = []
|
||||
if File.directory? path
|
||||
# include all library paths, if they exist
|
||||
libdir = File.join(path, 'lib')
|
||||
if File.directory? libdir and !$LOAD_PATH.include?(libdir)
|
||||
$LOAD_PATH.unshift(libdir)
|
||||
end
|
||||
files = Dir[File.join(path, 'spec', '*_spec.rb')]
|
||||
end
|
||||
files
|
||||
end
|
||||
end
|
||||
|
||||
module Vulcano::GlobalDSL
|
||||
def __register_rule(r)
|
||||
# make sure the profile id is attached to the rule
|
||||
::Vulcano::DSL.execute_rule(r, __profile_id)
|
||||
end
|
||||
|
||||
def __unregister_rule(_id)
|
||||
end
|
||||
end
|
||||
|
||||
module Vulcano::DSLHelper
|
||||
def self.bind_dsl(scope)
|
||||
# rubocop:disable Lint/NestedMethodDefinition
|
||||
(class << scope; self; end).class_exec do
|
||||
include Vulcano::DSL
|
||||
include Vulcano::GlobalDSL
|
||||
def __profile_id
|
||||
ENV['VULCANOSEC_PROFILE_ID']
|
||||
end
|
||||
end
|
||||
# rubocop:enable all
|
||||
end
|
||||
end
|
||||
|
||||
::Vulcano::DSLHelper.bind_dsl(self)
|
||||
|
|
Loading…
Reference in a new issue