Merge pull request #4485 from inspec/zenspider/objects-inputs

Split out Inspec::Input functional code from the code generation code.
This commit is contained in:
Ryan Davis 2019-09-16 17:03:49 -07:00 committed by GitHub
commit a0a7917faa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 484 additions and 471 deletions

View file

@ -14,31 +14,5 @@ module Inspec
class ConfigError::MalformedJson < ConfigError; end
class ConfigError::Invalid < ConfigError; end
class Input
class Error < Inspec::Error; end
class ValidationError < Error
attr_accessor :input_name
attr_accessor :input_value
attr_accessor :input_type
end
class TypeError < Error
attr_accessor :input_type
end
class RequiredError < Error
attr_accessor :input_name
end
end
class InputRegistry
class Error < Inspec::Error; end
class ProfileLookupError < Error
attr_accessor :profile_name
end
class InputLookupError < Error
attr_accessor :profile_name
attr_accessor :input_name
end
end
class UserInteractionRequired < Error; end
end

410
lib/inspec/input.rb Normal file
View file

@ -0,0 +1,410 @@
require "inspec/utils/deprecation"
# For backwards compatibility during the rename (see #3802),
# maintain the Inspec::Attribute namespace for people checking for
# Inspec::Attribute::DEFAULT_ATTRIBUTE
module Inspec
class Attribute
# This only exists to create the Inspec::Attribute::DEFAULT_ATTRIBUTE symbol with a class
class DEFAULT_ATTRIBUTE; end # rubocop: disable Naming/ClassAndModuleCamelCase
end
end
module Inspec
class Input
class Error < Inspec::Error; end
class ValidationError < Error
attr_accessor :input_name
attr_accessor :input_value
attr_accessor :input_type
end
class TypeError < Error
attr_accessor :input_type
end
class RequiredError < Error
attr_accessor :input_name
end
#===========================================================================#
# Class Input::Event
#===========================================================================#
# TODO: break this out to its own file under inspec/input?
# Information about how the input obtained its value.
# Each time it changes, an Input::Event is added to the #events array.
class Event
EVENT_PROPERTIES = [
:action, # :create, :set, :fetch
:provider, # Name of the plugin
:priority, # Priority of this plugin for resolving conflicts. 1-100, higher numbers win.
:value, # New value, if provided.
:file, # File containing the input-changing action, if known
:line, # Line in file containing the input-changing action, if known
:hit, # if action is :fetch, true if the remote source had the input
].freeze
# Value has a special handler
EVENT_PROPERTIES.reject { |p| p == :value }.each do |prop|
attr_accessor prop
end
attr_reader :value
def initialize(properties = {})
@value_has_been_set = false
properties.each do |prop_name, prop_value|
if EVENT_PROPERTIES.include? prop_name
# OK, save the property
send((prop_name.to_s + "=").to_sym, prop_value)
else
raise "Unrecognized property to Input::Event: #{prop_name}"
end
end
end
def value=(the_val)
# Even if set to nil or false, it has indeed been set; note that fact.
@value_has_been_set = true
@value = the_val
end
def value_has_been_set?
@value_has_been_set
end
def diagnostic_string
to_h.reject { |_, val| val.nil? }.to_a.map { |pair| "#{pair[0]}: '#{pair[1]}'" }.join(", ")
end
def to_h
EVENT_PROPERTIES.each_with_object({}) do |prop, hash|
hash[prop] = send(prop)
end
end
def self.probe_stack
frames = caller_locations(2, 40)
frames.reject! { |f| f.path && f.path.include?("/lib/inspec/") }
frames.first
end
end # class Event
#===========================================================================#
# Class NO_VALUE_SET
#===========================================================================#
# This special class is used to represent the value when an input has
# not been assigned a value. This allows a user to explicitly assign nil
# to an input.
class NO_VALUE_SET # rubocop: disable Naming/ClassAndModuleCamelCase
def initialize(name)
@name = name
# output warn message if we are in a exec call
if Inspec::BaseCLI.inspec_cli_command == :exec
Inspec::Log.warn(
"Input '#{@name}' does not have a value. "\
"Use --input-file to provide a value for '#{@name}' or specify a "\
"value with `attribute('#{@name}', value: 'somevalue', ...)`."
)
end
end
def method_missing(*_)
self
end
def respond_to_missing?(_, _)
true
end
def to_s
"Input '#{@name}' does not have a value. Skipping test."
end
def is_a?(klass)
if klass == Inspec::Attribute::DEFAULT_ATTRIBUTE
Inspec.deprecate(:rename_attributes_to_inputs, "Don't check for `is_a?(Inspec::Attribute::DEFAULT_ATTRIBUTE)`, check for `Inspec::Input::NO_VALUE_SET")
true # lie for backward compatibility
else
super(klass)
end
end
def kind_of?(klass)
if klass == Inspec::Attribute::DEFAULT_ATTRIBUTE
Inspec.deprecate(:rename_attributes_to_inputs, "Don't check for `kind_of?(Inspec::Attribute::DEFAULT_ATTRIBUTE)`, check for `Inspec::Input::NO_VALUE_SET")
true # lie for backward compatibility
else
super(klass)
end
end
end # class NO_VALUE_SET
#===========================================================================#
# Class Inspec::Input
#===========================================================================#
# Validation types for input values
VALID_TYPES = %w{
String
Numeric
Regexp
Array
Hash
Boolean
Any
}.freeze
# TODO: this is not used anywhere?
# If you call `input` in a control file, the input will receive this priority.
# You can override that with a :priority option.
DEFAULT_PRIORITY_FOR_DSL_ATTRIBUTES = 20
# If you somehow manage to initialize an Input outside of the DSL,
# AND you don't provide an Input::Event, this is the priority you get.
DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER = 10
# If you directly call value=, this is the priority assigned.
# This is the highest priority within InSpec core; though plugins
# are free to go higher.
DEFAULT_PRIORITY_FOR_VALUE_SET = 60
attr_reader :description, :events, :identifier, :name, :required, :title, :type
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
# TODO: is this here just for testing?
def set_events
events.select { |e| e.action == :set }
end
def diagnostic_string
"Input #{name}, with history:\n" +
events.map(&:diagnostic_string).map { |line| " #{line}" }.join("\n")
end
#--------------------------------------------------------------------------#
# Managing Value
#--------------------------------------------------------------------------#
def update(options)
_update_set_metadata(options)
normalize_type_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!
end
# We can determine a value:
# 1. By event.value (preferred)
# 2. By options[:value]
# 3. By options[:default] (deprecated)
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
private
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)
end
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
# Determine the current winning value, but don't validate it
def current_value
# 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)
else
winning_event.value # May still be nil
end
end
public
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!
end
def value
enforce_required_validation!
current_value
end
def has_value?
!current_value.is_a? NO_VALUE_SET
end
#--------------------------------------------------------------------------#
# Value Type Coercion
#--------------------------------------------------------------------------#
def to_s
"Input #{name} with #{current_value}"
end
#--------------------------------------------------------------------------#
# Validation
#--------------------------------------------------------------------------#
private
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
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
return unless type
return unless has_value?
type_req = type
return if type_req == "Any"
proposed_value = current_value
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 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 valid_numeric?(value)
Float(value)
true
rescue
false
end
def valid_regexp?(value)
# check for invalid regex syntax
Regexp.new(value)
true
rescue
false
end
end
end

