inspec-core 4.18.108 → 4.20.2

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: 07ce3c9c460909bfb27d25c98f2911d44d3319e287e1befb072b0a8d09e22f3d
4
- data.tar.gz: 7158a199c122637211d0a4b9e2e2795b4ec100709a16a1a38be6195d904eaf7d
3
+ metadata.gz: 2395ce22d38edca9eaac2fad00c1fc61f3fb95a154618540d23bc2a1e6ce0fcd
4
+ data.tar.gz: 4ebdbe5526025408e729b52e0614b43fe89f70b0faae5eeecc8c23db8f0f1ff9
5
5
  SHA512:
6
- metadata.gz: e51b5023ca17e20878508e7bb9b7f1abcd8c603676bba0f0a38644d7bd33165700f6fbe251ee61e15beff9c93304b55888bf346ec4228275d57f2201f2dbdcac
7
- data.tar.gz: 473ce96487948f10e81af956690fcb7ef1ca6d0924809d15099ff0919a01a2cc15b0c87ca470a0fb277e8d4086384c281ab4f3b86a6fb61d7b2923be4def5d3f
6
+ metadata.gz: f2b21bfbc96b4830edf20f1d48493257845bfbeb52137141269371066c47359092d3ed280cc64e9358b3587d0ddb0c54a643cafd730d57ad2d7ba2331d35b33a
7
+ data.tar.gz: 84bc11b59621836d20c045efcdee955cc97c605e5902fafaa79c8756dd44f66334731256c6ba4273484d645484f374b1ec2daf471a6dc56805ea819ab2685f38
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gem "inspec", path: "."
9
9
  # in it in order to package the executable. Hence the odd backwards dependency.
10
10
  gem "inspec-bin", path: "./inspec-bin"
11
11
 
12
- gem "ffi", ">= 1.9.14"
12
+ gem "ffi", [">= 1.9.14", "< 1.13"] # 1.13 does not work on Windows: https://github.com/ffi/ffi/issues/784
13
13
 
14
14
  group :omnibus do
15
15
  gem "rb-readline"
