inspec-core 4.1.4.preview → 4.2.0.preview

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4153e41e5d8fddfb696a73688b7d92b299aed682
4
- data.tar.gz: 9a9c379e82e175edacc705e63b299e091be4df1b
3
+ metadata.gz: 3cbd02d922c9f1c6cbe5d81b74598ac88ca10676
4
+ data.tar.gz: 31b971b6c1c65154a2952d4eb27ab6b1edb765ae
5
5
  SHA512:
6
- metadata.gz: abbbc79378e7f76da10089bea0030fe19c0ad470eba604caaa5ad909ceb8977919f9849346e558f3dceb213d290d2317ec12a282936100cb2152f412f034aac2
7
- data.tar.gz: 806520071a450b2a8c4fe097dbc580d3de4b6beed0b09f95f3ae3e4af2948ec653456ff59bdc815396149391b0a82d496f3abdaf608af1653ccd1ed82544009d
6
+ metadata.gz: 91dc0c86668e00121588268d2828521a73e315efa0480919b09d3ee7e82e2dd4a9d93e88204a47e960c96e31ed425ff32e3131a60f94590053c417c3da0a2344
7
+ data.tar.gz: 45628e2783b3ba28495fa7efe504956c21a9462355a77ef87aa164e9738858b186f187c6c27f18629083b66a5c02cf77835e9fde5cb7c200d8dc6aa8e997152f
@@ -3,12 +3,12 @@
3
3
  "unknown_group_action": "ignore",
4
4
  "groups": {
5
5
  "attrs_value_replaces_default": {
6
- "action": "ignore",
6
+ "action": "warn",
7
7
  "prefix": "The 'default' option for attributes is being replaced by 'value' - please use it instead."
8
8
  },
9
9
  "aws_resources_in_resource_pack": {
10
10
  "comment": "See #3822",
11
- "action": "ignore",
11
+ "action": "warn",
12
12
  "prefix": "AWS resources shipped with core InSpec are being to moved to a resource pack for faster iteration. Please update your profiles to depend on git@github.com:inspec/inspec-aws.git ."
13
13
  },
14
14
  "cli_option_json_config": {
@@ -17,11 +17,11 @@
17
17
  "comment": "See #3661"
18
18
  },
19
19
  "file_resource_be_mounted_matchers": {
20
- "action": "warn",
20
+ "action": "fail_control",
21
21
  "suffix": "This will not be supported in InSpec 4.0."
22
22
  },
23
23
  "host_resource_proto_usage": {
24
- "action": "warn",
24
+ "action": "fail_control",
25
25
  "suffix": "This will not be supported in InSpec 4.0."
26
26
  },
27
27
  "inspec_ui_methods": {
@@ -30,67 +30,67 @@
30
30
  "comment": "See #3715"
31
31
  },
32
32
  "mssql_session_pass_option": {
33
- "action": "warn",
33
+ "action": "exit",
34
34
  "suffix": "This will not be supported in InSpec 4.0."
35
35
  },
36
36
  "oracledb_session_pass_option": {
37
- "action": "warn",
38
- "suffix": "This will not be supported in InSpec 4.0."
37
+ "action": "exit",
38
+ "suffix": "This is not supported in InSpec 4.0."
39
39
  },
40
40
  "property_filesystem_size": {
41
- "action": "ignore",
41
+ "action": "warn",
42
42
  "comment": "See #3778"
43
43
  },
44
44
  "property_processes_list": {
45
- "action": "warn",
46
- "suffix": "This property will be removed in InSpec 4.0."
45
+ "action": "fail_control",
46
+ "suffix": "This property was removed in InSpec 4.0."
47
47
  },
48
48
  "properties_aws_iam_user": {
49
- "action": "warn",
50
- "suffix": "This property will be removed in InSpec 4.0."
49
+ "action": "fail_control",
50
+ "suffix": "This property was removed in InSpec 4.0."
51
51
  },
52
52
  "properties_shadow": {
53
- "action": "warn",
54
- "suffix": "This property will be removed in InSpec 4.0."
53
+ "action": "fail_control",
54
+ "suffix": "This property was removed in InSpec 4.0."
55
55
  },
56
56
  "rename_attributes_to_inputs": {
57
- "action": "ignore",
57
+ "action": "warn",
58
58
  "prefix": "InSpec Attributes are being renamed to InSpec Inputs to avoid confusion with Chef Attributes.",
59
59
  "comment": "See #3802"
60
60
  },
61
61
  "resource_apache": {
62
- "action": "warn",
63
- "suffix": "This resource will be removed in InSpec 4.0."
62
+ "action": "exit",
63
+ "suffix": "This resource was removed in InSpec 4.0."
64
64
  },
65
65
  "resource_azure_generic_resource": {
66
66
  "action": "warn",
67
67
  "prefix": "The azure_generic_resource is deprecated. Please use a specific resource. See: 'https://github.com/inspec/inspec/issues/3131'"
68
68
  },
69
69
  "resource_iis_website": {
70
- "action": "warn",
71
- "suffix": "This resource will be removed in InSpec 4.0.",
70
+ "action": "exit",
71
+ "suffix": "This resource was removed in InSpec 4.0.",
72
72
  "comment": "Needed for ServerSpec compatibility"
73
73
  },
74
74
  "resource_linux_kernel_parameter": {
75
- "action": "warn",
76
- "suffix": "This resource will be removed in InSpec 4.0.",
75
+ "action": "exit",
76
+ "suffix": "This resource was removed in InSpec 4.0.",
77
77
  "comment": "Needed for ServerSpec compatibility"
78
78
  },
79
79
  "resource_ppa": {
80
- "action": "warn",
81
- "suffix": "This resource will be removed in InSpec 4.0.",
80
+ "action": "exit",
81
+ "suffix": "This resource was removed in InSpec 4.0.",
82
82
  "comment": "Needed for ServerSpec compatibility"
83
83
  },
84
84
  "resource_script": {
85
- "action": "warn",
85
+ "action": "exit",
86
86
  "suffix": "This resource will be removed in InSpec 4.0"
87
87
  },
88
88
  "resource_user_serverspec_compat": {
89
- "action": "warn"
89
+ "action": "fail_control"
90
90
  },
91
91
  "resource_windows_registry_key": {
92
- "action": "warn",
93
- "suffix": "This resource will be removed in InSpec 4.0.",
92
+ "action": "exit",
93
+ "suffix": "This resource was removed in InSpec 4.0.",
94
94
  "comment": "Needed for ServerSpec compatibility"
95
95
  },
96
96
  "serverspec_compatibility": {
@@ -101,11 +101,11 @@
101
101
  "action": "warn"
102
102
  },
103
103
  "mount_parser_serverspec_compat": {
104
- "action": "warn"
104
+ "action": "fail_control"
105
105
  },
106
106
  "wmi_non_hash_usage": {
107
- "action": "warn",
108
- "suffix": "This property will be removed in InSpec 4.0."
107
+ "action": "fail_control",
108
+ "suffix": "This property was removed in InSpec 4.0."
109
109
  }
110
110
  }
