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 end
# method for inputs; import input handling # method for inputs; import input handling
# TODO: deprecate name, use input()
define_method :attribute do |name, options = nil| define_method :attribute do |name, options = nil|
if options.nil? if options.nil?
Inspec::InputRegistry.find_input(name, profile_id).value Inspec::InputRegistry.find_input(name, profile_id).value
else else
profile_context_owner.register_input(name, options) Inspec::InputRegistry.register_input(name, profile_id, options).value
end end
end end

View file

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

View file

@ -81,7 +81,7 @@ module Inspec
end end
attr_reader :source_reader, :backend, :runner_context, :check_mode 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, :tests
def_delegator :@source_reader, :libraries def_delegator :@source_reader, :libraries
def_delegator :@source_reader, :metadata 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, # 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. # we can create any inputs that were provided by various mechanisms.
Inspec::InputRegistry.bind_profile_inputs( 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 # Remaining args are possible sources of inputs
# TODO: deprecation checks throughout # TODO: deprecation checks throughout
cli_attr_files: options[:attrs], cli_input_files: options[:runner_conf].final_options[:attrs], # From --attrs
profile_metadata: metadata, 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 = @runner_context =

View file

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

View file

@ -2,6 +2,7 @@
require 'helper' require 'helper'
require 'inspec/input_registry' require 'inspec/input_registry'
require 'inspec/secrets'
describe Inspec::InputRegistry do describe Inspec::InputRegistry do
let(:registry) { Inspec::InputRegistry } 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'" ex.message.must_match "Profile 'dummy_profile' does not have an input with name 'unknown_input'"
end end
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 end

View file

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

View file

@ -24,12 +24,5 @@ module PluginV2BackCompat
assert_equal Inspec::Plugins::SourceReader, klass assert_equal Inspec::Plugins::SourceReader, klass
end 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
end end

View file

@ -2,141 +2,74 @@
# copyright: 2017, Chef Software Inc. # copyright: 2017, Chef Software Inc.
require 'helper' require 'helper'
require 'inspec/secrets'
describe Inspec::Runner do describe Inspec::Runner do
describe '#load_inputs' do let(:runner) { Inspec::Runner.new({ command_runner: :generic }) }
let(:runner) { Inspec::Runner.new({ command_runner: :generic }) }
before do # =============================================================== #
Inspec::Runner.any_instance.stubs(:validate_inputs_file_readability!) # Reporter Options
# =============================================================== #
describe 'confirm reporter defaults to cli' do
it 'defaults to cli when format and reporter not set' do
opts = { command_runner: :generic, backend_cache: true }
runner = Inspec::Runner.new(opts)
config = runner.instance_variable_get(:"@conf")
expected = { 'cli' => { 'stdout' => true } }
config['reporter'].must_equal expected
end end
describe 'confirm reporter defaults to cli' do it 'does not default when format is set' do
it 'defaults to cli when format and reporter not set' do opts = { command_runner: :generic, backend_cache: true, 'reporter' => ['json'] }
opts = { command_runner: :generic, backend_cache: true } runner = Inspec::Runner.new(opts)
runner = Inspec::Runner.new(opts) config = runner.instance_variable_get(:"@conf")
config = runner.instance_variable_get(:"@conf") expected = { 'json' => { 'stdout' => true } }
expected = { 'cli' => { 'stdout' => true } } config['reporter'].must_equal expected
config['reporter'].must_equal expected
end
it 'does not default when format is set' do
opts = { command_runner: :generic, backend_cache: true, 'reporter' => ['json'] }
runner = Inspec::Runner.new(opts)
config = runner.instance_variable_get(:"@conf")
expected = { 'json' => { 'stdout' => true } }
config['reporter'].must_equal expected
end
it 'delets format if set to a rspec format' do
opts = { command_runner: :generic, backend_cache: true, 'reporter' => ['progress'] }
runner = Inspec::Runner.new(opts)
config = runner.instance_variable_get(:"@conf")
config['reporter'].must_equal Hash.new
end
end end
describe 'testing runner.run exit codes' do it 'delets format if set to a rspec format' do
it 'returns proper exit code when no profile is added' do opts = { command_runner: :generic, backend_cache: true, 'reporter' => ['progress'] }
proc { runner.run.must_equal 0 } runner = Inspec::Runner.new(opts)
end config = runner.instance_variable_get(:"@conf")
config['reporter'].must_equal Hash.new
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 }
runner = Inspec::Runner.new(opts)
backend = runner.instance_variable_get(:@backend)
backend.backend.cache_enabled?(:command).must_equal true
end
end
describe 'when backend caching is disabled' do
it 'returns a backend without caching' do
opts = { command_runner: :generic, backend_cache: false }
runner = Inspec::Runner.new(opts)
backend = runner.instance_variable_get(:@backend)
backend.backend.cache_enabled?(:command).must_equal false
end end
describe 'when backend caching is enabled' do it 'returns a backend without caching as default' do
it 'returns a backend with caching' do backend = runner.instance_variable_get(:@backend)
opts = { command_runner: :generic, backend_cache: true } backend.backend.cache_enabled?(:command).must_equal false
runner = Inspec::Runner.new(opts)
backend = runner.instance_variable_get(:@backend)
backend.backend.cache_enabled?(:command).must_equal true
end
end
describe 'when backend caching is disabled' do
it 'returns a backend without caching' do
opts = { command_runner: :generic, backend_cache: false }
runner = Inspec::Runner.new(opts)
backend = runner.instance_variable_get(:@backend)
backend.backend.cache_enabled?(:command).must_equal false
end
it 'returns a backend without caching as default' do
backend = runner.instance_variable_get(:@backend)
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 end
end end