data/README.md CHANGED
@@ -141,7 +141,7 @@ That requires [bundler](http://bundler.io/):
141
141
 
142
142
  ```bash
143
143
  bundle install
144
- bundle exec bin/inspec help
144
+ bundle exec inspec help
145
145
  ```
146
146
 
147
147
  To install it as a gem locally, run:
@@ -135,8 +135,12 @@ module Inspec
135
135
  option :reporter, type: :array,
136
136
  banner: "one two:/output/file/path",
137
137
  desc: "Enable one or more output reporters: cli, documentation, html, progress, json, json-min, json-rspec, junit, yaml"
138
+ option :reporter_message_truncation, type: :string,
139
+ desc: "Number of characters to truncate failure messages in report data to (default: no truncation)"
140
+ option :reporter_backtrace_inclusion, type: :boolean,
141
+ desc: "Include a code backtrace in report data (default: true)"
138
142
  option :input, type: :array, banner: "name1=value1 name2=value2",
139
- desc: "Specify one or more inputs directly on the command line, as --input NAME=VALUE"
143
+ desc: "Specify one or more inputs directly on the command line, as --input NAME=VALUE. Accepts single-quoted YAML and JSON structures."
140
144
  option :input_file, type: :array,
141
145
  desc: "Load one or more input files, a YAML file with values for the profile to use"
142
146
  option :waiver_file, type: :array,
@@ -151,6 +155,9 @@ module Inspec
151
155
  desc: "Show progress while executing tests."
152
156
  option :distinct_exit, type: :boolean, default: true,
153
157
  desc: "Exit with code 101 if any tests fail, and 100 if any are skipped (default). If disabled, exit 0 on skips and 1 for failures."
158
+ option :silence_deprecations, type: :array,
159
+ banner: "[all]|[GROUP GROUP...]",
160
+ desc: "Suppress deprecation warnings. See install_dir/etc/deprecations.json for list of GROUPs or use 'all'."
154
161
  end
155
162
 
156
163
  def self.format_platform_info(params: {}, indent: 0, color: 39)
@@ -4,6 +4,7 @@ require "inspec/utils/deprecation/deprecator"
4
4
  require "inspec/dist"
5
5
  require "inspec/backend"
6
6
  require "inspec/dependencies/cache"
7
+ require "inspec/utils/json_profile_summary"
7
8
 
8
9
  module Inspec # TODO: move this somewhere "better"?
9
10
  autoload :BaseCLI, "inspec/base_cli"
@@ -77,24 +78,13 @@ class Inspec::InspecCLI < Inspec::BaseCLI
77
78
  o[:vendor_cache] = Inspec::Cache.new(o[:vendor_cache])
78
79
 
79
80
  profile = Inspec::Profile.for_target(target, o)
80
- info = profile.info
81
- # add in inspec version
82
- info[:generator] = {
83
- name: "inspec",
84
- version: Inspec::VERSION,
85
- }
86
81
  dst = o[:output].to_s
87
- if dst.empty?
88
- puts JSON.dump(info)
89
- else
90
- if File.exist? dst
91
- puts "----> updating #{dst}"
92
- else
93
- puts "----> creating #{dst}"
94
- end
95
- fdst = File.expand_path(dst)
96
- File.write(fdst, JSON.dump(info))
97
- end
82
+
83
+ # Write JSON
84
+ Inspec::Utils::JsonProfileSummary.produce_json(
85
+ info: profile.info,
86
+ write_path: dst
87
+ )
98
88
  rescue StandardError => e
99
89
  pretty_handle_exception(e)
100
90
  end
@@ -328,21 +328,36 @@ module Inspec
328
328
  def validate_reporters!(reporters)
329
329
  return if reporters.nil?
330
330
 
331
- # TODO: move this into a reporter plugin type system
332
- valid_types = %w{
333
- automate
334
- cli
331
+ # These "reporters" are actually RSpec Formatters.
332
+ # json-rspec is our alias for RSpec's json formatter.
333
+ rspec_built_in_formatters = %w{
335
334
  documentation
336
335
  html
336
+ json-rspec
337
+ progress
338
+ }
339
+
340
+ # These are true reporters, but have not been migrated to be plugins yet.
341
+ # Tracked on https://github.com/inspec/inspec/issues/3667
342
+ inspec_reporters_that_are_not_yet_plugins = %w{
343
+ automate
344
+ cli
337
345
  json
338
346
  json-automate
339
347
  json-min
340
- json-rspec
341
348
  junit
342
- progress
343
349
  yaml
344
350
  }
345
351
 
352
+ # Additional reporters may be loaded via plugins. They will have already been detected at
353
+ # this point (see v2_loader.load_all in cli.rb) but they may not (and need not) be
354
+ # activated at this point. We only care about their existance and their name, for validation's sake.
355
+ plugin_reporters = Inspec::Plugin::V2::Registry.instance\
356
+ .find_activators(plugin_type: :reporter)\
357
+ .map(&:activator_name).map(&:to_s)
358
+
359
+ valid_types = rspec_built_in_formatters + inspec_reporters_that_are_not_yet_plugins + plugin_reporters
360
+
346
361
  reporters.each do |reporter_name, reporter_config|
347
362
  raise NotImplementedError, "'#{reporter_name}' is not a valid reporter type." unless valid_types.include?(reporter_name)
348
363
 
@@ -360,6 +375,13 @@ module Inspec
360
375
  end
361
376
 
362
377
  raise ArgumentError, "The option --reporter can only have a single report outputting to stdout." if stdout_reporters > 1
378
+
379
+ # reporter_message_truncation needs to either be the string "ALL", an Integer, or a string representing an integer
380
+ if (truncation = @merged_options["reporter_message_truncation"])
381
+ unless truncation == "ALL" || truncation.is_a?(Integer) || truncation.to_i.to_s == truncation
382
+ raise ArgumentError, "reporter_message_truncation is set to #{truncation}. It must be set to an integer value or ALL to indicate no truncation."
383
+ end
384
+ end
363
385
  end
364
386
 
365
387
  def validate_plugins!
@@ -99,7 +99,7 @@ module Inspec::Fetcher
99
99
  def cache_key
100
100
  return resolved_ref unless @relative_path
101
101
 
102
- OpenSSL::Digest::SHA256.hexdigest(resolved_ref + @relative_path)
102
+ OpenSSL::Digest.hexdigest("SHA256", resolved_ref + @relative_path)
103
103
  end
104
104
 
105
105
  def archive_path
@@ -104,7 +104,7 @@ module Inspec::Fetcher
104
104
  return @archive_shasum if @archive_shasum
105
105
  raise(Inspec::FetcherFailure, "Profile dependency local path '#{target}' does not exist") unless File.exist?(target)
106
106
 
107
- @archive_shasum = OpenSSL::Digest::SHA256.digest(File.read(target)).unpack("H*")[0]
107
+ @archive_shasum = OpenSSL::Digest.digest("SHA256", File.read(target)).unpack("H*")[0]
108
108
  end
109
109
 
110
110
  def resolved_source
@@ -127,7 +127,7 @@ module Inspec::Fetcher
127
127
  end
128
128
 
129
129
  def sha256
130
- @archive_shasum ||= OpenSSL::Digest::SHA256.digest(File.read(@archive_path || temp_archive_path)).unpack("H*")[0]
130
+ @archive_shasum ||= OpenSSL::Digest.digest("SHA256", File.read(@archive_path || temp_archive_path)).unpack("H*")[0]
131
131
  end
132
132
 
133
133
  def file_type_from_remote(remote)
@@ -98,15 +98,15 @@ module Inspec
98
98
  # not been assigned a value. This allows a user to explicitly assign nil
99
99
  # to an input.
100
100
  class NO_VALUE_SET # rubocop: disable Naming/ClassAndModuleCamelCase
101
- def initialize(name)
101
+ def initialize(name, warn_on_create = true)
102
102
  @name = name
103
103
 
104
104
  # output warn message if we are in a exec call
105
- if Inspec::BaseCLI.inspec_cli_command == :exec
105
+ if warn_on_create && Inspec::BaseCLI.inspec_cli_command == :exec
106
106
  Inspec::Log.warn(
107
107
  "Input '#{@name}' does not have a value. "\
108
- "Use --input-file to provide a value for '#{@name}' or specify a "\
109
- "value with `attribute('#{@name}', value: 'somevalue', ...)`."
108
+ "Use --input-file or --input to provide a value for '#{@name}' or specify a "\
109
+ "value with `input('#{@name}', value: 'somevalue', ...)`."
110
110
  )
111
111
  end
112
112
  end
@@ -277,7 +277,7 @@ module Inspec
277
277
  end
278
278
 
279
279
  # Determine the current winning value, but don't validate it
280
- def current_value
280
+ def current_value(warn_on_missing = true)
281
281
  # Examine the events to determine highest-priority value. Tie-break
282
282
  # by using the last one set.
283
283
  events_that_set_a_value = events.select(&:value_has_been_set?)
@@ -287,7 +287,7 @@ module Inspec
287
287
 
288
288
  if winning_event.nil?
289
289
  # No value has been set - return special no value object
290
- NO_VALUE_SET.new(name)
290
+ NO_VALUE_SET.new(name, warn_on_missing)
291
291
  else
292
292
  winning_event.value # May still be nil
293
293
  end
@@ -315,7 +315,7 @@ module Inspec
315
315
  end
316
316
 
317
317
  def has_value?
318
- !current_value.is_a? NO_VALUE_SET
318
+ !current_value(false).is_a? NO_VALUE_SET
319
319
  end
320
320
 
321
321
  def to_hash
@@ -348,7 +348,7 @@ module Inspec
348
348
  # skip if we are not doing an exec call (archive/vendor/check)
349
349
  return unless Inspec::BaseCLI.inspec_cli_command == :exec
350
350
 
351
- proposed_value = current_value
351
+ proposed_value = current_value(false)
352
352
  if proposed_value.nil? || proposed_value.is_a?(NO_VALUE_SET)
353
353
  error = Inspec::Input::RequiredError.new
354
354
  error.input_name = name
@@ -363,7 +363,7 @@ module Inspec
363
363
  type_req = type
364
364
  return if type_req == "Any"
365
365
 
366
- proposed_value = current_value
366
+ proposed_value = current_value(false)
367
367
 
368
368
  invalid_type = false
369
369
  if type_req == "Regexp"
@@ -166,8 +166,9 @@ module Inspec
166
166
  end
167
167
  end
168
168
  input_name, input_value = pair.split("=")
169
+ input_value = parse_cli_input_value(input_name, input_value)
169
170
  evt = Inspec::Input::Event.new(
170
- value: input_value.chomp(","), # Trim trailing comma if any
171
+ value: input_value,
171
172
  provider: :cli,
172
173
  priority: 50
173
174
  )
@@ -175,6 +176,37 @@ module Inspec
175
176
  end
176
177
  end
177
178
 
179
+ # Remove trailing commas, resolve type.
180
+ def parse_cli_input_value(input_name, given_value)
181
+ value = given_value.chomp(",") # Trim trailing comma if any
182
+ case value
183
+ when /^true|false$/i
184
+ value = !!(value =~ /true/i)
185
+ when /^-?\d+$/
186
+ value = value.to_i
187
+ when /^-?\d+\.\d+$/
188
+ value = value.to_f
189
+ when /^(\[|\{).*(\]|\})$/
190
+ # Look for complex values and try to parse them.
191
+ require "yaml"
192
+ begin
193
+ value = YAML.load(value)
194
+ rescue Psych::SyntaxError => yaml_error
195
+ # It could be that we just tried to run JSON through the YAML parser.
196
+ require "json"
197
+ begin
198
+ value = JSON.parse(value)
199
+ rescue JSON::ParserError => json_error
200
+ msg = "Unparseable value '#{value}' for --input #{input_name}.\n"
201
+ msg += "When treated as YAML, error: #{yaml_error.message}\n"
202
+ msg += "When treated as JSON, error: #{json_error.message}"
203
+ Inspec::Log.warn msg
204
+ end
205
+ end
206
+ end
207
+ value
208
+ end
209
+
178
210
  def bind_inputs_from_runner_api(profile_name, input_hash)
179
211
  # TODO: move this into a core plugin
180
212
 
@@ -0,0 +1,68 @@
1
+ require_relative "../../../run_data"
2
+
3
+ module Inspec::Plugin::V2::PluginType
4
+ class Reporter < Inspec::Plugin::V2::PluginBase
5
+ register_plugin_type(:reporter)
6
+
7
+ attr_reader :run_data
8
+
9
+ def initialize(config)
10
+ @config = config
11
+
12
+ # Trim the run_data while still a Hash; if it is huge, this
13
+ # saves on conversion time
14
+ @run_data = config[:run_data] || {}
15
+ apply_report_resize_options
16
+
17
+ unless Inspec::RunData.compatible_schema?(self.class.run_data_schema_constraints)
18
+ # Best we can do is warn here, the InSpec run has finished
19
+ # TODO: one day, perhaps switch RunData implementations to try to satisfy constraints?
20
+ Inspec::Log.warn "Reporter does not support RunData API (#{Inspec::RunData::SCHEMA_VERSION}), Reporter constraints: '#{self.class.run_data_schema_constraints}'"
21
+ end
22
+ # Convert to RunData object for consumption by Reporter
23
+ @run_data = Inspec::RunData.new(@run_data)
24
+ @output = ""
25
+ end
26
+
27
+ # This is a temporary duplication of code from lib/inspec/reporters/base.rb
28
+ # To be DRY'd up once the core reporters become plugins...
29
+ # Apply options such as message truncation and removal of backtraces
30
+ def apply_report_resize_options
31
+ runtime_config = Inspec::Config.cached.respond_to?(:final_options) ? Inspec::Config.cached.final_options : {}
32
+
33
+ message_truncation = runtime_config[:reporter_message_truncation] || "ALL"
34
+ trunc = message_truncation == "ALL" ? -1 : message_truncation.to_i
35
+ include_backtrace = runtime_config[:reporter_backtrace_inclusion].nil? ? true : runtime_config[:reporter_backtrace_inclusion]
36
+
37
+ @run_data[:profiles]&.each do |p|
38
+ p[:controls].each do |c|
39
+ c[:results]&.map! do |r|
40
+ r.delete(:backtrace) unless include_backtrace
41
+ if r.key?(:message) && r[:message] != "" && trunc > -1
42
+ r[:message] = r[:message][0...trunc] + "[Truncated to #{trunc} characters]"
43
+ end
44
+ r
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def output(str, newline = true)
51
+ @output << str
52
+ @output << "\n" if newline
53
+ end
54
+
55
+ def rendered_output
56
+ @output
57
+ end
58
+
59
+ # each reporter must implement #render
60
+ def render
61
+ raise NotImplementedError, "#{self.class} must implement a `#render` method to format its output."
62
+ end
63
+
64
+ def self.run_data_schema_constraints
65
+ raise NotImplementedError, "#{self.class} must implement a `run_data_schema_constraints` class method to declare its compatibiltity with the RunData API."
66
+ end
67
+ end
68
+ end
@@ -12,6 +12,7 @@ require "inspec/method_source"
12
12
  require "inspec/dependencies/cache"
13
13
  require "inspec/dependencies/lockfile"
14
14
  require "inspec/dependencies/dependency_set"
15
+ require "inspec/utils/json_profile_summary"
15
16
 
16
17
  module Inspec
17
18
  class Profile
@@ -465,28 +466,39 @@ module Inspec
465
466
  end
466
467
 
467
468
  # remove existing archive
468
- File.delete(dst) if dst.exist?
469
+ FileUtils.rm_f(dst) if dst.exist?
469
470
  @logger.info "Generate archive #{dst}."
470
471
 
471
472
  # filter files that should not be part of the profile
472
473
  # TODO ignore all .files, but add the files to debug output
473
474
 
475
+ # Generate temporary inspec.json for archive
476
+ Inspec::Utils::JsonProfileSummary.produce_json(
477
+ info: info,
478
+ write_path: "#{root_path}inspec.json",
479
+ suppress_output: true
480
+ )
481
+
474
482
  # display all files that will be part of the archive
475
483
  @logger.debug "Add the following files to archive:"
476
484
  files.each { |f| @logger.debug " " + f }
485
+ @logger.debug " inspec.json"
477
486
 
478
487
  if opts[:zip]
479
488
  # generate zip archive
480
489
  require "inspec/archive/zip"
481
490
  zag = Inspec::Archive::ZipArchiveGenerator.new
482
- zag.archive(root_path, files, dst)
491
+ zag.archive(root_path, files.push("inspec.json"), dst)
483
492
  else
484
493
  # generate tar archive
485
494
  require "inspec/archive/tar"
486
495
  tag = Inspec::Archive::TarArchiveGenerator.new
487
- tag.archive(root_path, files, dst)
496
+ tag.archive(root_path, files.push("inspec.json"), dst)
488
497
  end
489
498
 
499
+ # Cleanup
500
+ FileUtils.rm_f("#{root_path}inspec.json")
501
+
490
502
  @logger.info "Finished archive generation."
491
503
  true
492
504
  end
@@ -559,7 +571,7 @@ module Inspec
559
571
  # get all dependency checksums
560
572
  deps = Hash[locked_dependencies.list.map { |k, v| [k, v.profile.sha256] }]
561
573
 
562
- res = OpenSSL::Digest::SHA256.new
574
+ res = OpenSSL::Digest.new("SHA256")
563
575
  files = source_reader.tests.to_a + source_reader.libraries.to_a +
564
576
  source_reader.data_files.to_a +
565
577
  [["inspec.yml", source_reader.metadata.content]] +
@@ -30,7 +30,10 @@ module Inspec::Reporters
30
30
  when "yaml"
31
31
  reporter = Inspec::Reporters::Yaml.new(config)
32
32
  else
33
- raise NotImplementedError, "'#{name}' is not a valid reporter type."
33
+ # If we made it here, it must be a plugin, and we know it exists (because we validated it in config.rb)
34
+ activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
35
+ activator.activate!
36
+ reporter = activator.implementation_class.new(config)
34
37
  end
35
38
 
36
39
  # optional send_report method on reporter
@@ -5,9 +5,31 @@ module Inspec::Reporters
5
5
  def initialize(config)
6
6
  @config = config
7
7
  @run_data = config[:run_data]
8
+ apply_report_resize_options unless @run_data.nil?
8
9
  @output = ""
9
10
  end
10
11
 
12
+ # Apply options such as message truncation and removal of backtraces
13
+ def apply_report_resize_options
14
+ runtime_config = Inspec::Config.cached.respond_to?(:final_options) ? Inspec::Config.cached.final_options : {}
15
+
16
+ message_truncation = runtime_config[:reporter_message_truncation] || "ALL"
17
+ trunc = message_truncation == "ALL" ? -1 : message_truncation.to_i
18
+ include_backtrace = runtime_config[:reporter_backtrace_inclusion].nil? ? true : runtime_config[:reporter_backtrace_inclusion]
19
+
20
+ @run_data[:profiles]&.each do |p|
21
+ p[:controls].each do |c|
22
+ c[:results]&.map! do |r|
23
+ r.delete(:backtrace) unless include_backtrace
24
+ if r.key?(:message) && r[:message] != "" && trunc > -1
25
+ r[:message] = r[:message][0...trunc] + "[Truncated to #{trunc} characters]"
26
+ end
27
+ r
28
+ end
29
+ end
30
+ end
31
+ end
32
+
11
33
  def output(str, newline = true)
12
34
  @output << str
13
35
  @output << "\n" if newline
@@ -59,7 +59,7 @@ module Inspec::Resources
59
59
  def fingerprint
60
60
  return if @cert.nil?
61
61
 
62
- OpenSSL::Digest::SHA1.new(@cert.to_der).to_s
62
+ OpenSSL::Digest.new("SHA1", @cert.to_der).to_s
63
63
  end
64
64
 
65
65
  def serial
@@ -332,7 +332,7 @@ module Inspec
332
332
  input_name = @__rule_id # TODO: control ID slugging
333
333
  registry = Inspec::InputRegistry.instance
334
334
  input = registry.inputs_by_profile.dig(__profile_id, input_name)
335
- return unless input
335
+ return unless input && input.has_value? && input.value.is_a?(Hash)
336
336
 
337
337
  # An InSpec Input is a datastructure that tracks a profile parameter
338
338
  # over time. Its value can be set by many sources, and it keeps a
@@ -353,9 +353,13 @@ module Inspec
353
353
  # if so, is it in the future?
354
354
  expiry = __waiver_data["expiration_date"]
355
355
  if expiry
356
- if expiry.is_a?(Date)
357
- # It appears that yaml.rb automagically parses dates for us
358
- if expiry < Date.today # If the waiver expired, return - no skip applied
356
+ # YAML will automagically give us a Date or a Time.
357
+ # If transcoding YAML between languages (e.g. Go) the date might have also ended up as a String.
358
+ # A string that does not represent a valid time results in the date 0000-01-01.
359
+ if [Date, Time].include?(expiry.class) || (expiry.is_a?(String) && Time.new(expiry).year != 0)
360
+ expiry = expiry.to_time if expiry.is_a? Date
361
+ expiry = Time.new(expiry) if expiry.is_a? String
362
+ if expiry < Time.now # If the waiver expired, return - no skip applied
359
363
  __waiver_data["message"] = "Waiver expired on #{expiry}, evaluating control normally"
360
364
  return
361
365
  end
@@ -0,0 +1,64 @@
1
+
2
+ module Inspec
3
+ module HashLikeStruct
4
+ def keys
5
+ members
6
+ end
7
+
8
+ def key?(item)
9
+ members.include?(item)
10
+ end
11
+ end
12
+
13
+ RunData = Struct.new(
14
+ :controls, # Array of Inspec::RunData::Control (flattened)
15
+ :other_checks,
16
+ :profiles, # Array of Inspec::RunData::Profile
17
+ :platform, # Inspec::RunData::Platform
18
+ :statistics, # Inspec::RunData::Statistics
19
+ :version # String
20
+ ) do
21
+ include HashLikeStruct
22
+ def initialize(raw_run_data)
23
+ self.controls = raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
24
+ self.profiles = raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
25
+ self.statistics = Inspec::RunData::Statistics.new(raw_run_data[:statistics])
26
+ self.platform = Inspec::RunData::Platform.new(raw_run_data[:platform])
27
+ self.version = raw_run_data[:version]
28
+ end
29
+ end
30
+
31
+ class RunData
32
+
33
+ # This is the data layout version of RunData.
34
+ # We plan to follow a data-oriented version of semver:
35
+ # patch: fixing a bug in the provenance or description of a data element, no key changes
36
+ # minor: adding new data elements
37
+ # major: deleting or renaming data elements
38
+ # Less than major version 1.0.0, the API is considered unstable.
39
+ # The current plan is to bump the major version to 1.0.0 when all of the existing
40
+ # core reporters have been migrated to plugins. It is probable that new data elements
41
+ # and new Hash compatibility behavior will be added during the core reporter plugin
42
+ # conversion process.
43
+ SCHEMA_VERSION = "0.1.0".freeze
44
+
45
+ def self.compatible_schema?(constraints)
46
+ reqs = Gem::Requirement.create(constraints)
47
+ reqs.satisfied_by?(Gem::Version.new(SCHEMA_VERSION))
48
+ end
49
+
50
+ Platform = Struct.new(
51
+ :name, :release, :target
52
+ ) do
53
+ include HashLikeStruct
54
+ def initialize(raw_plat_data)
55
+ %i{name release target}.each { |f| self[f] = raw_plat_data[f] || "" }
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ require_relative "run_data/result"
62
+ require_relative "run_data/control"
63
+ require_relative "run_data/profile"
64
+ require_relative "run_data/statistics"
@@ -0,0 +1,83 @@
1
+ module Inspec
2
+ class RunData
3
+ Control = Struct.new(
4
+ :code, # String
5
+ :desc, # String
6
+ :descriptions, # Hash with custom keys
7
+ :id, # String
8
+ :impact, # Float
9
+ :refs, # Complex local
10
+ :results, # complex standalone
11
+ :source_location, # Complex local
12
+ :tags, # Hash with custom keys
13
+ :title, # String
14
+ :waiver_data # Complex local
15
+ ) do
16
+ include HashLikeStruct
17
+ def initialize(raw_ctl_data)
18
+ self.refs = (raw_ctl_data[:refs] || []).map { |r| Inspec::RunData::Control::Ref.new(r) }
19
+ self.results = (raw_ctl_data[:results] || []).map { |r| Inspec::RunData::Result.new(r) }
20
+ self.source_location = Inspec::RunData::Control::SourceLocation.new(raw_ctl_data[:source_location] || {})
21
+ self.waiver_data = Inspec::RunData::Control::WaiverData.new(raw_ctl_data[:waiver_data] || {})
22
+
23
+ [
24
+ :code, # String
25
+ :desc, # String
26
+ :descriptions, # Hash with custom keys
27
+ :id, # String
28
+ :impact, # Float
29
+ :tags, # Hash with custom keys
30
+ :title, # String
31
+ ].each do |field|
32
+ self[field] = raw_ctl_data[field]
33
+ end
34
+ end
35
+ end
36
+
37
+ class Control
38
+ Ref = Struct.new(
39
+ :url, :ref
40
+ ) do
41
+ include HashLikeStruct
42
+ def initialize(raw_ref_data)
43
+ %i{url ref}.each { |f| self[f] = raw_ref_data[f] }
44
+ end
45
+ end
46
+
47
+ SourceLocation = Struct.new(
48
+ :line, :ref
49
+ ) do
50
+ include HashLikeStruct
51
+ def initialize(raw_sl_data)
52
+ %i{line ref}.each { |f| self[f] = raw_sl_data[f] }
53
+ end
54
+ end
55
+
56
+ # {
57
+ # "expiration_date"=>#<Date: 2077-06-01 ((2479821j,0s,0n),+0s,2299161j)>,
58
+ # "justification"=>"Lack of imagination",
59
+ # "run"=>false,
60
+ # "skipped_due_to_waiver"=>true,
61
+ # "message"=>""}
62
+ WaiverData = Struct.new(
63
+ :expiration_date,
64
+ :justification,
65
+ :run,
66
+ :skipped_due_to_waiver,
67
+ :message
68
+ ) do
69
+ include HashLikeStruct
70
+ def initialize(raw_wv_data)
71
+ # These have string keys in the raw data!
72
+ %i{
73
+ expiration_date
74
+ justification
75
+ run
76
+ skipped_due_to_waiver
77
+ message
78
+ }.each { |f| self[f] = raw_wv_data[f.to_s] }
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,109 @@
1
+ module Inspec
2
+ class RunData
3
+ Profile = Struct.new(
4
+ :controls, # complex standalone
5
+ :copyright,
6
+ :copyright_email,
7
+ :depends, # complex local
8
+ :groups, # complex local
9
+ :inputs, # complex local
10
+ :license,
11
+ :maintainer,
12
+ :name,
13
+ :sha256,
14
+ :status,
15
+ :summary,
16
+ :supports, # complex local
17
+ :parent_profile,
18
+ :skip_message,
19
+ :waiver_data, # Undocumented but used in JSON reporter - should not be?
20
+ :title,
21
+ :version
22
+ ) do
23
+ include HashLikeStruct
24
+ def initialize(raw_prof_data)
25
+ self.controls = (raw_prof_data[:controls] || []).map { |c| Inspec::RunData::Control.new(c) }
26
+ self.depends = (raw_prof_data[:depends] || []).map { |d| Inspec::RunData::Profile::Dependency.new(d) }
27
+ self.groups = (raw_prof_data[:groups] || []).map { |g| Inspec::RunData::Profile::Group.new(g) }
28
+ self.inputs = (raw_prof_data[:inputs] || []).map { |i| Inspec::RunData::Profile::Input.new(i) }
29
+ self.supports = (raw_prof_data[:supports] || []).map { |s| Inspec::RunData::Profile::Support.new(s) }
30
+
31
+ %i{
32
+ copyright
33
+ copyright_email
34
+ license
35
+ maintainer
36
+ name
37
+ sha256
38
+ status
39
+ summary
40
+ title
41
+ version
42
+ parent_profile
43
+ skip_message
44
+ waiver_data
45
+ }.each do |field|
46
+ self[field] = raw_prof_data[field]
47
+ end
48
+ end
49
+ end
50
+
51
+ class Profile
52
+ # Good candidate for keyword_init, but that is not in 2.4
53
+ Dependency = Struct.new(
54
+ :name, :path, :status, :skip_message, :git, :url, :compliance, :supermarket, :branch, :tag, :commit, :version, :relative_path
55
+ ) do
56
+ include HashLikeStruct
57
+ def initialize(raw_dep_data)
58
+ %i{name path status skip_message git url supermarket compliance branch tag commit version relative_path}.each { |f| self[f] = raw_dep_data[f] }
59
+ end
60
+ end
61
+
62
+ Support = Struct.new(
63
+ # snake case
64
+ :platform_family, :platform_name, :release, :platform
65
+ ) do
66
+ include HashLikeStruct
67
+ def initialize(raw_sup_data)
68
+ %i{release platform}.each { |f| self[f] = raw_sup_data[f] }
69
+ self.platform_family = raw_sup_data[:"platform-family"]
70
+ self.platform_name = raw_sup_data[:"platform-name"]
71
+ end
72
+ end
73
+
74
+ # Good candidate for keyword_init, but that is not in 2.4
75
+ Group = Struct.new(
76
+ :title, :controls, :id
77
+ ) do
78
+ include HashLikeStruct
79
+ def initialize(raw_grp_data)
80
+ %i{title id}.each { |f| self[f] = raw_grp_data[f] }
81
+ [:controls].each { |f| self[f] = raw_grp_data[f] || [] }
82
+ end
83
+ end
84
+
85
+ Input = Struct.new(
86
+ :name, :options
87
+ ) do
88
+ include HashLikeStruct
89
+ def initialize(raw_input_data)
90
+ self.name = raw_input_data[:name]
91
+ self.options = Inspec::RunData::Profile::Input::Options.new(raw_input_data[:options])
92
+ end
93
+ end
94
+ class Input
95
+ Options = Struct.new(
96
+ # There are probably others
97
+ :value,
98
+ :type,
99
+ :required
100
+ ) do
101
+ include HashLikeStruct
102
+ def initialize(raw_opts_data)
103
+ %i{value type required}.each { |f| self[f] = raw_opts_data[f] }
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,40 @@
1
+ module Inspec
2
+ class RunData
3
+ Result = Struct.new(
4
+ :message, # Human-friendly test failure message
5
+ :code_desc, # Generated test description
6
+ :expectation_message, # a substring of code_desc
7
+ :resource_name, # We try to determine this
8
+ :run_time, # Float seconds execution time
9
+ :skip_message, # String
10
+ :start_time, # DateTime
11
+ :status, # String
12
+ :resource_title, # Ugly internals
13
+ # :waiver_data, # Undocumented tramp data / not exposed in this API
14
+ :resource, # Undocumented, what is this
15
+ :exception,
16
+ :backtrace
17
+ ) do
18
+ include HashLikeStruct
19
+ def initialize(raw_res_data)
20
+ [
21
+ :status, # String
22
+ :code_desc, # Generated test description
23
+ :expectation_message, # a substring of code_desc
24
+ :skip_message, # String
25
+ :run_time,
26
+ :start_time,
27
+ :resource_title,
28
+ :resource,
29
+ :exception,
30
+ :backtrace,
31
+ :message,
32
+ ].each do |field|
33
+ self[field] = raw_res_data[field]
34
+ end
35
+
36
+ self.resource_name = raw_res_data[:resource_title].instance_variable_get(:@__resource_name__)&.to_s
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ module Inspec
2
+ class RunData
3
+ # {:duration=>0.018407, :controls=>{:total=>3, :passed=>{:total=>3}, :skipped=>{:total=>0}, :failed=>{:total=>0}}}
4
+ Statistics = Struct.new(
5
+ :duration,
6
+ :controls
7
+ ) do
8
+ include HashLikeStruct
9
+ def initialize(raw_stat_data)
10
+ self.controls = Inspec::RunData::Statistics::Controls.new(raw_stat_data[:controls])
11
+ self.duration = raw_stat_data[:duration]
12
+ end
13
+ end
14
+ class Statistics
15
+ Controls = Struct.new(
16
+ :total,
17
+ :passed,
18
+ :skipped,
19
+ :failed
20
+ ) do
21
+ include HashLikeStruct
22
+ def initialize(raw_stat_ctl_data)
23
+ self.total = raw_stat_ctl_data[:total]
24
+ self.passed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:passed][:total])
25
+ self.skipped = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:skipped][:total])
26
+ self.failed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:failed][:total])
27
+ end
28
+ end
29
+ class Controls
30
+ Total = Struct.new(:total) do
31
+ include HashLikeStruct
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,6 +1,7 @@
1
1
  require "stringio"
