class Inspec::Input
def self.infer_event(options)
2. By options[:value]
1. By event.value (preferred)
We can determine a value:
def self.infer_event(options) # Don't rely on this working; you really should be passing a proper Input::Event # with the context information you have. location = Input::Event.probe_stack event = Input::Event.new( action: :set, provider: options[:provider] || :unknown, priority: options[:priority] || Inspec::Input::DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER, file: location.path, line: location.lineno ) if options.key?(:default) Inspec.deprecate(:attrs_value_replaces_default, "attribute name: '#{name}'") if options.key?(:value) Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default" options.delete(:default) else options[:value] = options.delete(:default) end end event.value = options[:value] if options.key?(:value) options[:event] = event end
def _update_set_metadata(options)
def _update_set_metadata(options) # Basic metadata @title = options[:title] if options.key?(:title) @description = options[:description] if options.key?(:description) @required = options[:required] if options.key?(:required) @identifier = options[:identifier] if options.key?(:identifier) # TODO: determine if this is ever used @type = options[:type] if options.key?(:type) @sensitive = options[:sensitive] if options.key?(:sensitive) @pattern = options[:pattern] if options.key?(:pattern) end
def current_value(warn_on_missing = true)
def current_value(warn_on_missing = true) # Examine the events to determine highest-priority value. Tie-break # by using the last one set. events_that_set_a_value = events.select(&:value_has_been_set?) winning_priority = events_that_set_a_value.map(&:priority).max winning_events = events_that_set_a_value.select { |e| e.priority == winning_priority } winning_event = winning_events.last # Last for tie-break if winning_event.nil? # No value has been set - return special no value object NO_VALUE_SET.new(name, warn_on_missing) else winning_event.value # May still be nil end end
def diagnostic_string
def diagnostic_string "Input #{name}, with history:\n" + events.map(&:diagnostic_string).map { |line| " #{line}" }.join("\n") end
def enforce_pattern_restriction!
def enforce_pattern_restriction! return unless pattern return unless has_value? string_value = current_value(false).to_s valid_pattern = string_value.match?(pattern) unless valid_pattern error = Inspec::Input::ValidationError.new error.input_name = @name error.input_value = string_value error.input_pattern = pattern raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to pattern '#{error.input_pattern}'." end end
def enforce_required_validation!
def enforce_required_validation! return unless required # skip if we are not doing an exec call (archive/vendor/check) return unless Inspec::BaseCLI.inspec_cli_command == :exec proposed_value = current_value(false) if proposed_value.nil? || proposed_value.is_a?(NO_VALUE_SET) error = Inspec::Input::RequiredError.new error.input_name = name raise error, "Input '#{error.input_name}' is required and does not have a value." end end
def enforce_type_restriction! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def enforce_type_restriction! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity return unless type return unless has_value? type_req = type return if type_req == "Any" proposed_value = current_value(false) invalid_type = false if type_req == "Regexp" invalid_type = true unless valid_regexp?(proposed_value) elsif type_req == "Numeric" invalid_type = true unless valid_numeric?(proposed_value) elsif type_req == "Boolean" invalid_type = true unless [true, false].include?(proposed_value) elsif proposed_value.is_a?(Module.const_get(type_req)) == false # TODO: why is this case here? invalid_type = true end if invalid_type == true error = Inspec::Input::ValidationError.new error.input_name = @name error.input_value = proposed_value error.input_type = type_req raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to type '#{error.input_type}'." end end
def has_value?
def has_value? !current_value(false).is_a? NO_VALUE_SET end
def initialize(name, options = {})
def initialize(name, options = {}) @name = name @opts = options if @opts.key?(:default) Inspec.deprecate(:attrs_value_replaces_default, "input name: '#{name}'") if @opts.key?(:value) Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default" @opts.delete(:default) end end # Array of Input::Event objects. These compete with one another to determine # the value of the input when value() is called, as well as providing a # debugging record of when and how the value changed. @events = [] events.push make_creation_event(options) update(options) end
def make_creation_event(options)
def make_creation_event(options) loc = options[:location] || Event.probe_stack Input::Event.new( action: :create, provider: options[:provider], file: loc.path, line: loc.lineno ) end
def normalize_pattern_restriction!
def normalize_pattern_restriction! return unless pattern unless valid_regexp?(pattern) error = Inspec::Input::PatternError.new error.input_pattern = pattern raise error, "Pattern '#{error.input_pattern}' is not a valid regex pattern." end @pattern = pattern end
def normalize_type_restriction!
def normalize_type_restriction! return unless type type_req = type.capitalize abbreviations = { "Num" => "Numeric", "Regex" => "Regexp", } type_req = abbreviations[type_req] if abbreviations.key?(type_req) unless VALID_TYPES.include?(type_req) error = Inspec::Input::TypeError.new error.input_type = type_req raise error, "Type '#{error.input_type}' is not a valid input type." end @type = type_req end
def ruby_var_identifier
def ruby_var_identifier identifier || "attr_" + name.downcase.strip.gsub(/\s+/, "-").gsub(/[^\w-]/, "") end
def set_events
def set_events events.select { |e| e.action == :set } end
def to_hash
def to_hash as_hash = { name: name, options: {} } %i{description title identifier type required value sensitive pattern}.each do |field| val = send(field) next if val.nil? as_hash[:options][field] = val end as_hash end
def to_hash
def to_hash as_hash = { name: name, options: {} } %i{description title identifier type required value pattern}.each do |field| val = send(field) next if val.nil? as_hash[:options][field] = val end as_hash end
def to_ruby
def to_ruby res = ["#{ruby_var_identifier} = attribute('#{name}',{"] res.push " title: '#{title}'," unless title.to_s.empty? res.push " value: #{value.inspect}," unless value.to_s.empty? # to_ruby may generate code that is to be used by older versions of inspec. # Anything older than 3.4 will not recognize the value: option, so # send the default: option as well. See #3759 res.push " default: #{value.inspect}," unless value.to_s.empty? res.push " description: '#{description}'," unless description.to_s.empty? res.push "})" res.join("\n") end
def to_s
def to_s "Input #{name} with value " + (sensitive ? "*** (senstive)" : "#{current_value}") end
def update(options)
def update(options) _update_set_metadata(options) normalize_type_restriction! normalize_pattern_restriction! # Values are set by passing events in; but we can also infer an event. if options.key?(:value) || options.key?(:default) if options.key?(:event) if options.key?(:value) || options.key?(:default) Inspec::Log.warn "Do not provide both an Event and a value as an option to attribute('#{name}') - using value from event" end else self.class.infer_event(options) # Sets options[:event] end end events << options[:event] if options.key? :event enforce_type_restriction! enforce_pattern_restriction! end
def valid_numeric?(value)
def valid_numeric?(value) Float(value) true rescue false end
def valid_regexp?(value)
def valid_regexp?(value) # check for invalid regex syntax Regexp.new(value) true rescue false end
def value
def value enforce_required_validation! current_value end
def value=(new_value, priority = DEFAULT_PRIORITY_FOR_VALUE_SET)
def value=(new_value, priority = DEFAULT_PRIORITY_FOR_VALUE_SET) # Inject a new Event with the new value. location = Event.probe_stack events << Event.new( action: :set, provider: :value_setter, priority: priority, value: new_value, file: location.path, line: location.lineno ) enforce_type_restriction! enforce_pattern_restriction! end