111
111
  }
data/lib/inspec/cli.rb CHANGED
@@ -392,7 +392,11 @@ require 'license_acceptance/acceptor'
392
392
  begin
393
393
  if (commands_exempt_from_license_check & ARGV.map(&:downcase)).empty? && # Did they use a non-exempt command?
394
394
  !ARGV.empty? # Did they supply at least one command?
395
- LicenseAcceptance::Acceptor.check_and_persist('inspec', Inspec::VERSION)
395
+ LicenseAcceptance::Acceptor.check_and_persist(
396
+ 'inspec',
397
+ Inspec::VERSION,
398
+ logger: Inspec::Log,
399
+ )
396
400
  end
397
401
  rescue LicenseAcceptance::LicenseNotAcceptedError
398
402
  Inspec::Log.error 'InSpec cannot execute without accepting the license'
@@ -26,8 +26,23 @@ module Inspec
26
26
  with_resource_dsl resources_dsl
27
27
 
28
28
  # allow attributes to be accessed within control blocks
29
- define_method :attribute do |name|
30
- Inspec::InputRegistry.find_input(name, profile_id).value
29
+ # TODO: deprecate name, use input()
30
+ define_method :attribute do |input_name, options = {}|
31
+ if options.empty?
32
+ # Simply an access, no event here
33
+ Inspec::InputRegistry.find_or_register_input(input_name, profile_id).value
34
+ else
35
+ options[:priority] = 20
36
+ options[:provider] = :inline_control_code
37
+ evt = Inspec::Input.infer_event(options)
38
+ Inspec::InputRegistry.find_or_register_input(input_name, profile_name, event: evt).value
39
+ end
40
+ end
41
+
42
+ # Find the Input object, but don't collapse to a value.
43
+ # Will return nil on a miss.
44
+ define_method :input_object do |input_name|
45
+ Inspec::InputRegistry.find_or_register_input(input_name, profile_id)
31
46
  end
32
47
 
33
48
  # Support for Control DSL plugins.
@@ -168,14 +183,25 @@ module Inspec
168
183
  end
169
184
 
170
185
  # method for inputs; import input handling
171
- define_method :attribute do |name, options = nil|
172
- if options.nil?
173
- Inspec::InputRegistry.find_input(name, profile_id).value
186
+ # TODO: deprecate name, use input()
187
+ define_method :attribute do |input_name, options = {}|
188
+ if options.empty?
189
+ # Simply an access, no event here
190
+ Inspec::InputRegistry.find_or_register_input(input_name, profile_id).value
174
191
  else
175
- profile_context_owner.register_input(name, options)
192
+ options[:priority] = 20
193
+ options[:provider] = :inline_control_code
194
+ evt = Inspec::Input.infer_event(options)
195
+ Inspec::InputRegistry.find_or_register_input(input_name, profile_name, event: evt).value
176
196
  end
177
197
  end
178
198
 
199
+ # Find the Input object, but don't collapse to a value.
200
+ # Will return nil on a miss.
201
+ define_method :input_object do |input_name|
202
+ Inspec::InputRegistry.find_or_register_input(input_name, profile_id)
203
+ end
204
+
179
205
  define_method :skip_control do |id|
180
206
  profile_context_owner.unregister_rule(id)
181
207
  end
@@ -118,6 +118,7 @@ module Inspec
118
118
  return @profile unless @profile.nil?
119
119
  opts = @opts.dup
120
120
  opts[:backend] = @backend
121
+ opts[:runner_conf] = Inspec::Config.cached
121
122
  if !@dependencies.nil? && !@dependencies.empty?
122
123
  opts[:dependencies] = Inspec::DependencySet.from_array(@dependencies, @cwd, @cache, @backend)
123
124
  end
@@ -23,6 +23,7 @@ module Inspec
23
23
  # implementation of the fetcher being used.
24
24
  #
25
25
  class Resolver
26
+ # Here deps is an Array of Hashes
26
27
  def self.resolve(dependencies, cache, working_dir, backend)
27
28
  reqs = dependencies.map do |dep|
28
29
  req = Inspec::Requirement.from_metadata(dep, cache, cwd: working_dir, backend: backend)
@@ -47,6 +48,7 @@ module Inspec
47
48
  end
48
49
  end
49
50
 
51
+ # Here deps is an Array of Inspec::Requirement
50
52
  def resolve(deps, top_level = true, seen_items = {}, path_string = '') # rubocop:disable Metrics/AbcSize
51
53
  graph = {}
52
54
  if top_level
data/lib/inspec/dsl.rb CHANGED
@@ -79,7 +79,7 @@ module Inspec::DSL
79
79
 
80
80
  def self.filter_included_controls(context, profile, &block)
81
81
  mock = Inspec::Backend.create(Inspec::Config.mock)
82
- include_ctx = Inspec::ProfileContext.for_profile(profile, mock, {})
82
+ include_ctx = Inspec::ProfileContext.for_profile(profile, mock)
83
83
  include_ctx.load(block) if block_given?
84
84
  # remove all rules that were not registered
85
85
  context.all_rules.each do |r|
