inspec-core 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: 873c4c83c46030d3a3adb7093f085650f8400c6bbfc7c87984ebc77305aa660b
4
- data.tar.gz: '091ccf8b261dfeafac84a771ec549e46f015dcc9e5da9836982788df40c456c1'
3
+ metadata.gz: 3ff3612b0be834f75c0bd4e457652ee6736aaff5787519e28c8733c114ecc330
4
+ data.tar.gz: 58b2c9c7ddcf0bd09ce2e4d9cdf34e67ca0786cb3b97453253e375ec3fe6c29d
5
5
  SHA512:
6
- metadata.gz: ed9c35b0c96d17f96eea08658b251812ac3f03c4912a1663b9d5de600a5d6637460bf223ca90e9ca84625298b5dce64f999ac8d019c2b615b621247c48e1693a
7
- data.tar.gz: fca132a2f7221c41c78bf7379ccf74e4c07cc412d36da10817d99adc15a9c2e5a049f6311a8c6bf323a8c7deffed18462d8fb660b0c9669d5182b477cf3ca585
6
+ metadata.gz: d23d441a6db5edc44f754717a6e86b581065b8739d8ee68c657055f2ab053fcbecaed2b800938840a5d7fd8fb9879c27076862e9875258ae9c091778f5b8d99a
7
+ data.tar.gz: 3edba9eccd97749bb2661f1ab9dde985fbba0f3524e349383967d8e6517df36adad8969be7a7129e05bd96cdd35a49516c9b754fdc92ac3840630f135b1f05fb
@@ -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
@@ -116,9 +116,19 @@ module Inspec
116
116
  # we can create any inputs that were provided by various mechanisms.
117
117
  options[:runner_conf] ||= Inspec::Config.cached
118
118
 
119
+ # Catch legacy CLI input option usage
119
120
  if options[:runner_conf].key?(:attrs)
120
121
  Inspec.deprecate(:rename_attributes_to_inputs, "Use --input-file on the command line instead of --attrs.")
121
122
  options[:runner_conf][:input_file] = options[:runner_conf].delete(:attrs)
123
+ elsif options[:runner_conf].key?(:input_files)
124
+ # The kitchen-inspec docs say to use plural. Our CLI and internal expectations are singular.
125
+ options[:runner_conf][:input_file] = options[:runner_conf].delete(:input_files)
126
+ end
127
+
128
+ # Catch legacy kitchen-inspec input usage
129
+ if options[:runner_conf].key?(:attributes)
130
+ Inspec.deprecate(:rename_attributes_to_inputs, "Use :inputs in your kitchen.yml verifier config instead of :attributes.")
131
+ options[:runner_conf][:inputs] = options[:runner_conf].delete(:attributes)
122
132
  end
123
133
 
124
134
  Inspec::InputRegistry.bind_profile_inputs(
@@ -127,8 +137,8 @@ module Inspec
127
137
  # Remaining args are possible sources of inputs
128
138
  cli_input_files: options[:runner_conf][:input_file], # From CLI --input-file
129
139
  profile_metadata: metadata,
130
- # TODO: deprecation checks here
131
- runner_api: options[:runner_conf][:attributes] # This is the route the audit_cookbook and kitchen-inspec take
140
+ runner_api: options[:runner_conf][:inputs], # This is the route the audit_cookbook and kitchen-inspec take
141
+ cli_input_arg: options[:runner_conf][:input] # The --input name=value CLI option
132
142
  )
133
143
 
134
144
  @runner_context =
@@ -84,6 +84,7 @@ require "inspec/resources/passwd"
84
84
  require "inspec/resources/pip"
85
85
  require "inspec/resources/platform"
86
86
  require "inspec/resources/port"
87
+ require "inspec/resources/postfix_conf"
87
88
  require "inspec/resources/postgres"
88
89
  require "inspec/resources/postgres_conf"
89
90
  require "inspec/resources/postgres_hba_conf"
