Checkpoint commit after Input rename; precedence is broken

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Clinton Wolfe 2019-02-25 21:05:18 -05:00
parent 8ef5eb46b1
commit aae54d2cb6
8 changed files with 209 additions and 180 deletions

View file

@ -168,11 +168,12 @@ module Inspec
end
# method for inputs; import input handling
# TODO: deprecate name, use input()
define_method :attribute do |name, options = nil|
if options.nil?
Inspec::InputRegistry.find_input(name, profile_id).value
else
profile_context_owner.register_input(name, options)
Inspec::InputRegistry.register_input(name, profile_id, options).value
end
end

View file

@ -1,6 +1,8 @@
require 'forwardable'
require 'singleton'
require 'inspec/objects/input'
require 'inspec/secrets'
require 'inspec/exceptions'
module Inspec
# The InputRegistry's responsibilities include:
@ -58,13 +60,15 @@ module Inspec
inputs_by_profile[profile][name]
end
def register_input(name, profile, options = {})
# check for a profile override name
if profile_known?(profile) && inputs_by_profile[profile][name] && options.empty?
inputs_by_profile[profile][name]
def register_input(input_name, profile_name, options = {})
if profile_known?(profile_name) && inputs_by_profile[profile_name][input_name] && options.empty?
# Just accessing the input - then why did they call register???
# TODO: handle the "declaration" case better
inputs_by_profile[profile_name][input_name]
else
inputs_by_profile[profile] = {} unless profile_known?(profile)
inputs_by_profile[profile][name] = Inspec::Input.new(name, options)
inputs_by_profile[profile_name] = {} unless profile_known?(profile_name)
# BUG: This a clobbering operation, regardless of precendence!
inputs_by_profile[profile_name][input_name] = Inspec::Input.new(input_name, options)
end
end
@ -74,20 +78,20 @@ module Inspec
# This method is called by the Profile as soon as it has
# enough context to allow binding inputs to it.
def bind_profile_inputs(profile_obj, sources = {})
inputs_by_profile[profile_obj.profile_name] ||= {}
def bind_profile_inputs(profile_name, sources = {})
inputs_by_profile[profile_name] ||= {}
# In a more perfect world, we could let the core plugins choose
# self-determine what to do; but as-is, the APIs that call this
# are a bit over-constrained.
bind_inputs_from_metadata(profile_obj, sources[:metadata])
bind_inputs_from_input_files(profile_obj, sources[:cli_input_files])
bind_inputs_from_runner_api(profile_obj, sources[:runner_api])
bind_inputs_from_metadata(profile_name, sources[:metadata])
bind_inputs_from_input_files(profile_name, sources[:cli_input_files])
bind_inputs_from_runner_api(profile_name, sources[:runner_api])
end
private
def bind_inputs_from_runner_api(profile_obj, input_hash)
def bind_inputs_from_runner_api(profile_name, input_hash)
# TODO: move this into a core plugin
return if input_hash.nil?
@ -96,11 +100,11 @@ module Inspec
# These arrive as a bare hash - values are raw values, not options
input_hash.each do |input_name, input_value|
input_options = { value: input_value }
register_input(input_name, profile_obj.profile_name, input_options)
register_input(input_name, profile_name, input_options)
end
end
def bind_inputs_from_input_files(profile_obj, file_list)
def bind_inputs_from_input_files(profile_name, file_list)
# TODO: move this into a core plugin
return if file_list.nil?
@ -120,7 +124,7 @@ module Inspec
next if data.inputs.nil?
data.inputs.each do |input_name, input_value|
input_options = { value: input_value }
register_input(input_name, profile_obj.profile_name, input_options)
register_input(input_name, profile_name, input_options)
end
end
end
@ -141,7 +145,7 @@ module Inspec
true
end
def bind_inputs_from_metadata(profile_obj, profile_metadata_obj)
def bind_inputs_from_metadata(profile_name, profile_metadata_obj)
# TODO: move this into a core plugin
# TODO: add deprecation stuff
return if profile_metadata_obj.nil? # Metadata files are technically optional
@ -150,7 +154,7 @@ module Inspec
profile_metadata_obj.params[:attributes].each do |input|
input_options = input.dup
input_name = input_options.delete(:name)
register_input(input_name, profile_obj.profile_name, input_options)
register_input(input_name, profile_name, input_options)
end
elsif profile_metadata_obj.params.key?(:attributes)
Inspec::Log.warn 'Inputs must be defined as an Array. Skipping current definition.'

View file