data/lib/inspec/impact.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  module Inspec::Impact
5
5
  IMPACT_SCORES = {
6
6
  'none' => 0.0,
7
- 'low' => 0.01,
7
+ 'low' => 0.1,
8
8
  'medium' => 0.4,
9
9
  'high' => 0.7,
10
10
  'critical' => 0.9,
@@ -1,83 +1,224 @@
1
1
  require 'forwardable'
2
2
  require 'singleton'
3
3
  require 'inspec/objects/input'
4
+ require 'inspec/secrets'
5
+ require 'inspec/exceptions'
4
6
 
5
7
  module Inspec
8
+ # The InputRegistry's responsibilities include:
9
+ # - maintaining a list of Input objects that are bound to profiles
10
+ # - assisting in the lookup and creation of Inputs
6
11
  class InputRegistry
7
12
  include Singleton
8
13
  extend Forwardable
9
14
 
10
- attr_reader :list
11
- def_delegator :list, :each
12
- def_delegator :list, :[]
13
- def_delegator :list, :key?, :profile_exist?
14
- def_delegator :list, :select
15
+ attr_reader :inputs_by_profile, :profile_aliases
16
+ def_delegator :inputs_by_profile, :each
17
+ def_delegator :inputs_by_profile, :[]
18
+ def_delegator :inputs_by_profile, :key?, :profile_known?
19
+ def_delegator :inputs_by_profile, :select
20
+ def_delegator :profile_aliases, :key?, :profile_alias?
15
21
 
16
- # These self methods are convenience methods so you dont always
17
- # have to specify instance when calling the registry
18
- def self.find_input(name, profile)
19
- instance.find_input(name, profile)
20
- end
22
+ def initialize
23
+ # Keyed on String profile_name => Hash of String input_name => Input object
24
+ @inputs_by_profile = {}
21
25
 
22
- def self.register_input(name, profile, options = {})
23
- instance.register_input(name, profile, options)
26
+ # this is a list of optional profile name overrides set in the inspec.yml
27
+ @profile_aliases = {}
24
28
  end
25
29
 
26
- def self.register_profile_alias(name, alias_name)
27
- instance.register_profile_alias(name, alias_name)
30
+ #-------------------------------------------------------------#
31
+ # Support for Profiles
32
+ #-------------------------------------------------------------#
33
+
34
+ def register_profile_alias(name, alias_name)
35
+ @profile_aliases[name] = alias_name
28
36
  end
29
37
 
30
- def self.list_inputs_for_profile(profile)
31
- instance.list_inputs_for_profile(profile)
38
+ def list_inputs_for_profile(profile)
39
+ inputs_by_profile[profile] = {} unless profile_known?(profile)
40
+ inputs_by_profile[profile]
32
41
  end
33
42
 
34
- def initialize
35
- # this is a collection of profiles which have a value of input objects
36
- @list = {}
43
+ #-------------------------------------------------------------#
44
+ # Support for Individual Inputs
45
+ #-------------------------------------------------------------#
37
46
 
38
- # this is a list of optional profile name overrides set in the inspec.yml
39
- @profile_aliases = {}
47
+ def find_or_register_input(input_name, profile_name, options = {})
48
+ if profile_alias?(profile_name)
49
+ alias_name = profile_name
50
+ profile_name = profile_aliases[profile_name]
51
+ handle_late_arriving_alias(alias_name, profile_name) if profile_known?(alias_name)
52
+ end
53
+
54
+ inputs_by_profile[profile_name] ||= {}
55
+ if inputs_by_profile[profile_name].key?(input_name)
56
+ inputs_by_profile[profile_name][input_name].update(options)
57
+ else
58
+ inputs_by_profile[profile_name][input_name] = Inspec::Input.new(input_name, options)
59
+ end
60
+
61
+ inputs_by_profile[profile_name][input_name]
40
62
  end
41
63
 
42
- def find_input(name, profile)
43
- profile = @profile_aliases[profile] if !profile_exist?(profile) && @profile_aliases[profile]
44
- unless profile_exist?(profile)
45
- error = Inspec::InputRegistry::ProfileLookupError.new
46
- error.profile_name = profile
47
- raise error, "Profile '#{error.profile_name}' does not have any inputs"
64
+ # It is possible for a wrapper profile to create an input in metadata,
65
+ # referring to the child profile by an alias that has not yet been registered.
66
+ # The registry will then store the inputs under the alias, as if the alias
67
+ # were a true profile.
68
+ # If that happens and the child profile also mentions the input, we will
69
+ # need to move some things - all inputs should be stored under the true
70
+ # profile name, and no inputs should be stored under the alias.
71
+ def handle_late_arriving_alias(alias_name, profile_name)
72
+ inputs_by_profile[profile_name] ||= {}
73
+ inputs_by_profile[alias_name].each do |input_name, input_from_alias|
74
+ # Move the inpuut, or if it exists, merge events
75
+ existing = inputs_by_profile[profile_name][input_name]
76
+ if existing
77
+ existing.events.concat(input_from_alias.events)
78
+ else
79
+ inputs_by_profile[profile_name][input_name] = input_from_alias
80
+ end
48
81
  end
82
+ # Finally, delete the (now copied-out) entry for the alias
83
+ inputs_by_profile.delete(alias_name)
84
+ end
85
+ #-------------------------------------------------------------#
86
+ # Support for Binding Inputs
87
+ #-------------------------------------------------------------#
88
+
89
+ # This method is called by the Profile as soon as it has
90
+ # enough context to allow binding inputs to it.
91
+ def bind_profile_inputs(profile_name, sources = {})
92
+ inputs_by_profile[profile_name] ||= {}
93
+
94
+ # In a more perfect world, we could let the core plugins choose
95
+ # self-determine what to do; but as-is, the APIs that call this
96
+ # are a bit over-constrained.
97
+ bind_inputs_from_metadata(profile_name, sources[:profile_metadata])
98
+ bind_inputs_from_input_files(profile_name, sources[:cli_input_files])
99
+ bind_inputs_from_runner_api(profile_name, sources[:runner_api])
100
+ end
101
+
102
+ private
49
103
 
50
- unless list[profile].key?(name)
51
- error = Inspec::InputRegistry::InputLookupError.new
52
- error.input_name = name
53
- error.profile_name = profile
54
- raise error, "Profile '#{error.profile_name}' does not have an input with name '#{error.input_name}'"
104
+ def bind_inputs_from_runner_api(profile_name, input_hash)
105
+ # TODO: move this into a core plugin
106
+
107
+ return if input_hash.nil?
108
+ return if input_hash.empty?
109
+
110
+ # These arrive as a bare hash - values are raw values, not options
111
+ input_hash.each do |input_name, input_value|
112
+ loc = Inspec::Input::Event.probe_stack # TODO: likely modify this to look for a kitchen.yml, if that is realistic
113
+ evt = Inspec::Input::Event.new(
114
+ value: input_value,
115
+ provider: :runner_api, # TODO: suss out if audit cookbook or kitchen-inspec or something unknown
116
+ priority: 40,
117
+ file: loc.path,
118
+ line: loc.lineno,
119
+ )
120
+ find_or_register_input(input_name, profile_name, event: evt)
55
121
  end
56
- list[profile][name]
57
122
  end
58
123
 
59
- def register_input(name, profile, options = {})
60
- # check for a profile override name
61
- if profile_exist?(profile) && list[profile][name] && options.empty?
62
- list[profile][name]
63
- else
64
- list[profile] = {} unless profile_exist?(profile)
65
- list[profile][name] = Inspec::Input.new(name, options)
124
+ def bind_inputs_from_input_files(profile_name, file_list)
125
+ # TODO: move this into a core plugin
126
+
127
+ return if file_list.nil?
128
+ return if file_list.empty?
129
+
130
+ file_list.each do |path|
131
+ validate_inputs_file_readability!(path)
132
+
133
+ # TODO: drop this SecretsBackend stuff, will be handled by plugin system
134
+ data = Inspec::SecretsBackend.resolve(path)
135
+ if data.nil?
136
+ raise Inspec::Exceptions::SecretsBackendNotFound,
137
+ "Cannot find parser for inputs file '#{path}'. " \
138
+ 'Check to make sure file has the appropriate extension.'
139
+ end
140
+
141
+ next if data.inputs.nil?
142
+ data.inputs.each do |input_name, input_value|
143
+ evt = Inspec::Input::Event.new(
144
+ value: input_value,
145
+ provider: :cli_files,
146
+ priority: 40,
147
+ file: path,
148
+ # TODO: any way we could get a line number?
149
+ )
150
+ find_or_register_input(input_name, profile_name, event: evt)
151
+ end
66
152
  end
67
153
  end
68
154
 
69
- def register_profile_alias(name, alias_name)
70
- @profile_aliases[name] = alias_name
155
+ def validate_inputs_file_readability!(path)
156
+ unless File.exist?(path)
157
+ raise Inspec::Exceptions::InputsFileDoesNotExist,
158
+ "Cannot find input file '#{path}'. " \
159
+ 'Check to make sure file exists.'
160
+ end
161
+
162
+ unless File.readable?(path)
163
+ raise Inspec::Exceptions::InputsFileNotReadable,
164
+ "Cannot read input file '#{path}'. " \
165
+ 'Check to make sure file is readable.'
166
+ end
167
+
168
+ true
71
169
  end
72
170
 
73
- def list_inputs_for_profile(profile)
74
- list[profile] = {} unless profile_exist?(profile)
75
- list[profile]
171
+ def bind_inputs_from_metadata(profile_name, profile_metadata_obj)
172
+ # TODO: move this into a core plugin
173
+ # TODO: add deprecation stuff
174
+ return if profile_metadata_obj.nil? # Metadata files are technically optional
175
+
176
+ if profile_metadata_obj.params.key?(:attributes) && profile_metadata_obj.params[:attributes].is_a?(Array)
177
+ profile_metadata_obj.params[:attributes].each do |input_orig|
178
+ input_options = input_orig.dup
179
+ input_name = input_options.delete(:name)
180
+ input_options.merge!({ priority: 30, provider: :profile_metadata, file: File.join(profile_name, 'inspec.yml') })
181
+ evt = Inspec::Input.infer_event(input_options)
182
+
183
+ # Profile metadata may set inputs in other profiles by naming them.
184
+ if input_options[:profile]
185
+ profile_name = input_options[:profile] || profile_name
186
+ # Override priority to force this to win. Allow user to set their own priority.
187
+ evt.priority = input_orig[:priority] || 35
188
+ end
189
+ find_or_register_input(input_name,
190
+ profile_name,
191
+ type: input_options[:type],
192
+ required: input_options[:required],
193
+ event: evt)
194
+ end
195
+ elsif profile_metadata_obj.params.key?(:attributes)
196
+ Inspec::Log.warn 'Inputs must be defined as an Array. Skipping current definition.'
197
+ end
76
198
  end
77
199
 
200
+ #-------------------------------------------------------------#
201
+ # Other Support
202
+ #-------------------------------------------------------------#
203
+ public
204
+
205
+ # Used in testing
78
206
  def __reset
79
- @list = {}
207
+ @inputs_by_profile = {}
80
208
  @profile_aliases = {}
81
209
  end
210
+
211
+ # These class methods are convenience methods so you don't always
212
+ # have to call #instance when calling the registry
213
+ [
214
+ :find_or_register_input,
215
+ :register_profile_alias,
216
+ :list_inputs_for_profile,
217
+ :bind_profile_inputs,
218
+ ].each do |meth|
219
+ define_singleton_method(meth) do |*args|
220
+ instance.send(meth, *args)
221
+ end
222
+ end
82
223
  end
83
224
  end
@@ -14,6 +14,73 @@ end
14
14
 
15
15
  module Inspec
16
16
  class Input
17
+ #===========================================================================#
18
+ # Class Input::Event
19
+ #===========================================================================#
20
+
21
+ # Information about how the input obtained its value.
22
+ # Each time it changes, an Input::Event is added to the #events array.
23
+ class Event
24
+ EVENT_PROPERTIES = [
25
+ :action, # :create, :set, :fetch
26
+ :provider, # Name of the plugin
27
+ :priority, # Priority of this plugin for resolving conflicts. 1-100, higher numbers win.
28
+ :value, # New value, if provided.
29
+ :file, # File containing the input-changing action, if known
30
+ :line, # Line in file containing the input-changing action, if known
31
+ :hit, # if action is :fetch, true if the remote source had the input
32
+ ].freeze
33
+
34
+ # Value has a special handler
35
+ EVENT_PROPERTIES.reject { |p| p == :value }.each do |prop|
36
+ attr_accessor prop
37
+ end
38
+
39
+ attr_reader :value
40
+
41
+ def initialize(properties = {})
42
+ @value_has_been_set = false
43
+
44
+ properties.each do |prop_name, prop_value|
45
+ if EVENT_PROPERTIES.include? prop_name
46
+ # OK, save the property
47
+ send((prop_name.to_s + '=').to_sym, prop_value)
48
+ else
49
+ raise "Unrecognized property to Input::Event: #{prop_name}"
50
+ end
51
+ end
52
+ end
53
+
54
+ def value=(the_val)
55
+ # Even if set to nil or false, it has indeed been set; note that fact.
56
+ @value_has_been_set = true
57
+ @value = the_val
58
+ end
59
+
60
+ def value_has_been_set?
61
+ @value_has_been_set
62
+ end
63
+
64
+ def diagnostic_string
65
+ to_h.reject { |_, val| val.nil? }.to_a.map { |pair| "#{pair[0]}: '#{pair[1]}'" }.join(', ')
66
+ end
67
+
68
+ def to_h
69
+ EVENT_PROPERTIES.each_with_object({}) do |prop, hash|
70
+ hash[prop] = send(prop)
71
+ end
72
+ end
73
+
74
+ def self.probe_stack
75
+ frames = caller_locations(2, 40)
76
+ frames.reject! { |f| f.path && f.path.include?('/lib/inspec/') }
77
+ frames.first
78
+ end
79
+ end
80
+
81
+ #===========================================================================#
82
+ # Class NO_VALUE_SET
83
+ #===========================================================================#
17
84
  # This special class is used to represent the value when an input has
18
85
  # not been assigned a value. This allows a user to explicitly assign nil
19
86
  # to an input.
@@ -62,8 +129,11 @@ module Inspec
62
129
  end
63
130
 
64
131
  class Input
65
- attr_accessor :name
132
+ #===========================================================================#
133
+ # Class Inspec::Input
134
+ #===========================================================================#
66
135
 
136
+ # Validation types for input values
67
137
  VALID_TYPES = %w{
68
138
  String
69
139
  Numeric
@@ -74,6 +144,21 @@ module Inspec
74
144
  Any
75
145
  }.freeze
76
146
 
147
+ # If you call `input` in a control file, the input will receive this priority.
148
+ # You can override that with a :priority option.
149
+ DEFAULT_PRIORITY_FOR_DSL_ATTRIBUTES = 20
150
+
151
+ # If you somehow manage to initialize an Input outside of the DSL,
152
+ # AND you don't provide an Input::Event, this is the priority you get.
153
+ DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER = 10
154
+
155
+ # If you directly call value=, this is the priority assigned.
156
+ # This is the highest priority within InSpec core; though plugins
157
+ # are free to go higher.
158
+ DEFAULT_PRIORITY_FOR_VALUE_SET = 60
159
+
160
+ attr_reader :description, :events, :identifier, :name, :required, :title, :type
161
+
77
162
  def initialize(name, options = {})
78
163
  @name = name
79
164
  @opts = options
@@ -82,49 +167,164 @@ module Inspec
82
167
  if @opts.key?(:value)
83
168
  Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default"
84
169
  @opts.delete(:default)
170
+ end
171
+ end
172
+
173
+ # Array of Input::Event objects. These compete with one another to determine
174
+ # the value of the input when value() is called, as well as providing a
175
+ # debugging record of when and how the value changed.
176
+ @events = []
177
+ events.push make_creation_event(options)
178
+
179
+ update(options)
180
+ end
181
+
182
+ def set_events
183
+ events.select { |e| e.action == :set }
184
+ end
185
+
186
+ def diagnostic_string
187
+ "Input #{name}, with history:\n" +
188
+ events.map(&:diagnostic_string).map { |line| " #{line}" }.join("\n")
189
+ end
190
+
191
+ #--------------------------------------------------------------------------#
192
+ # Managing Value
193
+ #--------------------------------------------------------------------------#
194
+
195
+ def update(options)
196
+ _update_set_metadata(options)
197
+ normalize_type_restriction!
198
+
199
+ # Values are set by passing events in; but we can also infer an event.
200
+ if options.key?(:value) || options.key?(:default)
201
+ if options.key?(:event)
202
+ if options.key?(:value) || options.key?(:default)
203
+ Inspec::Log.warn "Do not provide both an Event and a value as an option to attribute('#{name}') - using value from event"
204
+ end
85
205
  else
86
- @opts[:value] = @opts.delete(:default)
206
+ self.class.infer_event(options) # Sets options[:event]
87
207
  end
88
208
  end
89
- @value = @opts[:value]
90
- validate_value_type(@value) if @opts.key?(:type) && @opts.key?(:value)
209
+ events << options[:event] if options.key? :event
210
+
211
+ enforce_type_restriction!
91
212
  end
92
213
 
93
- def value=(new_value)
94
- validate_value_type(new_value) if @opts.key?(:type)
95
- @value = new_value
214
+ # We can determine a value:
215
+ # 1. By event.value (preferred)
216
+ # 2. By options[:value]
217
+ # 3. By options[:default] (deprecated)
218
+ def self.infer_event(options)
219
+ # Don't rely on this working; you really should be passing a proper Input::Event
220
+ # with the context information you have.
221
+ location = Input::Event.probe_stack
222
+ event = Input::Event.new(
223
+ action: :set,
224
+ provider: options[:provider] || :unknown,
225
+ priority: options[:priority] || Inspec::Input::DEFAULT_PRIORITY_FOR_UNKNOWN_CALLER,
226
+ file: location.path,
227
+ line: location.lineno,
228
+ )
229
+
230
+ if options.key?(:default)
231
+ Inspec.deprecate(:attrs_value_replaces_default, "attribute name: '#{name}'")
232
+ if options.key?(:value)
233
+ Inspec::Log.warn "Input #{@name} created using both :default and :value options - ignoring :default"
234
+ options.delete(:default)
235
+ else
236
+ options[:value] = options.delete(:default)
237
+ end
238
+ end
239
+ event.value = options[:value] if options.key?(:value)
240
+ options[:event] = event
96
241
  end
97
242
 
98
- def value
99
- if @value.nil?
100
- validate_required(@value) if @opts[:required] == true
101
- @value = value_or_dummy
243
+ private
244
+
245
+ def _update_set_metadata(options)
246
+ # Basic metadata
247
+ @title = options[:title] if options.key?(:title)
248
+ @description = options[:description] if options.key?(:description)
249
+ @required = options[:required] if options.key?(:required)
250
+ @identifier = options[:identifier] if options.key?(:identifier) # TODO: determine if this is ever used
251
+ @type = options[:type] if options.key?(:type)
252
+ end
253
+
254
+ def make_creation_event(options)
255
+ loc = options[:location] || Event.probe_stack
256
+ Input::Event.new(
257
+ action: :create,
258
+ provider: options[:provider],
259
+ file: loc.path,
260
+ line: loc.lineno,
261
+ )
262
+ end
263
+
264
+ # Determine the current winning value, but don't validate it
265
+ def current_value
266
+ # Examine the events to determine highest-priority value. Tie-break
267
+ # by using the last one set.
268
+ events_that_set_a_value = events.select(&:value_has_been_set?)
269
+ winning_priority = events_that_set_a_value.map(&:priority).max
270
+ winning_events = events_that_set_a_value.select { |e| e.priority == winning_priority }
271
+ winning_event = winning_events.last # Last for tie-break
272
+
273
+ if winning_event.nil?
274
+ # No value has been set - return special no value object
275
+ NO_VALUE_SET.new(name)
102
276
  else
103
- @value
277
+ winning_event.value # May still be nil
104
278
  end
105
279
  end
106
280
 
107
- def title
108
- @opts[:title]
281
+ public
282
+
283
+ def value=(new_value, priority = DEFAULT_PRIORITY_FOR_VALUE_SET)
284
+ # Inject a new Event with the new value.
285
+ location = Event.probe_stack
286
+ events << Event.new(
287
+ action: :set,
288
+ provider: :value_setter,
289
+ priority: priority,
290
+ value: new_value,
291
+ file: location.path,
292
+ line: location.lineno,
293
+ )
294
+ enforce_type_restriction!
295
+
296
+ new_value
109
297
  end
110
298
 
111
- def description
112
- @opts[:description]
299
+ def value
300
+ enforce_required_validation!
301
+ current_value
113
302
  end
114
303
 
115
- def ruby_var_identifier
116
- @opts[:identifier] || 'attr_' + @name.downcase.strip.gsub(/\s+/, '-').gsub(/[^\w-]/, '')
304
+ def has_value?
305
+ !current_value.is_a? NO_VALUE_SET
117
306
  end
118
307
 
308
+ #--------------------------------------------------------------------------#
309
+ # Marshalling
310
+ #--------------------------------------------------------------------------#
311
+
119
312
  def to_hash
120
- {
121
- name: @name,
122
- options: @opts,
123
- }
313
+ as_hash = { name: name, options: {} }
314
+ [:description, :title, :identifier, :type, :required, :value].each do |field|
315
+ val = send(field)
316
+ next if val.nil?
317
+ as_hash[:options][field] = val
318
+ end
319
+ as_hash
320
+ end
321
+
322
+ def ruby_var_identifier
323
+ identifier || 'attr_' + name.downcase.strip.gsub(/\s+/, '-').gsub(/[^\w-]/, '')
124
324
  end
125
325
 
126
326
  def to_ruby
127
- res = ["#{ruby_var_identifier} = attribute('#{@name}',{"]
327
+ res = ["#{ruby_var_identifier} = attribute('#{name}',{"]
128
328
  res.push " title: '#{title}'," unless title.to_s.empty?
129
329
  res.push " value: #{value.inspect}," unless value.to_s.empty?
130
330
  # to_ruby may generate code that is to be used by older versions of inspec.
@@ -136,37 +336,78 @@ module Inspec
136
336
  res.join("\n")
137
337
  end
138
338
 
339
+ #--------------------------------------------------------------------------#
340
+ # Value Type Coercion
341
+ #--------------------------------------------------------------------------#
342
+
139
343
  def to_s
140
- "Input #{@name} with #{@value}"
344
+ "Input #{name} with #{current_value}"
141
345
  end
142
346
 
347
+ #--------------------------------------------------------------------------#
348
+ # Validation
349
+ #--------------------------------------------------------------------------#
350
+
143
351
  private
144
352
 
145
- def validate_required(value)
353
+ def enforce_required_validation!
354
+ return unless required
146
355
  # skip if we are not doing an exec call (archive/vendor/check)
147
356
  return unless Inspec::BaseCLI.inspec_cli_command == :exec
148
357
 
149
- # value will be set already if a secrets file was passed in
150
- if (!@opts.key?(:default) && value.nil?) || (@opts[:default].nil? && value.nil?)
358
+ proposed_value = current_value
359
+ if proposed_value.nil? || proposed_value.is_a?(NO_VALUE_SET)
151
360
  error = Inspec::Input::RequiredError.new
152
- error.input_name = @name
361
+ error.input_name = name
153
362
  raise error, "Input '#{error.input_name}' is required and does not have a value."
154
363
  end
155
364
  end
156
365
 
157
- def validate_type(type)
158
- type = type.capitalize
366
+ def enforce_type_restriction! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
367
+ return unless type
368
+ return unless has_value?
369
+
370
+ type_req = type
371
+ return if type_req == 'Any'
372
+
373
+ proposed_value = current_value
374
+
375
+ invalid_type = false
376
+ if type_req == 'Regexp'
377
+ invalid_type = true if !valid_regexp?(proposed_value)
378
+ elsif type_req == 'Numeric'
379
+ invalid_type = true if !valid_numeric?(proposed_value)
380
+ elsif type_req == 'Boolean'
381
+ invalid_type = true if ![true, false].include?(proposed_value)
382
+ elsif proposed_value.is_a?(Module.const_get(type_req)) == false
383
+ # TODO: why is this case here?
384
+ invalid_type = true
385
+ end
386
+
387
+ if invalid_type == true
388
+ error = Inspec::Input::ValidationError.new
389
+ error.input_name = @name
390
+ error.input_value = proposed_value
391
+ error.input_type = type_req
392
+ raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to type '#{error.input_type}'."
393
+ end
394
+ end
395
+
396
+ def normalize_type_restriction!
397
+ return unless type
398
+
399
+ type_req = type.capitalize
159
400
  abbreviations = {
160
401
  'Num' => 'Numeric',
161
402
  'Regex' => 'Regexp',
162
403
  }
163
- type = abbreviations[type] if abbreviations.key?(type)
164
- if !VALID_TYPES.include?(type)
404
+ type_req = abbreviations[type_req] if abbreviations.key?(type_req)
405
+ if !VALID_TYPES.include?(type_req)
165
406
  error = Inspec::Input::TypeError.new
166
- error.input_type = type
407
+ error.input_type = type_req
167
408
  raise error, "Type '#{error.input_type}' is not a valid input type."
168
409
  end
169
- type
410
+ @type = type_req
170
411
  end
171
412
 
172
413
  def valid_numeric?(value)
@@ -177,41 +418,11 @@ module Inspec
177
418
  end
178
419
 
179
420
  def valid_regexp?(value)
180
- # check for invalid regex syntex
421
+ # check for invalid regex syntax
181
422
  Regexp.new(value)
182
423
  true
183
424
  rescue
184
425
  false
185
426
  end
186
-
187
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
188
- def validate_value_type(value)
189
- type = validate_type(@opts[:type])
190
- return if type == 'Any'
191
-
192
- invalid_type = false
193
- if type == 'Regexp'
194
- invalid_type = true if !value.is_a?(String) || !valid_regexp?(value)
195
- elsif type == 'Numeric'
196
- invalid_type = true if !valid_numeric?(value)
197
- elsif type == 'Boolean'
198
- invalid_type = true if ![true, false].include?(value)
199
- elsif value.is_a?(Module.const_get(type)) == false
200
- invalid_type = true
201
- end
202
-
203
- if invalid_type == true
204
- error = Inspec::Input::ValidationError.new
205
- error.input_name = @name
206
- error.input_value = value
207
- error.input_type = type
208
- raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to type '#{error.input_type}'."
209
- end
210
- end
211
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
212
-
213
- def value_or_dummy
214
- @opts.key?(:value) ? @opts[:value] : Inspec::Input::NO_VALUE_SET.new(@name)
215
- end
216
427
  end
217
428
  end
@@ -81,7 +81,7 @@ module Inspec
81
81
  end
82
82
 
83
83
  attr_reader :source_reader, :backend, :runner_context, :check_mode
84
- attr_accessor :parent_profile, :profile_name
84
+ attr_accessor :parent_profile, :profile_id, :profile_name
85
85
  def_delegator :@source_reader, :tests
86
86
  def_delegator :@source_reader, :libraries
87
87
  def_delegator :@source_reader, :metadata
@@ -118,25 +118,32 @@ module Inspec
118
118
  @runtime_profile = RuntimeProfile.new(self)
119
119
  @backend.profile = @runtime_profile
120
120
 
121
+ # The AttributeRegistry is in charge of keeping track of inputs;
122
+ # it is the single source of truth. Now that we have a profile object,
123
+ # we can create any inputs that were provided by various mechanisms.
124
+ options[:runner_conf] ||= Inspec::Config.cached
125
+
126
+ if options[:runner_conf].key?(:attrs)
127
+ Inspec.deprecate(:rename_attributes_to_inputs, 'Use --input-file on the command line instead of --attrs.')
128
+ options[:runner_conf][:input_file] = options[:runner_conf].delete(:attrs)
129
+ end
130
+
131
+ Inspec::InputRegistry.bind_profile_inputs(
132
+ # Every input only exists in the context of a profile
133
+ metadata.params[:name], # TODO: test this with profile aliasing
134
+ # Remaining args are possible sources of inputs
135
+ cli_input_files: options[:runner_conf][:input_file], # From CLI --input-file
136
+ profile_metadata: metadata,
137
+ # TODO: deprecation checks here
138
+ runner_api: options[:runner_conf][:attributes], # This is the route the audit_cookbook and kitchen-inspec take
139
+ )
140
+
121
141
  @runner_context =
122
142
  options[:profile_context] ||
123
- Inspec::ProfileContext.for_profile(self, @backend, @input_values)
143
+ Inspec::ProfileContext.for_profile(self, @backend)
124
144
 
125
145
  @supports_platform = metadata.supports_platform?(@backend)
126
146
  @supports_runtime = metadata.supports_runtime?
127
- register_metadata_inputs
128
- end
129
-
130
- def register_metadata_inputs # TODO: deprecate
131
- if metadata.params.key?(:attributes) && metadata.params[:attributes].is_a?(Array)
132
- metadata.params[:attributes].each do |attribute|
133
- attr_dup = attribute.dup
134
- name = attr_dup.delete(:name)
135
- @runner_context.register_input(name, attr_dup)
136
- end
137
- elsif metadata.params.key?(:attributes)
138
- Inspec::Log.warn 'Inputs must be defined as an Array. Skipping current definition.'
139
- end
140
147
  end
141
148
 
142
149
  def name
@@ -595,7 +602,7 @@ module Inspec
595
602
  f = load_rule_filepath(prefix, rule)
596
603
  load_rule(rule, f, controls, groups)
597
604
  end
598
- params[:inputs] = @runner_context.inputs
605
+ params[:inputs] = Inspec::InputRegistry.list_inputs_for_profile(@profile_id)
599
606
  params
600
607
  end
601
608
 
@@ -12,13 +12,11 @@ require 'inspec/objects/input'
12
12
 
13
13
  module Inspec
14
14
  class ProfileContext
15
- def self.for_profile(profile, backend, inputs)
16
- new(profile.name, backend, { 'profile' => profile,
17
- 'inputs' => inputs,
18
- 'check_mode' => profile.check_mode })
15
+ def self.for_profile(profile, backend)
16
+ new(profile.name, backend, { 'profile' => profile, 'check_mode' => profile.check_mode })
19
17
  end
20
18
 
21
- attr_reader :inputs, :backend, :profile_name, :profile_id, :resource_registry
19
+ attr_reader :backend, :profile_name, :profile_id, :resource_registry
22
20
  attr_accessor :rules
23
21
  def initialize(profile_id, backend, conf)
24
22
  if backend.nil?
@@ -35,7 +33,8 @@ module Inspec
35
33
  @lib_subcontexts = []
36
34
  @require_loader = ::Inspec::RequireLoader.new
37
35
  Inspec::InputRegistry.register_profile_alias(@profile_id, @profile_name) if @profile_id != @profile_name
38
- @inputs = Inspec::InputRegistry.list_inputs_for_profile(@profile_id)
36
+ # TODO: consider polling input source plugins; this is a bulk fetch opportunity
37
+
39
38
  # A local resource registry that only contains resources defined
40
39
  # in the transitive dependency tree of the loaded profile.
41
40
  @resource_registry = Inspec::Resource.new_registry
@@ -43,6 +42,10 @@ module Inspec
43
42
  @current_load = nil
44
43
  end
45
44
 
45
+ def attributes
46
+ Inspec::AttributeRegistry.list_attributes_for_profile(@profile_id)
47
+ end
48
+
46
49
  def dependencies
47
50
  if @conf['profile'].nil?
48
51
  {}
@@ -187,13 +190,6 @@ module Inspec
187
190
  end
188
191
  end
189
192
 
190
- def register_input(name, options = {})
191
- # we need to return an input object, to allow dermination of values
192
- input = Inspec::InputRegistry.register_input(name, @profile_id, options)
193
- input.value = @conf['inputs'][name] unless @conf['inputs'].nil? || @conf['inputs'][name].nil?
194
- input.value
195
- end
196
-
197
193
  def set_header(field, val)
198
194
  @current_load[field] = val
199
195
  end
@@ -66,9 +66,13 @@ end
66
66
  class RSpec::Core::ExampleGroup
67
67
  # This DSL method allows us to access the values of inputs within InSpec tests
68
68
  def attribute(name)
69
- Inspec::InputRegistry.find_input(name, self.class.metadata[:profile_id]).value
69
+ Inspec::InputRegistry.find_or_register_input(name, self.class.metadata[:profile_id]).value
70
70
  end
71
71
  define_example_method :attribute
72
+ def input_obj(name)
73
+ Inspec::InputRegistry.find_or_register_input(name, self.class.metadata[:profile_id])
74
+ end
75
+ define_example_method :input_obj
72
76
 
73
77
  # Here, we have to ensure our method_missing gets called prior
74
78
  # to RSpec::Core::ExampleGroup.method_missing (the class method).
data/lib/inspec/runner.rb CHANGED
@@ -9,7 +9,6 @@ require 'inspec/backend'
9
9
  require 'inspec/profile_context'
10
10
  require 'inspec/profile'
11
11
  require 'inspec/metadata'
12
- require 'inspec/secrets'
13
12
  require 'inspec/config'
14
13
  require 'inspec/dependencies/cache'
15
14
  # spec requirements
@@ -32,7 +31,7 @@ module Inspec
32
31
  class Runner
33
32
  extend Forwardable
34
33
 
35
- attr_reader :backend, :rules, :inputs
34
+ attr_reader :backend, :rules
36
35
 
37
36
  def attributes
38
37
  Inspec.deprecate(:rename_attributes_to_inputs, "Don't call runner.attributes, call runner.inputs")
@@ -57,10 +56,17 @@ module Inspec
57
56
  RunnerRspec.new(@conf)
58
57
  end
59
58
 
60
- # list of profile inputs
61
- @inputs = {}
59
+ # About reading inputs:
60
+ # @conf gets passed around a lot, eventually to
61
+ # Inspec::InputRegistry.register_external_inputs.
62
+ #
63
+ # @conf may contain the key :attributes or :inputs, which is to be a Hash
64
+ # of values passed in from the Runner API.
65
+ # This is how kitchen-inspec and the audit_cookbook pass in inputs.
66
+ #
67
+ # @conf may contain the key :attrs or :input_file, which is to be an Array
68
+ # of file paths, each a YAML file. This how --input-file works.
62
69
 
63
- load_inputs(@conf)
64
70
  configure_transport
65
71
  end
66
72
 
@@ -101,7 +107,6 @@ module Inspec
101
107
  @test_collector.add_profile(requirement.profile)
102
108
  end
103
109
 
104
- @inputs = profile.runner_context.inputs if @inputs.empty?
105
110
  tests = profile.collect_tests
106
111
  all_controls += tests unless tests.nil?
107
112
  end
@@ -149,35 +154,6 @@ module Inspec
149
154
  @test_collector.exit_code
150
155
  end
151
156
 
152
- # determine all inputs before the execution, fetch data from secrets backend
153
- def load_inputs(options)
154
- # TODO: - rename :attributes - it is user-visible
155
- options[:attributes] ||= {}
156
-
157
- if options.key?(:attrs)
158
- Inspec.deprecate(:rename_attributes_to_inputs, 'Use --input-file on the command line instead of --attrs.')
159
- options[:input_file] = options.delete(:attrs)
160
- end
161
- secrets_targets = options[:input_file]
162
- return options[:attributes] if secrets_targets.nil?
163
-
164
- secrets_targets.each do |target|
165
- validate_inputs_file_readability!(target)
166
-
167
- secrets = Inspec::SecretsBackend.resolve(target)
168
- if secrets.nil?
169
- raise Inspec::Exceptions::SecretsBackendNotFound,
170
- "Cannot find parser for inputs file '#{target}'. " \
171
- 'Check to make sure file has the appropriate extension.'
172
- end
173
-
174
- next if secrets.inputs.nil?
175
- options[:attributes].merge!(secrets.inputs)
176
- end
177
-
178
- options[:attributes]
179
- end
180
-
181
157
  #
182
158
  # add_target allows the user to add a target whose tests will be
183
159
  # run when the user calls the run method.
@@ -209,7 +185,7 @@ module Inspec
209
185
  vendor_cache: @cache,
210
186
  backend: @backend,
211
187
  controls: @controls,
212
- inputs: @conf[:attributes]) # TODO: read form :inputs here (user visible)
188
+ runner_conf: @conf)
213
189
  raise "Could not resolve #{target} to valid input." if profile.nil?