2
2
  require "json"
3
3
  require "inspec/globals"
4
+ require "inspec/config"
4
5
 
5
6
  module Inspec
6
7
  module Deprecation
@@ -32,6 +33,7 @@ module Inspec
32
33
  @groups = {}
33
34
  @unknown_group_action = :warn
34
35
  validate!
36
+ silence_deprecations_from_cli
35
37
  end
36
38
 
37
39
  private
@@ -45,6 +47,25 @@ module Inspec
45
47
  File.open(default_path)
46
48
  end
47
49
 
50
+ def silence_deprecations_from_cli
51
+ # Read --silence-deprecations CLI option
52
+ cfg = Inspec::Config.cached
53
+ return unless cfg[:silence_deprecations]
54
+
55
+ groups_to_silence = cfg[:silence_deprecations]
56
+ silence_all = groups_to_silence.include?("all")
57
+
58
+ groups.each do |group_name, group|
59
+ # Only silence things that warn. Don't silence things that exit;
60
+ # those harsher measures are usually protecting removed code and ignoring
61
+ # and continuing regardless would be perilous and lead to errors.
62
+ if %i{warn fail_control}.include?(group.action) &&
63
+ (silence_all || groups_to_silence.include?(group_name.to_s))
64
+ group.action = :ignore
65
+ end
66
+ end
67
+ end
68
+
48
69
  #====================================================================================================#