@ -81,7 +81,7 @@ module Inspec
end
attr_reader :source_reader, :backend, :runner_context, :check_mode
attr_accessor :parent_profile, :profile_name
attr_accessor :parent_profile, :profile_id, :profile_name
def_delegator :@source_reader, :tests
def_delegator :@source_reader, :libraries
def_delegator :@source_reader, :metadata
@ -122,12 +122,13 @@ module Inspec
# it is the single source of truth. Now that we have a profile object,
# we can create any inputs that were provided by various mechanisms.
Inspec::InputRegistry.bind_profile_inputs(
self, # Every input only exists in the context of a profile
# Every input only exists in the context of a profile
metadata.params[:name], # TODO: test this with profile aliasing
# Remaining args are possible sources of inputs
# TODO: deprecation checks throughout
cli_attr_files: options[:attrs],
cli_input_files: options[:runner_conf].final_options[:attrs], # From --attrs
profile_metadata: metadata,
runner_api: options[:attributes], # This is the route the audit_cookbook and kitchen-inspec take
runner_api: options[:runner_conf].final_options[:attributes], # This is the route the audit_cookbook and kitchen-inspec take
)
@runner_context =

View file

@ -7,7 +7,7 @@ describe 'inputs' do
let(:inputs_profiles_path) { File.join(profile_path, 'inputs') }
[
'flat',
'nested',
#'nested',
].each do |input_file|
it "runs OK on #{input_file} inputs" do
cmd = 'exec '
@ -15,9 +15,9 @@ describe 'inputs' do
cmd += ' --no-create-lockfile'
cmd += ' --input-file ' + File.join(inputs_profiles_path, 'basic', 'files', "#{input_file}.yaml")
cmd += ' --controls ' + input_file
out = inspec(cmd)
out.stderr.must_equal ''
out.exit_status.must_equal 0
result = run_inspec_process(cmd)
result.stderr.must_equal ''
result.exit_status.must_equal 0
end
end
@ -69,39 +69,36 @@ describe 'inputs' do
# TODO: fix attribute inheritance override test
# we have one failing case on this - run manually to see
# For now, reduce cases to 20; we'll be reworking all this soon anyway
# out.stdout.must_include '21 successful'
# out.exit_status.must_equal 0
# result.stdout.must_include '21 successful'
# result.exit_status.must_equal 0
out.stdout.must_include '20 successful' # and one failing
result.stdout.must_include '20 successful' # and one failing
end
it "does not error when inputs are empty" do
cmd = 'exec '
cmd += File.join(inputs_profiles_path, 'metadata-empty')
cmd += ' --no-create-lockfile'
out = inspec(cmd)
out.stdout.must_include 'WARN: Inputs must be defined as an Array. Skipping current definition.'
out.exit_status.must_equal 0
result = run_inspec_process(cmd, json: true)
result.stdout.must_include 'WARN: Inputs must be defined as an Array. Skipping current definition.'
result.exit_status.must_equal 0
end
it "errors with invalid input types" do
cmd = 'exec '
cmd += File.join(inputs_profiles_path, 'metadata-invalid')
cmd += ' --no-create-lockfile'
out = inspec(cmd)
out.stderr.must_equal "Type 'Color' is not a valid input type.\n"
out.stdout.must_equal ''
out.exit_status.must_equal 1
result = run_inspec_process(cmd, json: true)
result.stderr.must_equal "Type 'Color' is not a valid input type.\n"
result.stdout.must_equal ''
result.exit_status.must_equal 1
end
it "errors with required input not defined" do
cmd = 'exec '
cmd += File.join(inputs_profiles_path, 'required')
cmd += ' --no-create-lockfile'
out = inspec(cmd)
out.stderr.must_equal "Input 'username' is required and does not have a value.\n"
out.stdout.must_equal ''
out.exit_status.must_equal 1
result = run_inspec_process(cmd, json: true)
result.stderr.must_equal "Input 'username' is required and does not have a value.\n"
result.stdout.must_equal ''
result.exit_status.must_equal 1
end
# TODO - add test for backwards compatibility using 'attribute' in DSL

View file

