inspec 4.12.0 → 4.16.0

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
  SHA256:
3
- metadata.gz: 72aac2ba7eb1565ecbbd18436e96c770151f5be68b3bcc8897db7a0baee22621
4
- data.tar.gz: 2466be933623846f985f9b66e3a572d617f516274ad2dc7b81d048b2ebe2d104
3
+ metadata.gz: 877fb588106fac603caf212f8224901fbad9d694097718b85382c40140989477
4
+ data.tar.gz: afc82cd0c4e59ddf7466c68f8a67a79288548f44fddb5b49b27726f8dbe2f05e
5
5
  SHA512:
6
- metadata.gz: 269ccd7e103663bec2dd23058dc0481775b840f22a93a3cd81e8f8e0e5c774def3cd6ecaae12604f2c13fe6bcfd33864040acfa9a2ff3bf49e50c1111f5d7be9
7
- data.tar.gz: 9f6b41d84be75a69cb1c405302f7c076f6192f16f506d8d4b958bf76e707057961a6ba692dadb528b39997140c1682ee9d2d328efd8d06f2249f104497c79b62
6
+ metadata.gz: 197367b25a88684493e27d1db0b070c2790d9373476f98b09fb11a5d213d50e4c971d26c9be8c4969833cbb87f09fa28c88be448303646bd26ca85374182af44
7
+ data.tar.gz: ac3787f1a29e222221edbeefa46c620fc59d2a6e712e2c1de8d3594de448473acd0700ba7880c1d2e82bd7b02804f161ad42421423940f36b8ffa8b43b0b90d1
@@ -25,10 +25,6 @@
25
25
  "plugin_name": "inspec-release",
26
26
  "rationale": "This gem is currently only a placeholder, waiting to be built."
27
27
  },
28
- {
29
- "plugin_name": "inspec-vault",
30
- "rationale": "This gem is currently only a placeholder, waiting to be built."
31
- },
32
28
  {
33
29
  "plugin_name": "train-vault",
34
30
  "rationale": "This gem is currently only a placeholder, waiting to be built."
data/lib/fetchers/git.rb CHANGED
@@ -52,6 +52,7 @@ module Fetchers
52
52
  # processing, but then again, if you passed a relative path
53
53
  # to an on-disk repo, you probably expect it to exist.
54
54
  return url_or_file_path unless File.exist?(url_or_file_path)
55
+
55
56
  # It's important to expand this path, because it may be specified
56
57
  # locally in the metadata files, and when we clone, we will be
57
58
  # in a temp dir.
@@ -97,6 +98,7 @@ module Fetchers
97
98
 
98
99
  def cache_key
99
100
  return resolved_ref unless @relative_path
101
+
100
102
  OpenSSL::Digest::SHA256.hexdigest(resolved_ref + @relative_path)
101
103
  end
102
104
 
@@ -133,6 +133,8 @@ module Inspec
133
133
  option :reporter, type: :array,
134
134
  banner: "one two:/output/file/path",
135
135
  desc: "Enable one or more output reporters: cli, documentation, html, progress, json, json-min, json-rspec, junit, yaml"
136
+ option :input, type: :array, banner: "name1=value1 name2=value2",
137
+ desc: "Specify one or more inputs directly on the command line, as --input NAME=VALUE"
136
138
  option :input_file, type: :array,
137
139
  desc: "Load one or more input files, a YAML file with values for the profile to use"
138
140
  option :attrs, type: :array,
data/lib/inspec/cli.rb CHANGED
@@ -124,8 +124,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
124
124
  else
125
125
  %w{location profile controls timestamp valid}.each do |item|
126
126
  prepared_string = format("%-12s %s",
127
- "#{item.to_s.capitalize} :",
128
- result[:summary][item.to_sym])
127
+ "#{item.to_s.capitalize} :",
128
+ result[:summary][item.to_sym])
129
129
  ui.plain_line(prepared_string)
130
130
  end
131
131
  puts
data/lib/inspec/config.rb CHANGED
@@ -7,9 +7,12 @@ require "forwardable"
7
7
  require "thor"