49
70
  # Validation
50
71
  #====================================================================================================#
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inspec
4
+ module Utils
5
+ #
6
+ # Inspec::Utils::JsonProfileSummary takes in certain information to identify a
7
+ # profile and then produces a JSON-formatted summary of that profile. It can
8
+ # return the results to STDOUT or a file. It is currently used in several
9
+ # places in the CLI such as `json`, `archive` and `artifact`.
10
+ #
11
+ #
12
+ module JsonProfileSummary
13
+ def self.produce_json(info:, write_path: "", suppress_output: false)
14
+ # add in inspec version
15
+ info[:generator] = {
16
+ name: "inspec",
17
+ version: Inspec::VERSION,
18
+ }
19
+ if write_path.empty?
20
+ puts JSON.dump(info)
21
+ else
22
+ unless suppress_output
23
+ if File.exist? write_path
24
+ Inspec::Log.info "----> updating #{write_path}"
25
+ else
26
+ Inspec::Log.info "----> creating #{write_path}"
27
+ end
28
+ end
29
+ full_write_path = File.expand_path(write_path)
30
+ File.write(full_write_path, JSON.dump(info))
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "4.18.108".freeze
2
+ VERSION = "4.20.2".freeze
3
3
  end
@@ -5,6 +5,7 @@ require "set"
5
5
  require "tempfile"