@@ -0,0 +1,31 @@
1
+ require "inspec/resources/ini"
2
+ require "inspec/utils/simpleconfig"
3
+
4
+ module Inspec::Resources
5
+ class PostfixConf < IniConfig
6
+ name "postfix_conf"
7
+ supports platform: "linux"
8
+ desc "Use the postfix_conf Inspec audit resource to test the configuration of the Postfix Mail Transfer Agent"
9
+
10
+ # Allow user to specify a custom configuration path, use default Postfix configuration path if no custom path is provided
11
+ def initialize(*opts)
12
+ @params = {}
13
+ if opts.length == 1
14
+ @raw_content = load_raw_content(opts)
15
+ else
16
+ @raw_content = load_raw_content("/etc/postfix/main.cf")
17
+ end
18
+ @params = parse(@raw_content)
19
+ end
20
+
21
+ def parse(content)
22
+ SimpleConfig.new(content).params
23
+ end
24
+
25
+ private
26
+
27
+ def resource_base_name
28
+ "POSTFIX_CONF"
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "4.12.0".freeze
2
+ VERSION = "4.16.0".freeze
3
3
  end
@@ -6,6 +6,14 @@ require "inspec/dist"
6
6
  module InspecPlugins
7
7
  module PluginManager
8
8
  class CliCommand < Inspec.plugin(2, :cli_command)
9
+ INSTALL_TYPE_LABELS = {
10
+ bundle: "core", # Calling this core, too - not much of a distinction
11
+ core: "core",
12
+ path: "path",
13
+ user_gem: "gem (user)",
14
+ system_gem: "gem (system)",
15
+ }.freeze
16
+
9
17
  include Inspec::Dist
10
18
 
11
19
  subcommand_desc "plugin SUBCOMMAND", "Manage #{PRODUCT_NAME} and Train plugins"
@@ -15,22 +23,36 @@ module InspecPlugins
15
23
  #==================================================================#
16
24
 
17
25
  desc "list [options]", "Lists user-installed #{PRODUCT_NAME} plugins."
18
- option :all, desc: "Include plugins shipped with #{PRODUCT_NAME} as well.", type: :boolean, aliases: [:a]
26
+ option :all, desc: "List all types of plugins (default)", type: :boolean, default: true, aliases: [:a]
27
+ option :user, desc: "List user plugins, from ~/.inspec/gems", banner: "", type: :boolean, default: false, aliases: [:u]
28
+ option :system, desc: "List system plugins, those InSpec depends on", banner: "", type: :boolean, default: false, aliases: [:s]
29
+ option :core, desc: "List core plugins, those InSpec ships with", banner: "", type: :boolean, default: false, aliases: [:c]
30
+
19
31
  def list
20
32
  plugin_statuses = Inspec::Plugin::V2::Registry.instance.plugin_statuses
21
- plugin_statuses.reject! { |s| %i{core bundle}.include?(s.installation_type) } unless options[:all]
33
+ options[:all] = false if options[:core] || options[:user] || options[:system]
34
+ plugin_statuses.select! do |status|
35
+ type = status.installation_type
36
+ options[:all] ||
37
+ (options[:core] && %i{core bundle}.include?(type)) ||
38
+ (options[:user] && %i{user_gem path}.include?(type)) ||
39
+ (options[:system] && :system_gem == type)
40
+ end
22
41
 
23
- puts
24
- ui.bold(format(" %-30s%-10s%-8s%-6s", "Plugin Name", "Version", "Via", "ApiVer"))
25
- ui.line
26
- plugin_statuses.sort_by(&:name).each do |status|
27
- ui.plain(format(" %-30s%-10s%-8s%-6s", status.name,
28
- make_pretty_version(status),
29
- status.installation_type,
30
- status.api_generation.to_s))
42
+ unless plugin_statuses.empty?
43
+ ui.table do |t|
44
+ t.header = ["Plugin Name", "Version", "Via", "ApiVer"]
45
+ plugin_statuses.sort_by { |s| s.name.to_s }.each do |status|
46
+ t << [
47
+ status.name,
48
+ make_pretty_version(status),
49
+ make_pretty_install_type(status),
50
+ status.api_generation,
51
+ ]
52
+ end
53
+ end
31
54
  end