8
8
  require "base64"
9
9
  require "inspec/base_cli"
10
+ require "inspec/plugin/v2/filter"
10
11
 
11
12
  module Inspec
12
13
  class Config
14
+ include Inspec::Plugin::V2::FilterPredicates
15
+
13
16
  # These are options that apply to any transport
14
17
  GENERIC_CREDENTIALS = %w{
15
18
  backend
@@ -23,6 +26,11 @@ module Inspec
23
26
  shell_command
24
27
  }.freeze
25
28
 
29
+ KNOWN_VERSIONS = [
30
+ "1.1",
31
+ "1.2",
32
+ ].freeze
33
+
26
34
  extend Forwardable
27
35
 
28
36
  # Many parts of InSpec expect to treat the Config as a Hash
@@ -48,6 +56,7 @@ module Inspec
48
56
  def initialize(cli_opts = {}, cfg_io = nil, command_name = nil)
49
57
  @command_name = command_name || (ARGV.empty? ? nil : ARGV[0].to_sym)
50
58
  @defaults = Defaults.for_command(@command_name)
59
+ @plugin_cfg = {}
51
60
 
52
61
  @cli_opts = cli_opts.dup
53
62
  cfg_io = resolve_cfg_io(@cli_opts, cfg_io)
@@ -119,6 +128,13 @@ module Inspec
119
128
  end
120
129
  end
121
130
 
131
+ #-----------------------------------------------------------------------#
132
+ # Fetching Plugin Data
133
+ #-----------------------------------------------------------------------#
134
+ def fetch_plugin_config(plugin_name)
135
+ Thor::CoreExt::HashWithIndifferentAccess.new(@plugin_cfg[plugin_name] || {})
136
+ end
137
+
122
138
  private
123
139
 
124
140
  def _utc_merge_transport_options(credentials, transport_name)
@@ -285,16 +301,24 @@ module Inspec
285
301
  # Assume legacy format, which is unconstrained
286
302
  return unless version
287
303
 
288
- unless version == "1.1"
289
- raise Inspec::ConfigError::Invalid, "Unsupported config file version '#{version}' - currently supported versions: 1.1"
304
+ unless KNOWN_VERSIONS.include?(version)
305
+ raise Inspec::ConfigError::Invalid, "Unsupported config file version '#{version}' - currently supported versions: #{KNOWN_VERSIONS.join(",")}"
290
306
  end
291
307
 
308
+ # Use Gem::Version for comparision operators
309
+ cfg_version = Gem::Version.new(version)
310
+ version_1_2 = Gem::Version.new("1.2")
311
+
312
+ # TODO: proper schema version loading and validation
292
313
  valid_fields = %w{version cli_options credentials compliance reporter}.sort
314
+ valid_fields << "plugins" if cfg_version >= version_1_2
293
315
  @cfg_file_contents.keys.each do |seen_field|
294
316
  unless valid_fields.include?(seen_field)
295
317
  raise Inspec::ConfigError::Invalid, "Unrecognized top-level configuration field #{seen_field}. Recognized fields: #{valid_fields.join(", ")}"
296
318
  end
297
319
  end
320
+
321
+ validate_plugins! if cfg_version >= version_1_2
298
322
  end
299
323
 
300
324
  def validate_reporters!(reporters)
@@ -334,6 +358,29 @@ module Inspec
334
358
  raise ArgumentError, "The option --reporter can only have a single report outputting to stdout." if stdout_reporters > 1
335
359
  end
336
360
 
361
+ def validate_plugins!
362
+ return unless @cfg_file_contents.key? "plugins"
363
+
364
+ data = @cfg_file_contents["plugins"]
365
+ unless data.is_a?(Hash)
366
+ raise Inspec::ConfigError::Invalid, "The 'plugin' field in your config file must be a hash (key-value list), not an array."
367
+ end
368
+
369
+ data.each do |plugin_name, plugin_settings|
370
+ # Enforce that every key is a valid inspec or train plugin name
371
+ unless valid_plugin_name?(plugin_name)
372
+ raise Inspec::ConfigError::Invalid, "Plugin settings should ne named after the the InSpec or Train plugin. Valid names must begin with inspec- or train-, not '#{plugin_name}' "
373
+ end
374
+
375
+ # Enforce that every entry is hash-valued
376
+ unless plugin_settings.is_a?(Hash)
377
+ raise Inspec::ConfigError::Invalid, "The plugin settings for '#{plugin_name}' in your config file should be a Hash (key-value list)."
378
+ end
379
+ end
380
+
381
+ @plugin_cfg = data
382
+ end
383
+
337
384
  #-----------------------------------------------------------------------#
