Allows input and control to have the same name

In https://github.com/inspec/inspec/issues/4936 the issue was reported that naming an input the same as a control caused an unexpected failure.

In that particular case, the naming was a result of a pre-waivers workaround which is no longer necessary, but ultimately a breakage of that name clash is an unexpected occurrance.

Due to how inputs are named and registered, `__apply_waivers` thinks that an object is a waiver that is not a waiver and tries to process it. On the micro level, it breaks when trying to pass a variable to a string as if it were a Hash.

It is imperative that we preserve 100% of the current featureset, pass our tests, and fix this edge case along with new test coverage for the failure.

This PR updates the code to do a slightly more elegant and small ‘waiver check’ to stop the namespace clash from breaking our code.

Signed-off-by: Nick Schwaderer <nschwaderer@chef.io>
This commit is contained in:
Nick Schwaderer 2020-04-29 11:52:28 +01:00
parent 5b0fdfec89
commit a1129f9efc
6 changed files with 46 additions and 4 deletions

View file

@ -332,7 +332,7 @@ module Inspec
input_name = @__rule_id # TODO: control ID slugging
registry = Inspec::InputRegistry.instance
input = registry.inputs_by_profile.dig(__profile_id, input_name)
return unless input
return unless input && input.has_value? && input.value.is_a?(Hash)
# An InSpec Input is a datastructure that tracks a profile parameter
# over time. Its value can be set by many sources, and it keeps a

View file

@ -0,0 +1,7 @@
# This fixture tests for a regression found here: https://github.com/inspec/inspec/issues/4936
control '01_my_control' do
only_if { input('01_my_control', value: 'false') == 'false' }
describe true do
it { should eq true }
end
end

View file

@ -0,0 +1,5 @@
name: namespace-clash
summary: Verifies input and control namespace can safely clash
version: 0.1.0
supports:
platform: os

View file

@ -176,8 +176,8 @@ describe "inspec exec with json formatter" do
"summary" => "Demonstrates the use of InSpec Compliance Profile",
"version" => "1.0.0",
"supports" => [{ "platform-family" => "unix" }, { "platform-family" => "windows" }],
"status" => "loaded",
"attributes" => [],
"status" => "loaded",
})
_(groups.sort_by { |x| x["id"] }).must_equal([

View file

@ -42,11 +42,11 @@ describe "inspec exec with junit formatter" do
describe "the test suite" do
let(:suite) { doc.elements.to_a("//testsuites/testsuite").first }
it "must have 6 testcase children" do
it "must have 4 testcase children" do
_(suite.elements.to_a("//testcase").length).must_equal 4
end
it "has the tests attribute with 5 total tests" do
it "has the tests attribute with 4 total tests" do
_(suite.attribute("tests").value).must_equal "4"
end

View file

@ -10,6 +10,23 @@ describe "waivers" do
let(:controls_by_id) { run_result; @json.dig("profiles", 0, "controls").map { |c| [c["id"], c] }.to_h }
let(:cmd) { "exec #{waivers_profiles_path}/#{profile_name} --input-file #{waivers_profiles_path}/#{profile_name}/files/#{waiver_file}" }
attr_accessor :out
def inspec(commandline, prefix = nil)
@stdout = @stderr = nil
self.out = super
end
def stdout
@stdout ||= out.stdout
.force_encoding(Encoding::UTF_8)
end
def stderr
@stderr ||= out.stderr
.force_encoding(Encoding::UTF_8)
end
def assert_test_outcome(expected, control_id)
assert_equal expected, controls_by_id.dig(control_id, "results", 0, "status")
end
@ -88,6 +105,19 @@ describe "waivers" do
end
end
describe "an input and control with the same name" do
# This is a test for a regression articulated here:
# https://github.com/inspec/inspec/issues/4936
it "can execute when control namespace clashes with input" do
inspec("exec " + "#{waivers_profiles_path}/namespace-clash" + " --no-create-lockfile" + " --no-color")
_(stdout).wont_include("Control Source Code Error")
_(stdout).must_include "\nProfile Summary: 1 successful control, 0 control failures, 0 controls skipped\n"
_(stderr).must_equal ""
assert_exit_code 0, out
end
end
describe "an inherited profile" do
let(:profile_name) { "waiver-wrapper" }
let(:waiver_file) { "waivers.yaml" }