@ -2,6 +2,7 @@
require 'helper'
require 'inspec/input_registry'
require 'inspec/secrets'
describe Inspec::InputRegistry do
let(:registry) { Inspec::InputRegistry }
@ -68,4 +69,103 @@ describe Inspec::InputRegistry do
ex.message.must_match "Profile 'dummy_profile' does not have an input with name 'unknown_input'"
end
end
# =============================================================== #
# Loading inputs from --attrs
# =============================================================== #
describe '#bind_profile_inputs' do
before do
Inspec::InputRegistry.any_instance.stubs(:validate_inputs_file_readability!)
end
let(:profile) do
p = mock()
p.expects(:profile_name).returns('test_fixture_profile').at_least_once
p
end
let(:seen_inputs) do
registry.bind_profile_inputs(profile, sources)
inputs = registry.list_inputs_for_profile('test_fixture_profile')
# Flatten Input objects down to their values
inputs.keys.each { |input_name| inputs[input_name] = inputs[input_name].value }
inputs
end
describe 'when no CLI --attrs are specified' do
let(:sources) { { cli_input_files: [] } }
it 'returns an empty hash' do
seen_inputs.must_equal({})
end
end
describe 'when a CLI --attrs option is provided and does not resolve' do
let(:sources) { { cli_input_files: ['nope.jpg'] } }
it 'raises an exception' do
Inspec::SecretsBackend.expects(:resolve).with('nope.jpg').returns(nil)
proc { seen_inputs }.must_raise Inspec::Exceptions::SecretsBackendNotFound
end
end
describe 'when a CLI --attrs option is provided and has no inputs' do
let(:sources) { { cli_input_files: ['empty.yaml'] } }
it 'returns an empty hash' do
secrets = mock
secrets.stubs(:inputs).returns(nil)
Inspec::SecretsBackend.expects(:resolve).with('empty.yaml').returns(secrets)
seen_inputs.must_equal({})
end
end
describe 'when a CLI --attrs file is provided and has inputs' do
let(:sources) { { cli_input_files: ['file1.yaml'] } }
it 'returns a hash containing the inputs' do
fixture_inputs = { foo: 'bar' }
secrets = mock
secrets.stubs(:inputs).returns(fixture_inputs)
Inspec::SecretsBackend.expects(:resolve).with('file1.yaml').returns(secrets)
seen_inputs.must_equal(fixture_inputs)
end
end
describe 'when multiple CLI --attrs option args are provided and one fails' do
let(:sources) { { cli_input_files: ['file1.yaml', 'file2.yaml'] } }
it 'raises an exception' do
secrets = mock
secrets.stubs(:inputs).returns(nil)
Inspec::SecretsBackend.expects(:resolve).with('file1.yaml').returns(secrets)
Inspec::SecretsBackend.expects(:resolve).with('file2.yaml').returns(nil)
proc { seen_inputs }.must_raise Inspec::Exceptions::SecretsBackendNotFound
end
end
describe 'when multiple CLI --attrs option args are provided and one has no inputs' do
let(:sources) { { cli_input_files: ['file1.yaml', 'file2.yaml'] } }
it 'returns a hash containing the inputs from the valid files' do
inputs = { foo: 'bar' }
secrets1 = mock
secrets1.stubs(:inputs).returns(nil)
secrets2 = mock
secrets2.stubs(:inputs).returns(inputs)
Inspec::SecretsBackend.expects(:resolve).with('file1.yaml').returns(secrets1)
Inspec::SecretsBackend.expects(:resolve).with('file2.yaml').returns(secrets2)
seen_inputs.must_equal(inputs)
end
end
describe 'when multiple CLI --attrs option args are provided and all have inputs' do
let(:sources) { { cli_input_files: ['file1.yaml', 'file2.yaml'] } }
it 'returns a hash containing all the inputs' do
options = { attrs: ['file1.yaml', 'file2.yaml'] }
secrets1 = mock
secrets1.stubs(:inputs).returns({ key1: 'value1' })
secrets2 = mock
secrets2.stubs(:inputs).returns({ key2: 'value2' })
Inspec::SecretsBackend.expects(:resolve).with('file1.yaml').returns(secrets1)
Inspec::SecretsBackend.expects(:resolve).with('file2.yaml').returns(secrets2)
seen_inputs.must_equal({ key1: 'value1', key2: 'value2' })
end
end
end
end

View file

@ -16,10 +16,10 @@ tests = expecteds.keys.map do |test_name|
end
control 'flat' do
tests.each do |info|
describe "#{info[:name]} using string key" do
subject { info[:input_via_string] }
it { should eq info[:expected] }
tests.each do |details|
describe "#{details[:name]} using string key" do
subject { details[:input_via_string] }
it { should eq details[:expected] }
end
end
end

View file