View file

@ -1,6 +1,6 @@
require "forwardable"
require "singleton"
require "inspec/objects/input"
require "inspec/input"
require "inspec/secrets"
require "inspec/exceptions"
require "inspec/plugin/v2"
@ -13,6 +13,15 @@ module Inspec
include Singleton
extend Forwardable
class Error < Inspec::Error; end
class ProfileLookupError < Error
attr_accessor :profile_name
end
class InputLookupError < Error
attr_accessor :profile_name
attr_accessor :input_name
end
attr_reader :inputs_by_profile, :profile_aliases, :plugins
def_delegator :inputs_by_profile, :each
def_delegator :inputs_by_profile, :[]

View file

@ -1,5 +1,5 @@
module Inspec
autoload :Input, "inspec/objects/input"
# TODO: these should be namespaced in Objects
autoload :Tag, "inspec/objects/tag"
autoload :Control, "inspec/objects/control"
autoload :Describe, "inspec/objects/describe"
@ -10,3 +10,5 @@ module Inspec
autoload :Test, "inspec/objects/test"
autoload :Value, "inspec/objects/value"
end
require "inspec/objects/input" # already defined so you can't autoload

View file

@ -1,307 +1,14 @@
require "inspec/utils/deprecation"
# For backwards compatibility during the rename (see #3802),
# maintain the Inspec::Attribute namespace for people checking for
# Inspec::Attribute::DEFAULT_ATTRIBUTE
module Inspec
class Attribute
# This only exists to create the Inspec::Attribute::DEFAULT_ATTRIBUTE symbol with a class
class DEFAULT_ATTRIBUTE; end # rubocop: disable Naming/ClassAndModuleCamelCase
end
end
require "inspec/input"
module Inspec
class Input
#===========================================================================#
# Class Input::Event
#===========================================================================#
# Information about how the input obtained its value.
# Each time it changes, an Input::Event is added to the #events array.
class Event
EVENT_PROPERTIES = [
:action, # :create, :set, :fetch
:provider, # Name of the plugin
:priority, # Priority of this plugin for resolving conflicts. 1-100, higher numbers win.
:value, # New value, if provided.
:file, # File containing the input-changing action, if known
:line, # Line in file containing the input-changing action, if known
:hit, # if action is :fetch, true if the remote source had the input
].freeze
# Value has a special handler
EVENT_PROPERTIES.reject { |p| p == :value }.each do |prop|
attr_accessor prop
end
attr_reader :value
def initialize(properties = {})
@value_has_been_set = false
properties.each do |prop_name, prop_value|
if EVENT_PROPERTIES.include? prop_name
# OK, save the property
send((prop_name.to_s + "=").to_sym, prop_value)
else
raise "Unrecognized property to Input::Event: #{prop_name}"
end
end
end
def value=(the_val)
# Even if set to nil or false, it has indeed been set; note that fact.
@value_has_been_set = true
@value = the_val
end
def value_has_been_set?
@value_has_been_set
end
def diagnostic_string
to_h.reject { |_, val| val.nil? }.to_a.map { |pair| "#{pair[0]}: '#{pair[1]}'" }.join(", ")
end
def to_h
EVENT_PROPERTIES.each_with_object({}) do |prop, hash|
hash[prop] = send(prop)
end
end
def self.probe_stack
frames = caller_locations(2, 40)
frames.reject! { |f| f.path && f.path.include?("/lib/inspec/") }
frames.first
end
end
#===========================================================================#
# Class NO_VALUE_SET
#===========================================================================#
# This special class is used to represent the value when an input has
# not been assigned a value. This allows a user to explicitly assign nil
# to an input.
class NO_VALUE_SET # rubocop: disable Naming/ClassAndModuleCamelCase
def initialize(name)
@name = name
# output warn message if we are in a exec call
if Inspec::BaseCLI.inspec_cli_command == :exec
Inspec::Log.warn(
"Input '#{@name}' does not have a value. "\
"Use --input-file to provide a value for '#{@name}' or specify a "\
"value with `attribute('#{@name}', value: 'somevalue', ...)`."
)
end
end
def method_missing(*_)
self
end
def respond_to_missing?(_, _)
true
end
def to_s
"Input '#{@name}' does not have a value. Skipping test."
end
def is_a?(klass)
if klass == Inspec::Attribute::DEFAULT_ATTRIBUTE
Inspec.deprecate(:rename_attributes_to_inputs, "Don't check for `is_a?(Inspec::Attribute::DEFAULT_ATTRIBUTE)`, check for `Inspec::Input::NO_VALUE_SET")
true # lie for backward compatibility
else
super(klass)
end
end
def kind_of?(klass)
if klass == Inspec::Attribute::DEFAULT_ATTRIBUTE
Inspec.deprecate(:rename_attributes_to_inputs, "Don't check for `kind_of?(Inspec::Attribute::DEFAULT_ATTRIBUTE)`, check for `Inspec::Input::NO_VALUE_SET")
true # lie for backward compatibility
else
super(klass)
end
end
end
end
# NOTE: due to namespacing, this reopens and extends the existing
# Inspec::Input. This should be under Inspec::Objects but that ship
# has sailed.
class Input
#===========================================================================#
# Class Inspec::Input
#===========================================================================#
# Validation types for input values
VALID_TYPES = %w{
String
Numeric
Regexp
Array
Hash
Boolean
Any
}.freeze
# If you call `input` in a control file, the input will receive this priority.
# You can override that with a :priority option.
DEFAULT_PRIORITY_FOR_DSL_ATTRIBUTES = 20
# If you somehow manage to initialize an Input outside of the DSL,
# AND you don't provide an Input::Event, this is the priority you get.
DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER = 10
# If you directly call value=, this is the priority assigned.
# This is the highest priority within InSpec core; though plugins
# are free to go higher.
DEFAULT_PRIORITY_FOR_VALUE_SET = 60
attr_reader :description, :events, :identifier, :name, :required, :title, :type
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 set_events
events.select { |e| e.action == :set }
end
def diagnostic_string
"Input #{name}, with history:\n" +
events.map(&:diagnostic_string).map { |line| " #{line}" }.join("\n")
end
#--------------------------------------------------------------------------#
# Managing Value
#--------------------------------------------------------------------------#
def update(options)
_update_set_metadata(options)
normalize_type_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!
end
# We can determine a value:
# 1. By event.value (preferred)
# 2. By options[:value]
# 3. By options[:default] (deprecated)
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
private
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)
end
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
# Determine the current winning value, but don't validate it
def current_value
# 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)
else
winning_event.value # May still be nil
end
end
public
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!
end
def value
enforce_required_validation!
current_value
end
def has_value?
!current_value.is_a? NO_VALUE_SET
end
# NOTE: No initialize method or accessors for the reasons listed above
#--------------------------------------------------------------------------#
# Marshalling
@ -334,94 +41,5 @@ module Inspec
res.push "})"
res.join("\n")
end
#--------------------------------------------------------------------------#
# Value Type Coercion
#--------------------------------------------------------------------------#
def to_s
"Input #{name} with #{current_value}"
end
#--------------------------------------------------------------------------#
# Validation
#--------------------------------------------------------------------------#
private
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
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
return unless type
return unless has_value?
type_req = type
return if type_req == "Any"
proposed_value = current_value
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 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 valid_numeric?(value)
Float(value)
true
rescue
false
end
def valid_regexp?(value)
# check for invalid regex syntax
Regexp.new(value)
true
rescue
false
end
end
end