6
6
  require "yaml"
7
7
  require "inspec/dist"
8
+ require "inspec/utils/json_profile_summary"
8
9
 
9
10
  module InspecPlugins
10
11
  module Artifact
@@ -40,9 +41,13 @@ module InspecPlugins
40
41
 
41
42
  def self.profile_sign(options)
42
43
  artifact = new
44
+ path_to_profile = options["profile"]
45
+
46
+ # Write inspec.json file within artifact
47
+ write_inspec_json(path_to_profile, options)
48
+
43
49
  Dir.mktmpdir do |workdir|
44
50
  puts "Signing #{options["profile"]} with key #{options["keyname"]}"
45
- path_to_profile = options["profile"]
46
51
  profile_md = artifact.read_profile_metadata(path_to_profile)
47
52
  artifact_filename = "#{profile_md["name"]}-#{profile_md["version"]}.#{SIGNED_PROFILE_SUFFIX}"
48
53
  tarfile = artifact.profile_compress(path_to_profile, profile_md, workdir)
@@ -63,6 +68,9 @@ module InspecPlugins
63
68
  end
64
69
  puts "Successfully generated #{artifact_filename}"
65
70
  end
71
+
72
+ # Cleanup
73
+ File.delete("#{path_to_profile}/inspec.json")
66
74
  end