@ -24,12 +24,5 @@ module PluginV2BackCompat
assert_equal Inspec::Plugins::SourceReader, klass
end
def test_get_plugin_v1_base_for_secrets
klass = Inspec.secrets(1)
assert_kind_of Class, klass
assert Inspec::Plugins.const_defined? :Secret
assert_equal Inspec::Plugins::Secret, klass
end
end
end

View file

@ -2,14 +2,14 @@
# copyright: 2017, Chef Software Inc.
require 'helper'
require 'inspec/secrets'
describe Inspec::Runner do
describe '#load_inputs' do
let(:runner) { Inspec::Runner.new({ command_runner: :generic }) }
before do
Inspec::Runner.any_instance.stubs(:validate_inputs_file_readability!)
end
# =============================================================== #
# Reporter Options
# =============================================================== #
describe 'confirm reporter defaults to cli' do
it 'defaults to cli when format and reporter not set' do
@ -36,12 +36,20 @@ describe Inspec::Runner do
end
end
# =============================================================== #
# Exit Codes
# =============================================================== #
describe 'testing runner.run exit codes' do
it 'returns proper exit code when no profile is added' do
proc { runner.run.must_equal 0 }
end
end
# =============================================================== #
# Backend Caching
# =============================================================== #
describe 'when backend caching is enabled' do
it 'returns a backend with caching' do
opts = { command_runner: :generic, backend_cache: true }
@ -64,79 +72,4 @@ describe Inspec::Runner do
backend.backend.cache_enabled?(:command).must_equal false
end
end
describe 'when no input files are specified' do
it 'returns an empty hash' do
options = {}
runner.load_inputs(options).must_equal({})
end
end
describe 'when an input file is provided and does not resolve' do
it 'raises an exception' do
options = { input_file: ['nope.jpg'] }
Inspec::SecretsBackend.expects(:resolve).with('nope.jpg').returns(nil)
proc { runner.load_inputs(options) }.must_raise Inspec::Exceptions::SecretsBackendNotFound
end
end
describe 'when an input file is provided and has no inputs' do
it 'returns an empty hash' do
secrets = mock
secrets.stubs(:inputs).returns(nil)
options = { input_file: ['empty.yaml'] }
Inspec::SecretsBackend.expects(:resolve).with('empty.yaml').returns(secrets)
runner.load_inputs(options).must_equal({})
end
end
describe 'when an input file is provided and has inputs' do
it 'returns a hash containing the inputs' do
options = { input_file: ['file1.yaml'] }
inputs = { foo: 'bar' }
secrets = mock
secrets.stubs(:inputs).returns(inputs)
Inspec::SecretsBackend.expects(:resolve).with('file1.yaml').returns(secrets)
runner.load_inputs(options).must_equal(inputs)
end
end
describe 'when multiple input files are provided and one fails' do
it 'raises an exception' do
options = { input_file: ['file1.yaml', 'file2.yaml'] }
secrets = mock
secrets.stubs(:inputs).returns(nil)
Inspec::SecretsBackend.expects(:resolve).with('file1.yaml').returns(secrets)
Inspec::SecretsBackend.expects(:resolve).with('file2.yaml').returns(nil)
proc { runner.load_inputs(options) }.must_raise Inspec::Exceptions::SecretsBackendNotFound
end
end
describe 'when multiple input files are provided and one has no inputs' do
it 'returns a hash containing the inputs from the valid files' do
options = { input_file: ['file1.yaml', 'file2.yaml'] }
inputs = { foo: 'bar' }
secrets1 = mock
secrets1.stubs(:inputs).returns(nil)
secrets2 = mock
secrets2.stubs(:inputs).returns(inputs)
Inspec::SecretsBackend.expects(:resolve).with('file1.yaml').returns(secrets1)
Inspec::SecretsBackend.expects(:resolve).with('file2.yaml').returns(secrets2)
runner.load_inputs(options).must_equal(inputs)
end
end
describe 'when multiple input files are provided and all have inputs' do
it 'returns a hash containing all the inputs' do
options = { input_file: ['file1.yaml', 'file2.yaml'] }
secrets1 = mock
secrets1.stubs(:inputs).returns({ key1: 'value1' })
secrets2 = mock
secrets2.stubs(:inputs).returns({ key2: 'value2' })
Inspec::SecretsBackend.expects(:resolve).with('file1.yaml').returns(secrets1)
Inspec::SecretsBackend.expects(:resolve).with('file2.yaml').returns(secrets2)
runner.load_inputs(options).must_equal({ key1: 'value1', key2: 'value2' })
end
end
end
end