338
385
  # Merging Options
339
386
  #-----------------------------------------------------------------------#
@@ -64,6 +64,9 @@ module Inspec
64
64
  #-------------------------------------------------------------#
65
65
 
66
66
  def find_or_register_input(input_name, profile_name, options = {})
67
+ input_name = input_name.to_s
68
+ profile_name = profile_name.to_s
69
+
67
70
  if profile_alias?(profile_name) && !profile_aliases[profile_name].nil?
68
71
  alias_name = profile_name
69
72
  profile_name = profile_aliases[profile_name]
@@ -132,10 +135,37 @@ module Inspec
132
135
  bind_inputs_from_metadata(profile_name, sources[:profile_metadata])
133
136
  bind_inputs_from_input_files(profile_name, sources[:cli_input_files])
134
137
  bind_inputs_from_runner_api(profile_name, sources[:runner_api])
138
+ bind_inputs_from_cli_args(profile_name, sources[:cli_input_arg])
135
139
  end
136
140
 
137
141
  private
138
142
 
143
+ def bind_inputs_from_cli_args(profile_name, input_list)
144
+ # TODO: move this into a core plugin
145
+
146
+ return if input_list.nil?
147
+ return if input_list.empty?
148
+
149
+ # These arrive as an array of "name=value" strings
150
+ # If the user used a comma, we'll see unfortunately see it as "name=value," pairs
151
+ input_list.each do |pair|
152
+ unless pair.include?("=")
153
+ if pair.end_with?(".yaml")
154
+ raise ArgumentError, "ERROR: --input is used for individual input values, as --input name=value. Use --input-file to load a YAML file."
155
+ else
156
+ raise ArgumentError, "ERROR: An '=' is required when using --input. Usage: --input input_name1=input_value1 input2=value2"
157
+ end
158
+ end
159
+ input_name, input_value = pair.split("=")
160
+ evt = Inspec::Input::Event.new(
161
+ value: input_value.chomp(","), # Trim trailing comma if any
162
+ provider: :cli,
163
+ priority: 50
164
+ )
165
+ find_or_register_input(input_name, profile_name, event: evt)
166
+ end
167
+ end
168
+
139
169
  def bind_inputs_from_runner_api(profile_name, input_hash)
140
170
  # TODO: move this into a core plugin
141
171
 
@@ -88,6 +88,10 @@ module Inspec
88
88
  errors.push("Version needs to be in SemVer format")
89
89
  end
90
90
 
91
+ unless supports_runtime?
92
+ warnings.push("The current inspec version #{Inspec::VERSION} cannot satisfy profile inspec_version constraint #{params[:inspec_version]}")
93
+ end
94
+
91
95
  %w{title summary maintainer copyright license}.each do |field|
92
96
  next unless params[field.to_sym].nil?
93
97
 
@@ -81,17 +81,13 @@ module Inspec
81
81
  @resource_skipped = false
82
82
  @resource_failed = false
83
83
  @supports = Inspec::Resource.supports[name]
84
+ @resource_exception_message = nil
84
85
 
85
86
  # attach the backend to this instance
86
87
  @__backend_runner__ = backend
87
88
  @__resource_name__ = name
88
89
 
89
- # check resource supports
90
- supported = true
91
- supported = check_supports unless @supports.nil?
92
- test_backend = defined?(Train::Transports::Mock::Connection) && backend.backend.class == Train::Transports::Mock::Connection
93
- # do not return if we are supported, or for tests
94
- return unless supported || test_backend
90
+ check_supports unless @supports.nil? # this has side effects
95
91
 