67
75
 
68
76
  def self.profile_verify(options)
@@ -165,6 +173,15 @@ module InspecPlugins
165
173
  raise "Artifact is invalid"
166
174
  end
167
175
  end
176
+
177
+ def self.write_inspec_json(root_path, opts)
178
+ profile = Inspec::Profile.for_path(root_path, opts)
179
+ Inspec::Utils::JsonProfileSummary.produce_json(
180
+ info: profile.info,
181
+ write_path: "#{root_path}/inspec.json",
182
+ suppress_output: true
183
+ )
184
+ end
168
185
  end
169
186
  end
170
187
  end
@@ -41,8 +41,9 @@ module InspecPlugins
41
41
  templates_path: TEMPLATES_PATH,
42
42
  overwrite: options[:overwrite],
43
43
  file_rename_map: make_rename_map(plugin_type, plugin_name, snake_case),
44
- skip_files: make_skip_list,
44
+ skip_files: make_skip_list(template_vars["hooks"].keys),
45
45
  }
46
+
46
47
  renderer = InspecPlugins::Init::Renderer.new(ui, render_opts)
47
48
 
48
49
  renderer.render_with_values(template_path, plugin_type + " plugin", template_vars)
@@ -72,6 +73,7 @@ module InspecPlugins
72
73
  File.join("lib", "inspec-plugin-template") => File.join("lib", plugin_name),