View file

@ -5,7 +5,7 @@ require "inspec/library_eval_context"
require "inspec/control_eval_context"
require "inspec/require_loader"
require "securerandom"
require "inspec/objects/input"
require "inspec/input_registry"
module Inspec
class ProfileContext

View file

@ -1,4 +1,4 @@
require "inspec/objects/input"
require "inspec/input"
module PkeyReader
def read_pkey(filecontent, passphrase)

View file

@ -502,4 +502,55 @@ end
control.to_hash.must_equal control_hash
end
end
describe "Inspec::Input" do
describe "to_ruby method" do
it "generates the code for the input" do
input = Inspec::Input.new("application_port", description: "The port my application uses", value: 80)
ruby_code = input.to_ruby
ruby_code.must_include "attr_application_port = " # Should assign to a var
# Should have the DSL call. This should be attribute(), not input(), for the
# foreseeable future, to maintain backwards compatibility.
ruby_code.must_include "attribute('application_port'"
ruby_code.must_include "value: 80"
ruby_code.must_include "default: 80"
ruby_code.must_include "description: 'The port my application uses'"
# Try to eval the code to verify that the generated code was valid ruby.
# Note that the input() method is part of the DSL, so we need to
# alter the call into something that can respond - the constructor will do
ruby_code_for_eval = ruby_code.sub(/attribute\(/, "Inspec::Input.new(")
# This will throw exceptions if there is a problem
new_attr = eval(ruby_code_for_eval) # rubocop:disable Security/Eval # Could use ripper!
new_attr.value.must_equal 80
end
end
# TODO - deprecate this, not sure it is used
describe "to_hash method" do
it "generates a similar hash" do
ipt = Inspec::Input.new(
"some_attr",
description: "The port my application uses",
value: 80,
identifier: "app_port",
required: false,
type: "numeric"
)
expected = {
name: "some_attr",
options: {
description: "The port my application uses",
value: 80,
identifier: "app_port",
required: false,
type: "Numeric", # This gets normalized
},
}
ipt.to_hash.must_equal expected
end
end
end
end

View file

@ -1,5 +1,5 @@
require "helper"
require "inspec/objects/input"
require "inspec/input"
describe "Inspec::Input and Events" do
let(:ipt) { Inspec::Input.new("input") }

View file

@ -1,5 +1,5 @@
require "helper"
require "inspec/objects/input"
require "inspec/input"
describe Inspec::Input do
let(:opts) { {} }
@ -25,57 +25,6 @@ describe Inspec::Input do
end
end
#==============================================================#
# Code Generation
#==============================================================#
describe "to_ruby method" do
it "generates the code for the input" do
input = Inspec::Input.new("application_port", description: "The port my application uses", value: 80)
ruby_code = input.to_ruby
ruby_code.must_include "attr_application_port = " # Should assign to a var
# Should have the DSL call. This should be attribute(), not input(), for the
# foreseeable future, to maintain backwards compatibility.
ruby_code.must_include "attribute('application_port'"
ruby_code.must_include "value: 80"
ruby_code.must_include "default: 80"
ruby_code.must_include "description: 'The port my application uses'"
# Try to eval the code to verify that the generated code was valid ruby.
# Note that the input() method is part of the DSL, so we need to
# alter the call into something that can respond - the constructor will do
ruby_code_for_eval = ruby_code.sub(/attribute\(/, "Inspec::Input.new(")
# This will throw exceptions if there is a problem
new_attr = eval(ruby_code_for_eval) # rubocop:disable Security/Eval # Could use ripper!
new_attr.value.must_equal 80
end
end
# TODO - deprecate this, not sure it is used
describe "to_hash method" do
it "generates a similar hash" do
ipt = Inspec::Input.new(
"some_attr",
description: "The port my application uses",
value: 80,
identifier: "app_port",
required: false,
type: "numeric"
)
expected = {
name: "some_attr",
options: {
description: "The port my application uses",
value: 80,
identifier: "app_port",
required: false,
type: "Numeric", # This gets normalized
},
}
ipt.to_hash.must_equal expected
end
end
#==============================================================#
# Setting Value - One Shot
# (see events_test.rb for overwrite support)

View file

@ -1,5 +1,5 @@
require "helper"
require "inspec/objects/input"
require "inspec/input"
describe "type validation" do
let(:opts) { {} }