32
- ui.line
33
- ui.plain(" #{plugin_statuses.count} plugin(s) total")
55
+ ui.plain_line(" #{plugin_statuses.count} plugin(s) total")
34
56
  puts
35
57
  end
36
58
 
@@ -60,15 +82,15 @@ module InspecPlugins
60
82
  end
61
83
 
62
84
  puts
63
- ui.bold(format(" %-30s%-50s", "Plugin Name", "Versions Available"))
85
+ ui.bold(format(" %-30s%-50s\n", "Plugin Name", "Versions Available"))
64
86
  ui.line
65
87
  search_results.keys.sort.each do |plugin_name|
66
88
  versions = options[:all] ? search_results[plugin_name] : [search_results[plugin_name].first]
67
89
  versions = "(" + versions.join(", ") + ")"
68
- ui.plain(format(" %-30s%-50s", plugin_name, versions))
90
+ ui.plain_line(format(" %-30s%-50s", plugin_name, versions))
69
91
  end
70
92
  ui.line
71
- ui.plain(" #{search_results.count} plugin(s) found")
93
+ ui.plain_line(" #{search_results.count} plugin(s) found")
72
94
  puts
73
95
 
74
96
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR if search_results.empty?
@@ -118,14 +140,14 @@ module InspecPlugins
118
140
  begin
119
141
  installer.update(plugin_name)
120
142
  rescue Inspec::Plugin::V2::UpdateError => ex
121
- ui.plain("#{ui.red('Update error:')} #{ex.message} - update failed")
143
+ ui.plain_line("#{ui.red("Update error:", print: false)} #{ex.message} - update failed")
122
144
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
123
145
  end
124
146
  post_update_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
125
147
  new_version = (post_update_versions - pre_update_versions).first
126
148
 
127
149
  ui.bold(plugin_name + " plugin, version #{old_version} -> " \
128
- "#{new_version}, updated from rubygems.org")
150
+ "#{new_version}, updated from rubygems.org\n")
129
151
  end
130
152
 
131
153
  #--------------------------
@@ -144,7 +166,7 @@ module InspecPlugins
144
166
  def uninstall(plugin_name)
145
167
  status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym]
146
168
  unless status
147
- ui.plain("#{ui.red('No such plugin installed:')} #{plugin_name} is not " \
169
+ ui.plain_line("#{ui.red("No such plugin installed:", print: false)} #{plugin_name} is not " \
148
170
  "installed - uninstall failed")
149
171
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
150
172
  end
@@ -157,11 +179,12 @@ module InspecPlugins
157
179
 
158
180
  if status.installation_type == :path
159
181
  ui.bold(plugin_name + " path-based plugin install has been " \
160
- "uninstalled")
182
+ "uninstalled\n")
161
183
  else
162
184
  ui.bold(plugin_name + " plugin, version #{old_version}, has " \
163
- "been uninstalled")
185
+ "been uninstalled\n")
164
186
  end
187
+
165
188
  ui.exit Inspec::UI::EXIT_NORMAL
166
189
  end
167
190
 
@@ -174,7 +197,7 @@ module InspecPlugins
174
197
 
175
198
  def install_from_gemfile(gem_file)
176
199
  unless File.exist? gem_file
177
- ui.red("No such plugin gem file #{gem_file} - installation failed.")
200
+ ui.red("No such plugin gem file #{gem_file} - installation failed.\n")
178
201
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
179
202
  end
180
203
 
@@ -186,13 +209,13 @@ module InspecPlugins
186
209
  installer.install(plugin_name, gem_file: gem_file)
187
210
 
188
211
  ui.bold("#{plugin_name} plugin, version #{version}, installed from " \
189
- "local .gem file")
212
+ "local .gem file\n")
190
213
  ui.exit Inspec::UI::EXIT_NORMAL
191
214
  end
192
215
 