214
190
  @target_profiles << profile if supports_profile?(profile)
215
191
  end
@@ -300,22 +276,6 @@ module Inspec
300
276
  examples.each { |e| @test_collector.add_test(e, rule) }
301
277
  end
302
278
 
303
- def validate_inputs_file_readability!(target)
304
- unless File.exist?(target)
305
- raise Inspec::Exceptions::InputsFileDoesNotExist,
306
- "Cannot find input file '#{target}'. " \
307
- 'Check to make sure file exists.'
308
- end
309
-
310
- unless File.readable?(target)
311
- raise Inspec::Exceptions::InputsFileNotReadable,
312
- "Cannot read input file '#{target}'. " \
313
- 'Check to make sure file is readable.'
314
- end
315
-
316
- true
317
- end
318
-
319
279
  def rspec_skipped_block(arg, opts, message)
320
280
  @test_collector.example_group(*arg, opts) do
321
281
  # Send custom `it` block to RSpec
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = '4.1.4.preview'.freeze
2
+ VERSION = '4.2.0.preview'.freeze
3
3
  end
@@ -108,7 +108,7 @@ module Inspec::Resources
108
108
  results = table.map { |row|
109
109
  res = {}
110
110
  headers.each { |header|
111
- res[header.downcase] = row[header]
111
+ res[header.downcase] = row[header] if header
112
112
  }
113
113
  Hashie::Mash.new(res)
114
114
  }
@@ -569,6 +569,10 @@ module Inspec::Resources
569
569
  # example: ::ffff:10.0.2.15:9200
570
570
  host.delete!('::ffff:') if host.start_with?('::ffff:')
571
571
 
572
+ # To remove brackets that might surround the IPv6 address
573
+ # example: [::] and [fe80::dc11:b9b6:514b:134]%eth0:123
574
+ host = host.tr('[]', '')
575
+
572
576
  # if there's an interface name in the local address, which is common for
573
577
  # IPv6 listeners, strip that out too.
574
578
  # example: fe80::a00:27ff:fe32:ed09%enp0s3
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.4.preview
4
+ version: 4.2.0.preview
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-22 00:00:00.000000000 Z
11
+ date: 2019-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train-core