96
92
  # call the resource initializer
97
93
  begin
@@ -100,12 +96,10 @@ module Inspec
100
96
  skip_resource(e.message)
101
97
  rescue Inspec::Exceptions::ResourceFailed => e
102
98
  fail_resource(e.message)
99
+ rescue NotImplementedError => e
100
+ fail_resource(e.message) unless @resource_failed
103
101
  rescue NoMethodError => e
104
- # The new platform resources have methods generated on the fly
105
- # for inspec check to work we need to skip these train errors
106
- raise unless test_backend && e.receiver.class == Train::Transports::Mock::Connection
107
-
108
- skip_resource(e.message)
102
+ skip_resource(e.message) unless @resource_failed
109
103
  end
110
104
  end
111
105
 
@@ -2,6 +2,8 @@ require "singleton"
2
2
  require "json"
3
3
  require "inspec/globals"
4
4
 
5
+ module Inspec::Plugin; end
6
+
5
7
  module Inspec::Plugin::V2
6
8
  Exclusion = Struct.new(:plugin_name, :rationale)
7
9
 
@@ -60,4 +62,35 @@ module Inspec::Plugin::V2
60
62
  end
61
63
  end
62
64
  end
65
+
66
+ # To be a valid plugin name, the plugin must beign with either
67
+ # inspec- or train-, AND ALSO not be on the exclusion list.
68
+ # We maintain this exclusion list to avoid confusing users.
69
+ # For example, we want to have a real gem named inspec-test-fixture,
70
+ # but we don't want the users to see that.
71
+ module FilterPredicates
72
+ def train_plugin_name?(name)
73
+ valid_plugin_name?(name, :train)
74
+ end
75
+
76
+ def inspec_plugin_name?(name)
77
+ valid_plugin_name?(name, :inspec)
78
+ end
79
+
80
+ def valid_plugin_name?(name, kind = :either)
81
+ # Must have a permitted prefix.
82
+ return false unless case kind
83
+ when :inspec
84
+ name.to_s.start_with?("inspec-")
85
+ when :train
86
+ name.to_s.start_with?("train-")
87
+ when :either
88
+ name.to_s.match(/^(inspec|train)-/)
89
+ else false
90
+ end # rubocop: disable Layout/EndAlignment
91
+
92
+ # And must not be on the exclusion list.
93
+ ! Inspec::Plugin::V2::PluginFilter.exclude?(name)
94
+ end
95
+ end
63
96
  end
@@ -60,14 +60,15 @@ module Inspec::Plugin::V2
60
60
  # TODO: - check plugins.json for validity before trying anything that needs to modify it.
61
61
  validate_installation_opts(plugin_name, opts)
62
62
 
63
- # TODO: change all of these to return installed spec/gem/thingy
64
63
  # TODO: return installed thingy
65
64
  if opts[:path]
66
65
  install_from_path(plugin_name, opts)
67
66
  elsif opts[:gem_file]
68
- install_from_gem_file(plugin_name, opts)
67
+ gem_version = install_from_gem_file(plugin_name, opts)
68
+ opts[:version] = gem_version.to_s
69
69
  else
70
- install_from_remote_gems(plugin_name, opts)
70
+ gem_version = install_from_remote_gems(plugin_name, opts)
71
+ opts[:version] = gem_version.to_s
71
72
  end
72
73
 
73
74
  update_plugin_config_file(plugin_name, opts.merge({ action: :install }))
@@ -88,9 +89,9 @@ module Inspec::Plugin::V2
88
89
 
89
90
  # TODO: Handle installing from a local file
90
91
  # TODO: Perform dependency checks to make sure the new solution is valid
91
- install_from_remote_gems(plugin_name, opts)
92
+ gem_version = install_from_remote_gems(plugin_name, opts)
92
93
 
93
- update_plugin_config_file(plugin_name, opts.merge({ action: :update }))
94
+ update_plugin_config_file(plugin_name, opts.merge({ action: :update, version: gem_version.to_s }))
94
95
  end
95
96
 