193
216
  def install_from_path(path)
194
217
  unless File.exist? path
195
- ui.red("No such source code path #{path} - installation failed.")
218
+ ui.red("No such source code path #{path} - installation failed.\n")
196
219
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
197
220
  end
198
221
 
@@ -209,7 +232,7 @@ module InspecPlugins
209
232
  if registry.known_plugin?(plugin_name.to_sym)
210
233
  ui.red("Plugin already installed - #{plugin_name} - Use '#{EXEC_NAME} " \
211
234
  "plugin list' to see previously installed plugin - " \
212
- "installation failed.")
235
+ "installation failed.\n")
213
236
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
214
237
  end
215
238
 
@@ -223,7 +246,7 @@ module InspecPlugins
223
246
  installer.install(plugin_name, path: entry_point)
224
247
 
225
248
  ui.bold("#{plugin_name} plugin installed via source path reference, " \
226
- "resolved to entry point #{entry_point}")
249
+ "resolved to entry point #{entry_point}\n")
227
250
  ui.exit Inspec::UI::EXIT_NORMAL
228
251
  end
229
252
 
@@ -288,7 +311,7 @@ module InspecPlugins
288
311
  # Give up.
289
312
  ui.red("Unrecognizable plugin structure - #{parts[2]} - When " \
290
313
  "installing from a path, please provide the path of the " \
291
- "entry point file - installation failed.")
314
+ "entry point file - installation failed.\n")
292
315
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
293
316
  end
294
317
 
@@ -299,8 +322,8 @@ module InspecPlugins
299
322
  rescue LoadError => ex
300
323
  ui.red("Plugin contains errors - #{plugin_name} - Encountered " \
301
324
  "errors while trying to test load the plugin entry point, " \
302
- "resolved to #{entry_point} - installation failed")
303
- ui.plain ex.message
325
+ "resolved to #{entry_point} - installation failed\n")
326
+ ui.plain_line ex.message
304
327
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
305
328
  end
306
329
 
@@ -313,7 +336,7 @@ module InspecPlugins
313
336
  ui.red("Does not appear to be a plugin - #{plugin_name} - After " \
314
337
  "probe-loading the supposed plugin, it did not register " \
315
338
  "itself to Train. Ensure something inherits from " \
316
- "'Train.plugin(1)' - installation failed.")
339
+ "'Train.plugin(1)' - installation failed.\n")
317
340
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
318
341
  end
319
342
  else
@@ -321,7 +344,7 @@ module InspecPlugins
321
344
  ui.red("Does not appear to be a plugin - #{plugin_name} - After " \
322
345
  "probe-loading the supposed plugin, it did not register " \
323
346
  "itself to InSpec. Ensure something inherits from " \
324
- "'Inspec.plugin(2)' - installation failed.")
347
+ "'Inspec.plugin(2)' - installation failed.\n")
325
348
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
326
349
  end
327
350
  end
@@ -343,7 +366,7 @@ module InspecPlugins
343
366
  new_version = (post_installed_versions - pre_installed_versions).first
344
367
 
345
368
  ui.bold("#{plugin_name} plugin, version #{new_version}, installed " \
346
- "from rubygems.org")
369
+ "from rubygems.org\n")
347
370
  ui.exit Inspec::UI::EXIT_NORMAL
348
371
  end
349
372
 
@@ -367,16 +390,16 @@ module InspecPlugins
367
390
  what_we_would_install_is_already_installed = pre_installed_versions.include?(requested_version)
368
391
  if what_we_would_install_is_already_installed && they_explicitly_asked_for_a_version
369
392
  ui.red("Plugin already installed at requested version - plugin " \
370
- "#{plugin_name} #{requested_version} - refusing to install.")
393
+ "#{plugin_name} #{requested_version} - refusing to install.\n")
371
394
  elsif what_we_would_install_is_already_installed && !they_explicitly_asked_for_a_version
372
395
  ui.red("Plugin already installed at latest version - plugin " \
373
- "#{plugin_name} #{requested_version} - refusing to install.")
396
+ "#{plugin_name} #{requested_version} - refusing to install.\n")
374
397
  else
