diff --git a/docs-chef-io/content/inspec/cli.md b/docs-chef-io/content/inspec/cli.md index 43bfe77eb..4e8185ccf 100644 --- a/docs-chef-io/content/inspec/cli.md +++ b/docs-chef-io/content/inspec/cli.md @@ -337,7 +337,7 @@ This subcommand has additional options: * ``--target-id=TARGET_ID`` Provide a ID which will be included on reports * ``--tags=one two three`` - A list of tags, a list of regular expressions that match tags, or a hash map where each value is a tag. `exec` will run controls referenced by the listed or matching tags. + A list of tags, a list of regular expressions that match tags. `exec` will run controls referenced by the listed or matching tags. * ``--user=USER`` The login user for a remote scan. * ``--vendor-cache=VENDOR_CACHE`` diff --git a/lib/inspec/control_eval_context.rb b/lib/inspec/control_eval_context.rb index a062d84a5..5ad3f885f 100644 --- a/lib/inspec/control_eval_context.rb +++ b/lib/inspec/control_eval_context.rb @@ -53,19 +53,26 @@ module Inspec def control(id, opts = {}, &block) opts[:skip_only_if_eval] = @skip_only_if_eval - tag_ids = control_tags(&block) - if (controls_list_empty? && tags_list_empty?) || control_exist_in_controls_list?(id) || tag_exist_in_control_tags?(tag_ids) + if (controls_list_empty? && tags_list_empty?) || control_exist_in_controls_list?(id) register_control(Inspec::Rule.new(id, profile_id, resources_dsl, opts, &block)) + elsif !tags_list_empty? + # Inside elsif rule is initialised before registering it because it enables fetching of control tags + # This condition is only true when --tags option is used + inspec_rule = Inspec::Rule.new(id, profile_id, resources_dsl, opts, &block) + tag_ids = control_tags(inspec_rule) + register_control(inspec_rule) if tag_exist_in_control_tags?(tag_ids) end end alias rule control - def control_tags(&block) - tag_source = block.source.split("\n").select { |src| src.split.first.eql?("tag") } - tag_source = tag_source.map { |src| src.sub("tag", "").strip }.map { |src| src.split(",").map { |final_src| final_src.sub(/([^:]*):/, "") } }.flatten - output = tag_source.map { |src| src.sub(/\[|\]/, "") }.map { |src| instance_eval(src) } - output.compact.uniq + def control_tags(inspec_rule) + all_tags = [] + inspec_rule.tag.each do |key, value| + all_tags.push(key) + all_tags.push(value) unless value.nil? + end + all_tags.flatten.compact.uniq.map(&:to_s) rescue [] end diff --git a/test/fixtures/profiles/control-tags/controls/example.rb b/test/fixtures/profiles/control-tags/controls/example.rb index b96cfdc2b..9c140017d 100644 --- a/test/fixtures/profiles/control-tags/controls/example.rb +++ b/test/fixtures/profiles/control-tags/controls/example.rb @@ -1,8 +1,16 @@ control "basic" do tag "tag1" + tag :symbol_key1, :symbol_key2 tag severity: nil tag data: "tag2" tag data_arr: ["tag3", "tag4"] + tag error1: "Line with a line-feed + error" + tag error2: "Line with a comma,error" + tag cci: ['CCI-000366'] + tag legacy: [] + tag nist: ["AU-9", "AU-9 (3)", "AC-3 (4)", "AC-6 (10)"] + tag ref: "http:example.html:CIS CSC v6.0 #5.1;" describe(true) { it { should eq true } } end diff --git a/test/functional/inspec_exec_test.rb b/test/functional/inspec_exec_test.rb index 42d7a9654..f1872ff4a 100644 --- a/test/functional/inspec_exec_test.rb +++ b/test/functional/inspec_exec_test.rb @@ -258,7 +258,7 @@ Test Summary: 0 successful, 0 failures, 0 skipped it "executes only specified controls when selecting the controls by using regex on tags" do inspec("exec " + File.join(profile_path, "control-tags") + " --no-create-lockfile --tags '/\s+/'") _(stdout).must_include "true is expected to eq true\n" - _(stdout).must_include "Test Summary: 1 successful, 0 failures, 0 skipped\n" + _(stdout).must_include "Test Summary: 2 successful, 0 failures, 0 skipped\n" _(stderr).must_equal "" assert_exit_code 0, out @@ -282,6 +282,15 @@ Test Summary: 0 successful, 0 failures, 0 skipped assert_exit_code 100, out end + it "executes profile successfully when tags are used with single element array, punctuations and linefeeds" do + inspec("exec " + File.join(profile_path, "control-tags") + " --no-create-lockfile --tags tag1 'Line with a comma,error' CCI-000366") + _(stdout).must_include "true is expected to eq true\n" + _(stdout).must_include "Test Summary: 1 successful, 0 failures, 0 skipped\n" + _(stderr).must_equal "" + + assert_exit_code 0, out + end + it "reports whan a profile cannot be loaded" do inspec("exec " + File.join(profile_path, "raise_outside_control") + " --no-create-lockfile") _(stdout).must_match(/Profile:[\W]+InSpec Profile \(raise_outside_control\)/)