From 2e1106b933f54c20dedd0ca524badf886ae87954 Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Sat, 20 Jun 2015 00:00:53 +0200 Subject: [PATCH] feature: add rule hierarchy with include and require rules include_rules 'vulcanosec/ssh' this will include all rules defined in vulcanosec/ssh require_rules 'vulcanosec/linux' this will not include any rules yet, but you may choose what you want to pull in. both have a block attached which will allow you to choose rules (for require_rules) and redefined/change existing rules as you like. small example: require_rules 'vulcanosec/linux' do rule fs-3 do impact 1.0 end end Signed-off-by: Dominik Richter --- lib/vulcano/base_rule.rb | 31 +++++++++++++- lib/vulcano/rule.rb | 89 +++++++++++++++++++++++++++++++--------- 2 files changed, 99 insertions(+), 21 deletions(-) diff --git a/lib/vulcano/base_rule.rb b/lib/vulcano/base_rule.rb index 240d31cbe..cb3a867c1 100644 --- a/lib/vulcano/base_rule.rb +++ b/lib/vulcano/base_rule.rb @@ -3,12 +3,15 @@ # license: All rights reserved class VulcanoBaseRule - def initialize(id, &block) + def initialize(id, opts, &block) @id = id @impact = nil @title = nil @desc = nil - + # not changeable by the user: + @profile_id = nil + @checks = [] + # evaluate the given definition self.instance_eval(&block) if block_given? end @@ -31,4 +34,28 @@ class VulcanoBaseRule @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) if src.impact != nil + dst.title(src.title) if src.title != nil + dst.desc(src.desc) if 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 diff --git a/lib/vulcano/rule.rb b/lib/vulcano/rule.rb index 52cc93bd2..536214e72 100644 --- a/lib/vulcano/rule.rb +++ b/lib/vulcano/rule.rb @@ -13,7 +13,6 @@ module Vulcano # IDs to each example group # TODO: remove this once IDs are in rspec-core def describe(sth, &block) - @checks ||= [] @checks.push(['describe', [sth], block]) end @@ -28,11 +27,7 @@ end module Vulcano::DSL def rule id, opts = {}, &block - __register_rule Vulcano::Rule.new(id, &block) - end - - def __register_rule r - @rules.push(r) + __register_rule Vulcano::Rule.new(id, opts, &block) end def require_rules id, &block @@ -59,6 +54,12 @@ module Vulcano::DSL 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 @@ -71,28 +72,50 @@ module Vulcano::DSL } end - def self.load_spec_file_for_profile id, file + def self.load_spec_file_for_profile profile_id, file, rule_registry raw = File::read(file) # TODO: error-handling - ctx = Vulcano::ProfileContext.new(id) + ctx = Vulcano::ProfileContext.new(profile_id, rule_registry) ctx.instance_eval(raw, file, 1) - return ctx.instance_variable_get(:@rules) end - def self.load_spec_files_for_profile bind_context, id, include_all, &block + def self.load_spec_files_for_profile bind_context, profile_id, include_all, &block # get all spec files - files = get_spec_files_for_profile id + files = get_spec_files_for_profile profile_id # load all rules from spec files - rules = files.map do |file| - load_spec_file_for_profile(id, file) - end.flatten.compact + rule_registry = {} + files.each do |file| + load_spec_file_for_profile(profile_id, file, rule_registry) + end - # TODO: interpret the block and make adjustments - # (ie include rule, adjust rule, etc...) + # interpret the block and create a set of rules from it + block_registry = {} + if block_given? + ctx = Vulcano::ProfileContext.new(profile_id, block_registry) + 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 + else + merge_rules(org, r) + end + end # finally register all combined rules - rules.each do |rule| + rule_registry.each do |id, rule| bind_context.__register_rule rule end end @@ -117,11 +140,39 @@ end module Vulcano class ProfileContext + include Vulcano::DSL - def initialize profile_id + def initialize profile_id, profile_registry @profile_id = profile_id - @rules = [] + @rules = profile_registry end + + def __register_rule r + # Profile registry consists of profile_id + rule_id + # As the profile context is exclusively pulled with a + # profile ID, attach it to the rule if necessary. + rid = r.instance_variable_get(:@id) + if rid.nil? + # TODO: Message about skipping this rule + # due to missing ID + return + end + pid = r.instance_variable_get(:@profile_id) + if pid.nil? + r.instance_variable_set(:@profile_id, @profile_id) + pid = @profile_id + end + full_id = pid + "/" + rid + + # add the rule to the registry + existing = @rules[full_id] + if existing.nil? + @rules[full_id] = r + else + VulcanoBaseRule::merge(existing, r) + end + end + end end