375
398
  # There are existing versions installed, but none of them are what was requested
376
399
  ui.red("Update required - plugin #{plugin_name}, requested " \
377
400
  "#{requested_version}, have " \
378
- "#{pre_installed_versions.join(', ')}; use `inspec " \
379
- "plugin update` - refusing to install.")
401
+ "#{pre_installed_versions.join(", ")}; use `inspec " \
402
+ "plugin update` - refusing to install.\n")
380
403
  end
381
404
 
382
405
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
@@ -387,11 +410,11 @@ module InspecPlugins
387
410
  installer.install(plugin_name, version: options[:version])
388
411
  rescue Inspec::Plugin::V2::PluginExcludedError => ex
389
412
  ui.red("Plugin on Exclusion List - #{plugin_name} is listed as an " \
390
- "incompatible gem - refusing to install.")
391
- ui.plain("Rationale: #{ex.details.rationale}")
392
- ui.plain("Exclusion list location: " +
413
+ "incompatible gem - refusing to install.\n")
414
+ ui.plain_line("Rationale: #{ex.details.rationale}")
415
+ ui.plain_line("Exclusion list location: " +
393
416
  File.join(Inspec.src_root, "etc", "plugin_filters.json"))
394
- ui.plain("If you disagree with this determination, please accept " \
417
+ ui.plain_line("If you disagree with this determination, please accept " \
395
418
  "our apologies for the misunderstanding, and open an issue " \
396
419
  "at https://github.com/inspec/inspec/issues/new")
397
420
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
@@ -401,13 +424,13 @@ module InspecPlugins
401
424
  results = installer.search(plugin_name, exact: true)
402
425
  if results.empty?
403
426
  ui.red("No such plugin gem #{plugin_name} could be found on " \
404
- "rubygems.org - installation failed.")
427
+ "rubygems.org - installation failed.\n")
405
428
  elsif options[:version] && !results[plugin_name].include?(options[:version])
406
429
  ui.red("No such version - #{plugin_name} exists, but no such " \
407
430
  "version #{options[:version]} found on rubygems.org - " \
408
- "installation failed.")
431
+ "installation failed.\n")
409
432
  else
410
- ui.red("Unknown error occured - installation failed.")
433
+ ui.red("Unknown error occured - installation failed.\n")
411
434
  end
412
435
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
413
436
  end
@@ -420,10 +443,10 @@ module InspecPlugins
420
443
  # Check for path install
421
444
  status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym]
422
445
  if !status
423
- ui.plain("#{ui.red('No such plugin installed:')} #{plugin_name} - update failed")
446
+ ui.plain_line("#{ui.red("No such plugin installed:", print: false)} #{plugin_name} - update failed")
424
447
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
425
448
  elsif status.installation_type == :path