73
74
  File.join("lib", "inspec-plugin-template.rb") => File.join("lib", plugin_name + ".rb"),
74
75
  File.join("lib", "inspec-plugin-template", "cli_command.rb") => File.join("lib", plugin_name, "cli_command.rb"),
76
+ File.join("lib", "inspec-plugin-template", "reporter.rb") => File.join("lib", plugin_name, "reporter.rb"),
75
77
  File.join("lib", "inspec-plugin-template", "plugin.rb") => File.join("lib", plugin_name, "plugin.rb"),
76
78
  File.join("lib", "inspec-plugin-template", "version.rb") => File.join("lib", plugin_name, "version.rb"),
77
79
  File.join("test", "functional", "inspec_plugin_template_test.rb") => File.join("test", "functional", snake_case + "_test.rb"),
@@ -168,6 +170,9 @@ module InspecPlugins
168
170
  if hooks_by_type.key?(:cli_command)
169
171
  vars[:command_name_dashes] = hooks_by_type[:cli_command].tr("_", "-")
170
172
  vars[:command_name_snake] = hooks_by_type[:cli_command].tr("-", "_")
173
+ elsif hooks_by_type.key?(:reporter)
174
+ vars[:reporter_name_dashes] = hooks_by_type[:reporter].tr("_", "-")
175
+ vars[:reporter_name_snake] = hooks_by_type[:reporter].tr("-", "_")
171
176
  end
172
177
  vars
173
178
  end
@@ -205,19 +210,20 @@ module InspecPlugins
205
210
  end
206
211
  end
207
212
 
208
- def make_skip_list
213
+ def make_skip_list(requested_hooks)
214
+ skips = []
209
215
  case options[:detail]
