lib/inspec/input_registry.rb in inspec-4.3.2 vs lib/inspec/input_registry.rb in inspec-4.6.3

- old
+ new

@@ -1,20 +1,21 @@ -require 'forwardable' -require 'singleton' -require 'inspec/objects/input' -require 'inspec/secrets' -require 'inspec/exceptions' +require "forwardable" +require "singleton" +require "inspec/objects/input" +require "inspec/secrets" +require "inspec/exceptions" +require "inspec/plugin/v2" module Inspec # The InputRegistry's responsibilities include: # - maintaining a list of Input objects that are bound to profiles # - assisting in the lookup and creation of Inputs class InputRegistry include Singleton extend Forwardable - attr_reader :inputs_by_profile, :profile_aliases + attr_reader :inputs_by_profile, :profile_aliases, :plugins def_delegator :inputs_by_profile, :each def_delegator :inputs_by_profile, :[] def_delegator :inputs_by_profile, :key?, :profile_known? def_delegator :inputs_by_profile, :select def_delegator :profile_aliases, :key?, :profile_alias? @@ -23,46 +24,80 @@ # Keyed on String profile_name => Hash of String input_name => Input object @inputs_by_profile = {} # this is a list of optional profile name overrides set in the inspec.yml @profile_aliases = {} + + # Upon creation, activate all input plugins + activators = Inspec::Plugin::V2::Registry.instance.find_activators(plugin_type: :input) + + @plugins = activators.map do |activator| + activator.activate! + activator.implementation_class.new + end end #-------------------------------------------------------------# # Support for Profiles #-------------------------------------------------------------# def register_profile_alias(name, alias_name) @profile_aliases[name] = alias_name end + # Returns an Hash, name => Input that have actually been mentioned def list_inputs_for_profile(profile) inputs_by_profile[profile] = {} unless profile_known?(profile) inputs_by_profile[profile] end + # Returns an Array of input names. This includes input names + # that plugins may be able to fetch, but have not actually been + # mentioned in the control code. + def list_potential_input_names_for_profile(profile_name) + input_names_from_dsl = inputs_by_profile[profile_name].keys + input_names_from_plugins = plugins.map { |plugin| plugin.list_inputs(profile_name) } + (input_names_from_dsl + input_names_from_plugins).flatten.uniq + end + #-------------------------------------------------------------# # Support for Individual Inputs #-------------------------------------------------------------# def find_or_register_input(input_name, profile_name, options = {}) - if profile_alias?(profile_name) + if profile_alias?(profile_name) && !profile_aliases[profile_name].nil? alias_name = profile_name profile_name = profile_aliases[profile_name] handle_late_arriving_alias(alias_name, profile_name) if profile_known?(alias_name) end + # Find or create the input inputs_by_profile[profile_name] ||= {} if inputs_by_profile[profile_name].key?(input_name) inputs_by_profile[profile_name][input_name].update(options) else inputs_by_profile[profile_name][input_name] = Inspec::Input.new(input_name, options) + poll_plugins_for_update(profile_name, input_name) end inputs_by_profile[profile_name][input_name] end + def poll_plugins_for_update(profile_name, input_name) + plugins.each do |plugin| + response = plugin.fetch(profile_name, input_name) + evt = Inspec::Input::Event.new( + action: :fetch, + provider: plugin.class.plugin_name, + priority: plugin.default_priority, + hit: !response.nil? + ) + evt.value = response unless response.nil? + inputs_by_profile[profile_name][input_name].events << evt + end + end + # It is possible for a wrapper profile to create an input in metadata, # referring to the child profile by an alias that has not yet been registered. # The registry will then store the inputs under the alias, as if the alias # were a true profile. # If that happens and the child profile also mentions the input, we will @@ -113,11 +148,11 @@ evt = Inspec::Input::Event.new( value: input_value, provider: :runner_api, # TODO: suss out if audit cookbook or kitchen-inspec or something unknown priority: 40, file: loc.path, - line: loc.lineno, + line: loc.lineno ) find_or_register_input(input_name, profile_name, event: evt) end end @@ -133,20 +168,20 @@ # TODO: drop this SecretsBackend stuff, will be handled by plugin system data = Inspec::SecretsBackend.resolve(path) if data.nil? raise Inspec::Exceptions::SecretsBackendNotFound, "Cannot find parser for inputs file '#{path}'. " \ - 'Check to make sure file has the appropriate extension.' + "Check to make sure file has the appropriate extension." end next if data.inputs.nil? data.inputs.each do |input_name, input_value| evt = Inspec::Input::Event.new( value: input_value, provider: :cli_files, priority: 40, - file: path, + file: path # TODO: any way we could get a line number? ) find_or_register_input(input_name, profile_name, event: evt) end end @@ -154,51 +189,66 @@ def validate_inputs_file_readability!(path) unless File.exist?(path) raise Inspec::Exceptions::InputsFileDoesNotExist, "Cannot find input file '#{path}'. " \ - 'Check to make sure file exists.' + "Check to make sure file exists." end unless File.readable?(path) raise Inspec::Exceptions::InputsFileNotReadable, "Cannot read input file '#{path}'. " \ - 'Check to make sure file is readable.' + "Check to make sure file is readable." end true end 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 - if profile_metadata_obj.params.key?(:attributes) && profile_metadata_obj.params[:attributes].is_a?(Array) - profile_metadata_obj.params[:attributes].each do |input_orig| - input_options = input_orig.dup - input_name = input_options.delete(:name) - input_options.merge!({ priority: 30, provider: :profile_metadata, file: File.join(profile_name, 'inspec.yml') }) - evt = Inspec::Input.infer_event(input_options) - - # Profile metadata may set inputs in other profiles by naming them. - if input_options[:profile] - profile_name = input_options[:profile] || profile_name - # Override priority to force this to win. Allow user to set their own priority. - evt.priority = input_orig[:priority] || 35 - end - find_or_register_input(input_name, - profile_name, - type: input_options[:type], - required: input_options[:required], - event: evt) - end + if profile_metadata_obj.params.key?(:inputs) + raw_inputs = profile_metadata_obj.params[:inputs] elsif profile_metadata_obj.params.key?(:attributes) - Inspec::Log.warn 'Inputs must be defined as an Array. Skipping current definition.' + Inspec.deprecate(:attrs_rename_in_metadata, "Profile: '#{profile_name}'.") + raw_inputs = profile_metadata_obj.params[:attributes] + else + return end + + unless raw_inputs.is_a?(Array) + Inspec::Log.warn "Inputs must be defined as an Array in metadata files. Skipping definition from #{profile_name}." + return + end + + raw_inputs.each { |i| handle_raw_input_from_metadata(i, profile_name) } end + def handle_raw_input_from_metadata(input_orig, profile_name) + input_options = input_orig.dup + input_name = input_options.delete(:name) + input_options[:provider] = :profile_metadata + input_options[:file] = File.join(profile_name, "inspec.yml") + input_options[:priority] ||= 30 + evt = Inspec::Input.infer_event(input_options) + + # Profile metadata may set inputs in other profiles by naming them. + if input_options[:profile] + profile_name = input_options[:profile] || profile_name + # Override priority to force this to win. Allow user to set their own priority. + evt.priority = input_orig[:priority] || 35 + end + find_or_register_input( + input_name, + profile_name, + type: input_options[:type], + required: input_options[:required], + event: evt + ) + end + #-------------------------------------------------------------# # Other Support #-------------------------------------------------------------# public @@ -212,9 +262,10 @@ # have to call #instance when calling the registry [ :find_or_register_input, :register_profile_alias, :list_inputs_for_profile, + :list_potential_input_names_for_profile, :bind_profile_inputs, ].each do |meth| define_singleton_method(meth) do |*args| instance.send(meth, *args) end