inspec-core 4.18.114 → 4.19.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bee754e41f53d26552da0d2972a28cf55127effcad8c93df678227e99061e676
4
- data.tar.gz: 60bd7f57517db8c4e1848ded6ac1fd580b61c29a9b2d24168480b3d62e37557f
3
+ metadata.gz: 0c6037004db1c4ef1ba337ea6ef710938a2ce1d02c45e56b0bdfe7349b146a92
4
+ data.tar.gz: 926e31bc929c5e5c76e3a5e89ed686218000a2ad5374c6b7901ac006ee2536a7
5
5
  SHA512:
6
- metadata.gz: 7c9b443a572925dd12dc846c094256aff3f9fabcc4508ee51ab813f0ec6221088c500ce9a1c77cca693258b48a94212043d2ffd4eb8c49052dbb15bc8d93cc00
7
- data.tar.gz: 55c2e45a2ef2e8c9da30a24eb4b09e213320e1d33d2c50e30856754038d75f59576dd76f084a59ee2028920baa37674c0683b426dce6bb3dc06bc5a8c879566b
6
+ metadata.gz: 36c0d1cf39247500f0bc33a633279c08bdaeab0c2dc848fc14f54405cb8c3b3fb1d86ae0ef0afc0ce1394c73b6de978fc61e4ff523b2dfc56d58536920caae47
7
+ data.tar.gz: c385ff9ee77aca4689dc4a83c8bef4984a3b509e6a7f459655e44a5f1a1944792c2ac1f78b0626cde72697c2b6fee1d4c2eb05ae566d928596e447d5d08f2e34
@@ -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
 
@@ -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)
@@ -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
@@ -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
@@ -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
@@ -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.114".freeze
2
+ VERSION = "4.19.0".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.114
4
+ version: 4.19.0
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-05-11 00:00:00.000000000 Z
11
+ date: 2020-05-27 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