96
97
  # Uninstalls (removes) a plugin. Refers to plugin.json to determine if it
@@ -335,13 +336,15 @@ module Inspec::Plugin::V2
335
336
  # not obliged to during packaging.)
336
337
  # So, after each install, run a scan for all gem(specs) we manage, and copy in their gemspec file
337
338
  # into the exploded gem source area if absent.
338
-
339
339
  loader.list_managed_gems.each do |spec|
340
340
  path_inside_source = File.join(spec.gem_dir, "#{spec.name}.gemspec")
341
341
  unless File.exist?(path_inside_source)
342
342
  File.write(path_inside_source, spec.to_ruby)
343
343
  end
344
344
  end
345
+
346
+ # Locate the GemVersion for the new dependency and return it
347
+ solution.detect { |g| g.name == new_plugin_dependency.name }.version
345
348
  end
346
349
 
347
350
  #===================================================================#
@@ -365,7 +368,7 @@ module Inspec::Plugin::V2
365
368
  # excluding any that are path-or-core-based, excluding the gem to be removed
366
369
  plugin_deps_we_still_must_satisfy = registry.plugin_statuses
367
370
  plugin_deps_we_still_must_satisfy = plugin_deps_we_still_must_satisfy.select do |status|
368
- status.installation_type == :gem && status.name != plugin_name_to_be_removed.to_sym
371
+ status.installation_type == :user_gem && status.name != plugin_name_to_be_removed.to_sym
369
372
  end
370
373
  plugin_deps_we_still_must_satisfy = plugin_deps_we_still_must_satisfy.map do |status|
371
374
  constraint = status.version || "> 0"
@@ -1,5 +1,6 @@
1
1
  require "inspec/log"
2
2
  require "inspec/plugin/v2/config_file"
3
+ require "inspec/plugin/v2/filter"
3
4
 
4
5
  # Add the current directory of the process to the load path
5
6
  $LOAD_PATH.unshift(".") unless $LOAD_PATH.include?(".")
@@ -11,9 +12,16 @@ module Inspec::Plugin::V2
11
12
  class Loader
12
13
  attr_reader :conf_file, :registry, :options
13
14
 
15
+ # For {inspec|train}_plugin_name?
16
+ include Inspec::Plugin::V2::FilterPredicates
17
+ extend Inspec::Plugin::V2::FilterPredicates
18
+
14
19
  def initialize(options = {})
15
20
  @options = options
16
21
  @registry = Inspec::Plugin::V2::Registry.instance
22
+
23
+ # User plugins are those installed by the user via `inspec plugin install`
24
+ # and are installed under ~/.inspec/gems
17
25
  unless options[:omit_user_plugins]
18
26
  @conf_file = Inspec::Plugin::V2::ConfigFile.new
19
27
  read_conf_file_into_registry
@@ -27,9 +35,8 @@ module Inspec::Plugin::V2
27
35
  # and may be safely loaded
28
36
  detect_core_plugins unless options[:omit_core_plugins]
29
37
 
30
- # Train plugins aren't InSpec plugins (they don't use our API)
31
- # but InSpec CLI manages them. So, we have to wrap them a bit.
32
- accommodate_train_plugins
38
+ # Identify plugins that inspec is co-installed with
39
+ detect_system_plugins unless options[:omit_sys_plugins]
33
40
  end
34
41
 
35
42
  def load_all
@@ -46,7 +53,7 @@ module Inspec::Plugin::V2
46
53
  begin
47
54
  # We could use require, but under testing, we need to repeatedly reload the same
48
55
  # plugin. However, gems only work with require (rubygems dooes not overload `load`)
49
- if plugin_details.installation_type == :gem
56
+ if plugin_details.installation_type == :user_gem
50
57
  activate_managed_gems_for_plugin(plugin_name)
51
58
  require plugin_details.entry_point
52
59
  else
@@ -130,10 +137,11 @@ module Inspec::Plugin::V2
130
137
  end
131
138
 
132
139
  # Lists all plugin gems found in the plugin_gem_path.