210
- when "full"
211
- []
216
+ when "full" # rubocop: disable Lint/EmptyWhen
217
+ # Do nothing but allow this case for validation
212
218
  when "core"
213
- [
219
+ skips += [
214
220
  "Gemfile",
215
221
  "inspec-plugin-template.gemspec",
216
222
  "LICENSE",
217
223
  "Rakefile",
218
224
  ]
219
225
  when "test-fixture"
220
- [
226
+ skips += [
221
227
  "Gemfile",
222
228
  "inspec-plugin-template.gemspec",
223
229
  "LICENSE",
@@ -237,6 +243,22 @@ module InspecPlugins
237
243
  ui.error "Unrecognized value for 'detail': #{options[:detail]} - expected one of full, core, test-fixture"
238
244
  ui.exit(:usage_error)
239
245
  end
246
+
247
+ # Remove hook-specific files
248
+ unless requested_hooks.include?(:cli_command)
249
+ skips += [
250
+ File.join("lib", "inspec-plugin-template", "cli_command.rb"),
251
+ File.join("test", "unit", "cli_args_test.rb"),
252
+ File.join("test", "functional", "inspec_plugin_template_test.rb"),
253
+ ]
254
+ end
255
+ unless requested_hooks.include?(:reporter)
256
+ skips += [
257
+ File.join("lib", "inspec-plugin-template", "reporter.rb"),
258
+ ]
259
+ end
260
+
261
+ skips.uniq
240
262
  end
241
263
  end
242
264
  end
@@ -28,6 +28,7 @@ module InspecPlugins
28
28
  # Internal machine name of the plugin. InSpec will use this in errors, etc.
29
29
  plugin_name :'<%= plugin_name %>'
30
30
 
31
+ <% if hooks[:cli_command] %>
31
32
  # Define a new CLI subcommand.
32
33
  # The argument here will be used to match against the command line args,
33
34
  # and if the user said `inspec list-resources`, this hook will get called.
@@ -48,6 +49,23 @@ module InspecPlugins
48
49
  # CLI engine tap into it.
49
50
  InspecPlugins::<%= module_name %>::CliCommand
50
51
  end
52
+ <% end %>
53
+
54
+ <% if hooks[:reporter] %>
55
+ # Define a new Reporter.
56
+ # The argument here will be used to match against the CLI --reporter option.
57
+ # `--reporter <%= reporter_name_snake %>` will load your reporter and call its renderer.
58
+ reporter :<%= reporter_name_snake %> do
59
+ # Calling this hook doesn't mean the reporter is being executed - just
60
+ # that we should be ready to do so. So, load the file that defines the
61
+ # functionality.
62
+ require '<%= plugin_name %>/reporter'
63
+
64
+ # Having loaded our functionality, return a class that will let the
65
+ # reporting engine tap into it.
66
+ InspecPlugins::<%= module_name %>::Reporter
67
+ end
68
+ <% end %>
51
69
  end
52
70
  end
53
71
  end
@@ -0,0 +1,27 @@
1
+ module InspecPlugins::<%= module_name %>
2
+ # This class will provide the actual Reporter implementation.
3
+ # Its superclass is provided by another call to Inspec.plugin,
4
+ # this time with two args. The first arg specifies we are requesting
5
+ # version 2 of the Plugins API. The second says we are making a
6
+ # Reporter plugin component, so please make available any DSL needed
7
+ # for that.
8
+
9
+ class Reporter < Inspec.plugin(2, :reporter)
10
+
11
+ # All a Reporter *must* do is define a render() method that calls
12
+ # output(). You should access the run_data accessor to read off the
13
+ # results of the run.
14
+ def render
15
+ # There is much more to explore in the run_data structure!
16
+ run_data[:profiles].each do |profile|
17
+ output(profile[:title])
18
+ profile[:controls].each do |control|
19
+ output(control[:title])
20
+ control[:results].each do |test|
21
+ output(test[:status])
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ 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.18.108
4
+ version: 4.20.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef InSpec Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-23 00:00:00.000000000 Z
11
+ date: 2020-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef-telemetry
@@ -471,6 +471,7 @@ files:
471
471
  - lib/inspec/plugin/v2/plugin_types/dsl.rb
472
472
  - lib/inspec/plugin/v2/plugin_types/input.rb
473
473
  - lib/inspec/plugin/v2/plugin_types/mock.rb
474
+ - lib/inspec/plugin/v2/plugin_types/reporter.rb
474
475
  - lib/inspec/plugin/v2/registry.rb
475
476
  - lib/inspec/plugin/v2/status.rb
476
477
  - lib/inspec/profile.rb
@@ -613,6 +614,11 @@ files:
613
614
  - lib/inspec/resources/zfs_pool.rb
614
615
  - lib/inspec/rspec_extensions.rb
615
616
  - lib/inspec/rule.rb
617
+ - lib/inspec/run_data.rb
618
+ - lib/inspec/run_data/control.rb
619
+ - lib/inspec/run_data/profile.rb
620
+ - lib/inspec/run_data/result.rb
621
+ - lib/inspec/run_data/statistics.rb
616
622
  - lib/inspec/runner.rb
617
623
  - lib/inspec/runner_mock.rb
618
624
  - lib/inspec/runner_rspec.rb
@@ -648,6 +654,7 @@ files:
648
654
  - lib/inspec/utils/hash.rb
649
655
  - lib/inspec/utils/install_context.rb
650
656
  - lib/inspec/utils/json_log.rb
657
+ - lib/inspec/utils/json_profile_summary.rb
651
658
  - lib/inspec/utils/modulator.rb
652
659
  - lib/inspec/utils/nginx_parser.rb
653
660
  - lib/inspec/utils/object_traversal.rb
@@ -697,6 +704,7 @@ files:
697
704
  - lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template.rb
698
705
  - lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/cli_command.rb
699
706
  - lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/plugin.rb
707
+ - lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/reporter.rb
700
708
  - lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/version.rb
701
709
  - lib/plugins/inspec-init/templates/profiles/aws/README.md
702
710
  - lib/plugins/inspec-init/templates/profiles/aws/attributes.yml