426
- ui.plain("#{ui.red('Cannot update path-based install:')} " \
449
+ ui.plain_line("#{ui.red("Cannot update path-based install:", print: false)} " \
427
450
  "#{plugin_name} is installed via path reference; " \
428
451
  "use `inspec plugin uninstall` to remove - refusing to" \
429
452
  "update")
@@ -436,7 +459,7 @@ module InspecPlugins
436
459
  latest_version = latest_version[plugin_name]&.last
437
460
 
438
461
  if pre_update_versions.include?(latest_version)
439
- ui.plain("#{ui.red('Already installed at latest version:')} " \
462
+ ui.plain_line("#{ui.red("Already installed at latest version:", print: false)} " \
440
463
  "#{plugin_name} is at #{latest_version}, which the " \
441
464
  "latest - refusing to update")
442
465
  ui.exit Inspec::UI::EXIT_PLUGIN_ERROR
@@ -458,7 +481,7 @@ module InspecPlugins
458
481
  unless plugin_name =~ /^(inspec|train)-/
459
482
  ui.red("Invalid plugin name - #{plugin_name} - All inspec " \
460
483
  "plugins must begin with either 'inspec-' or 'train-' " \
461
- "- #{action} failed.")
484
+ "- #{action} failed.\n")
462
485
  ui.exit Inspec::UI::EXIT_USAGE_ERROR
463
486
  end
464
487
  end
@@ -467,17 +490,29 @@ module InspecPlugins
467
490
  case status.installation_type
468
491
  when :core, :bundle
469
492
  Inspec::VERSION
470
- when :gem
471
- # TODO: this is naive, and assumes the latest version is the one that will be used. Logged on #3317
472
- # In fact, the logic to determine "what version would be used" belongs in the Loader.
473
- Inspec::Plugin::V2::Loader.list_installed_plugin_gems
474
- .select { |spec| spec.name == status.name.to_s }
475
- .sort_by(&:version)
476
- .last.version
493
+ when :user_gem, :system_gem
494
+ if status.version.nil?
495
+ "(unknown)"
496
+ elsif status.version =~ /^\d+\.\d+\.\d+$/
497
+ status.version
498
+ else
499
+ # Assume it is a version constraint string and try to resolve
500
+ # TODO: this is naive, and assumes the latest version is the one that will be used. Logged on #3317
501
+ # In fact, the logic to determine "what version would be used" belongs in the Loader.
502
+ plugin_name = status.name.to_s
503
+ Inspec::Plugin::V2::Loader.list_installed_plugin_gems
504
+ .select { |spec| spec.name == plugin_name }
505
+ .sort_by(&:version)
506
+ .last.version
507
+ end
477
508
  when :path
478
509
  "src"
479
510
  end
480
511
  end
512
+
513
+ def make_pretty_install_type(status)
514
+ INSTALL_TYPE_LABELS[status.installation_type]
515
+ end
481
516
  end
482
517
  end
483
518
  end
@@ -40,25 +40,27 @@ module SourceReaders
40
40
  raise "Unable to parse #{metadata_source}: #{e.class} -- #{e.message}"
41
41
  end
42
42
 
43
+ def find_all(regexp)
44
+ @target.files.grep(regexp)
45
+ end
46
+
47
+ def load_all(regexp)
48
+ find_all(regexp)
49
+ .map { |path| file = @target.read(path); [path, file] if file }
50
+ .compact
51
+ .to_h
52
+ end
53
+
43
54
  def load_tests
44
- tests = @target.files.find_all do |path|
45
- path.start_with?("controls") && path.end_with?(".rb")
46
- end
47
- Hash[tests.map { |x| [x, @target.read(x)] }.delete_if { |_file, contents| contents.nil? }]
55
+ load_all(%r{^controls/.*\.rb$})
48
56
  end
49
57
 
50
58
  def load_libs
51
- tests = @target.files.find_all do |path|
52
- path.start_with?("libraries") && path.end_with?(".rb")
53
- end
54
- Hash[tests.map { |x| [x, @target.read(x)] }.delete_if { |_file, contents| contents.nil? }]
59
+ load_all(%r{^libraries/.*\.rb$})
55
60
  end
56
61
 
57
62
  def load_data_files
58
- files = @target.files.find_all do |path|
59
- path.start_with?("files" + File::SEPARATOR)
60
- end
61
- Hash[files.map { |x| [x, @target.read(x)] }.delete_if { |_file, contents| contents.nil? }]
63
+ load_all(%r{^files/})
62
64
  end
63
65
  end
64
66
  end
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.12.0
4
+ version: 4.16.0
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-08-15 00:00:00.000000000 Z
11
+ date: 2019-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train-core
@@ -536,6 +536,7 @@ files:
536
536
  - lib/inspec/resources/pip.rb
537
537
  - lib/inspec/resources/platform.rb
538
538
  - lib/inspec/resources/port.rb
539
+ - lib/inspec/resources/postfix_conf.rb
539
540
  - lib/inspec/resources/postgres.rb
540
541
  - lib/inspec/resources/postgres_conf.rb
541
542
  - lib/inspec/resources/postgres_hba_conf.rb