133
- # This is simply all gems that begin with train- or inspec-.
140
+ # This is simply all gems that begin with train- or inspec-
141
+ # and are not on the exclusion list.
134
142
  # @return [Array[Gem::Specification]] Specs of all gems found.
135
143
  def self.list_installed_plugin_gems
136
- list_managed_gems.select { |spec| spec.name.match(/^(inspec|train)-/) }
144
+ list_managed_gems.select { |spec| valid_plugin_name?(spec.name) }
137
145
  end
138
146
 
139
147
  def list_installed_plugin_gems
@@ -234,34 +242,70 @@ module Inspec::Plugin::V2
234
242
  end
235
243
  end
236
244
 
237
- def accommodate_train_plugins
238
- registry.plugin_names.map(&:to_s).grep(/^train-/).each do |train_plugin_name|
239
- status = registry[train_plugin_name.to_sym]
240
- status.api_generation = :'train-1'
241
-
242
- if status.installation_type == :gem
243
- # Activate the gem. This allows train to 'require' the gem later.
244
- activate_managed_gems_for_plugin(train_plugin_name)
245
- end
246
- end
247
- end
248
-
249
245
  def read_conf_file_into_registry
250
246
  conf_file.each do |plugin_entry|
251
247
  status = Inspec::Plugin::V2::Status.new
252
248
  status.name = plugin_entry[:name]
253
249
  status.loaded = false
254
- status.installation_type = (plugin_entry[:installation_type] || :gem)
250
+ status.installation_type = (plugin_entry[:installation_type] || :user_gem)
255
251
  case status.installation_type
256
- when :gem
252
+ when :user_gem
257
253
  status.entry_point = status.name.to_s
258
254
  status.version = plugin_entry[:version]
259
255
  when :path
260
256
  status.entry_point = plugin_entry[:installation_path]
261
257
  end
262
258
 
259
+ # Train plugins are not true InSpec plugins; we need to decorate them a
260
+ # bit more to integrate them.
261
+ fixup_train_plugin_status(status) if train_plugin_name?(plugin_entry[:name])
262
+
263
263
  registry[status.name] = status
264
264
  end
265
265
  end
266
+
267
+ def fixup_train_plugin_status(status)
268
+ status.api_generation = :'train-1'
269
+ if status.installation_type == :user_gem
270
+ # Activate the gem. This allows train to 'require' the gem later.
271
+ activate_managed_gems_for_plugin(status.entry_point)
272
+ end
273
+ end
274
+
275
+ def detect_system_plugins
276
+ # Find the gemspec for inspec
277
+ inspec_gemspec = Gem::Specification.find_by_name("inspec", "=#{Inspec::VERSION}")
278
+
279
+ # Make a RequestSet that represents the dependencies of inspec
280
+ inspec_deps_request_set = Gem::RequestSet.new(*inspec_gemspec.dependencies)
281
+ inspec_deps_request_set.remote = false
282
+
283
+ # Resolve the request against the installed gem universe
284
+ gem_resolver = Gem::Resolver::CurrentSet.new
285
+ runtime_solution = inspec_deps_request_set.resolve(gem_resolver)
286
+
287
+ inspec_gemspec.dependencies.each do |inspec_dep|
288
+ next unless inspec_plugin_name?(inspec_dep.name) || train_plugin_name?(inspec_dep.name)
289
+
290
+ plugin_spec = runtime_solution.detect { |s| s.name == inspec_dep.name }.spec
291
+
292
+ status = Inspec::Plugin::V2::Status.new
293
+ status.name = inspec_dep.name
294
+ status.entry_point = inspec_dep.name # gem-based, just 'require' the name
295
+ status.version = plugin_spec.version.to_s
296
+ status.loaded = false
297
+ status.installation_type = :system_gem
298
+
299
+ if train_plugin_name?(status[:name])
300
+ # Train plugins are not true InSpec plugins; we need to decorate them a
301
+ # bit more to integrate them.
302
+ fixup_train_plugin_status(status)
303
+ else
304
+ status.api_generation = 2
305
+ end
306
+
307
+ registry[status.name.to_sym] = status
308
+ end
309
+ end
266
310
  end
267
311
  end