inspec-core 5.10.5 → 5.17.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,137 @@
1
+ require "inspec/resources/command"
2
+
3
+ module Inspec::Resources
4
+ class Routingtable < Inspec.resource(1)
5
+ # resource internal name.
6
+ name "routing_table"
7
+
8
+ # Restrict to only run on the below platforms (if none were given,
9
+ # all OS's and cloud API's supported)
10
+ supports platform: "unix"
11
+ supports platform: "windows"
12
+
13
+ desc "Use the `routing_table` Chef InSpec audit resource to test the routing information parameters(destination, gateway and interface) present in the routing table."
14
+
15
+ example <<~EXAMPLE
16
+ describe routing_table do
17
+ it do
18
+ should have_entry(
19
+ :destination => '192.168.43.1/32',
20
+ :interface => 'lxdbr0',
21
+ :gateway => '172.31.80.1',
22
+ )
23
+ end
24
+ end
25
+
26
+ describe routing_table do
27
+ it { should have_entry(destination: '0.0.0.0', interface: 'eth0', gateway: '172.31.80.1') }
28
+ end
29
+ EXAMPLE
30
+
31
+ def initialize
32
+ skip_resource "The `routing_table` resource is not yet available on your OS." unless inspec.os.unix? || inspec.os.windows?
33
+ # fetch the routing information and store it in @routing_info (could be hash, tbd)
34
+ @routing_info = {}
35
+ fetch_routing_information
36
+ end
37
+
38
+ # resource appearance in test reports.
39
+ def to_s
40
+ "routing_table"
41
+ end
42
+
43
+ def has_entry?(input_route)
44
+ # check if the destination, gateway, interface exists as part of the routing_info
45
+ if input_route.key?(:destination) && input_route.key?(:gateway) && input_route.key?(:interface)
46
+ # check if there is key with destination's value in hash;
47
+ # if yes, check if destination and gateway is present else return false
48
+ @routing_info.key?(input_route[:destination]) ? @routing_info[input_route[:destination]].include?([input_route[:gateway], input_route[:interface]]) : false
49
+ else
50
+ raise Inspec::Exceptions::ResourceSkipped, "One or more missing key, have_entry? matcher expects a hash with 3 keys: destination, gateway and interface"
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # fetches the routing information for the system
57
+ def fetch_routing_information
58
+ # check if netstat is available on the system
59
+ utility = find_netstat_or_error
60
+
61
+ # the command to fetch the routing information
62
+ fetch_route_cmd = "#{utility} -rn"
63
+
64
+ # execute the above netstat command
65
+ cmd = inspec.command(fetch_route_cmd)
66
+
67
+ # raise error if the exit status is not zero;
68
+ raise Inspec::Exceptions::ResourceFailed, "Executing netstat failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
69
+
70
+ # Todo:
71
+ # Improve logic to fetch destination, gateway & interface efficiently.
72
+ # The below logic assumes the following:
73
+ # 1. destination, gateway & interface header is present as Destination, Gateway & (Iface or Netif or Interface) respectively.
74
+ # (Netif on BSD, Darwin,Iface on linux & Interface on Windows)
75
+ # 2. there is no blank data for any columns or the blank data are present after the interface column.
76
+
77
+ # cmd.stdout is the standard out of netstat -rn; split on new line to get the rows
78
+ raw_route_info = cmd.stdout.split("\n")
79
+
80
+ # since raw_route_info contains some row before the header (i.e. Destination Gateway ...); remove those rows
81
+ raw_route_info.shift until raw_route_info[0] =~ /Destination/i
82
+
83
+ # split each rows based on space to get the individual columns
84
+ # raw_route_info is now array of arrays with the routing information
85
+ raw_route_info.map! { |info| info.strip.split }
86
+
87
+ # these variables will store the indices where destination, gateway and interface are present
88
+ destination_index, gateway_index, interface_index = -1, -1, -1
89
+
90
+ # The headers in windows are as:
91
+ # Network Destination Netmask Gateway Interface Metric
92
+ # Splitting on space makes "Network Destination" to be two separate values as "Network" & "Destination"
93
+ # Remove "Network" value to apply the logic of finding index
94
+ raw_route_info[0].shift if inspec.os.windows?
95
+
96
+ # find the indices of destination, gateway and interface;
97
+ # because the position of gateway & interface varies with operating system
98
+ raw_route_info[0].each_with_index do |header, index|
99
+ if header =~ /Destination/i
100
+ destination_index = index
101
+ elsif header =~ /Gateway/i
102
+ gateway_index = index
103
+ elsif header =~ /Iface|Netif|Interface/i
104
+ interface_index = index
105
+ end
106
+ end
107
+
108
+ # remove the initial header consisting of Destination, Gateway, Mask, ... since this is of no use
109
+ raw_route_info.shift
110
+
111
+ # check the indices are assigned with some index and not -1
112
+ if destination_index != -1 && gateway_index != -1 && interface_index != -1
113
+ # iterate through the route_info; and find destination, gateway and interface from each row
114
+ raw_route_info.each do |info|
115
+ # if value exists at the destination_index, gateway_index, and interface_index; store the value in @routing_info
116
+ if !info[destination_index].nil? && !info[gateway_index].nil? && !info[interface_index].nil?
117
+ # if the destination_key is already present, append the gateway & interface; else create new array and add them
118
+ if @routing_info.key?(info[destination_index])
119
+ @routing_info[info[destination_index]] << [info[gateway_index], info[interface_index]]
120
+ else
121
+ @routing_info[info[destination_index]] = [[info[gateway_index], info[interface_index]]]
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ # check if netstat is available on the system
129
+ def find_netstat_or_error
130
+ %w{/usr/sbin/netstat /sbin/netstat /usr/bin/netstat /bin/netstat netstat}.each do |cmd|
131
+ return cmd if inspec.command(cmd).exist?
132
+ end
133
+
134
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `netstat` utility to view routing table information"
135
+ end
136
+ end
137
+ end
@@ -182,7 +182,9 @@ module Inspec::Resources
182
182
  when "aix"
183
183
  SrcMstr.new(inspec)
184
184
  when "amazon"
185
- if os[:release] =~ /^20\d\d/
185
+ # If `initctl` exists on the system, use `Upstart`. Else use `Systemd` since all-new Amazon Linux supports `systemctl`.
186
+ # This way, it is not dependent on the version of Amazon Linux.
187
+ if inspec.command("initctl").exist? || inspec.command("/sbin/initctl").exist?
186
188
  Upstart.new(inspec, service_ctl)
187
189
  else
188
190
  Systemd.new(inspec, service_ctl)
@@ -271,6 +273,30 @@ module Inspec::Resources
271
273
  info[:startname]
272
274
  end
273
275
 
276
+ # matcher equivalent to startmode property; compares start-up mode
277
+ # supported only on windows.
278
+ def has_start_mode?(mode)
279
+ raise Inspec::Exceptions::ResourceSkipped, "The `has_start_mode` matcher is not supported on your OS yet." unless inspec.os.windows?
280
+
281
+ mode == startmode
282
+ end
283
+
284
+ # matcher to check if the service is monitored by the given monitoring tool/software
285
+ def monitored_by?(monitoring_tool)
286
+ # Currently supported monitoring tools are: monit & god
287
+ # To add support for new monitoring tools, extend the case statement with additional monitoring tool and
288
+ # add the definition and logic in a new class (inheriting the base class MonitoringTool: optional)
289
+ case monitoring_tool
290
+ when "monit"
291
+ current_monitoring_tool = Monit.new(inspec, @service_name)
292
+ when "god"
293
+ current_monitoring_tool = God.new(inspec, @service_name)
294
+ else
295
+ puts "The monitoring tool #{monitoring_tool} is not yet supported by InSpec."
296
+ end
297
+ current_monitoring_tool.is_service_monitored?
298
+ end
299
+
274
300
  def to_s
275
301
  "Service #{@service_name}"
276
302
  end
@@ -537,9 +563,22 @@ module Inspec::Resources
537
563
  end
538
564
 
539
565
  def info(service_name)
566
+ # `service -l` lists all files in /etc/rc.d and the local startup directories
567
+ # % service -l
568
+ # accounting
569
+ # addswap
570
+ # adjkerntz
571
+ # apm
572
+ # archdep
573
+ cmd = inspec.command("#{service_ctl} -l")
574
+ return nil if cmd.exit_status != 0
575
+
576
+ # search for the service
577
+ srv = /^#{service_name}$/.match(cmd.stdout)
578
+ return nil if srv.nil? || srv[0].nil?
579
+
540
580
  # check if service is enabled
541
581
  cmd = inspec.command("#{service_ctl} #{service_name} enabled")
542
-
543
582
  enabled = cmd.exit_status == 0
544
583
 
545
584
  # check if the service is running
@@ -880,4 +919,53 @@ module Inspec::Resources
880
919
  Runit.new(inspec, service_ctl)
881
920
  end
882
921
  end
922
+
923
+ # Helper class for monitored_by matcher
924
+ class MonitoringTool
925
+ attr_reader :inspec, :service_name
926
+ def initialize(inspec, service_name)
927
+ @inspec = inspec
928
+ @service_name ||= service_name
929
+ end
930
+
931
+ def find_utility_or_error(utility_name)
932
+ [ "/usr/sbin/#{utility_name}" , "/sbin/#{utility_name}" , "/usr/bin/#{utility_name}" , "/bin/#{utility_name}" , "#{utility_name}" ].each do |cmd|
933
+ return cmd if inspec.command(cmd).exist?
934
+ end
935
+
936
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `#{utility_name}`"
937
+ end
938
+ end
939
+
940
+ class Monit < MonitoringTool
941
+ def is_service_monitored?
942
+ utility = find_utility_or_error("monit")
943
+ utility_cmd = inspec.command("#{utility} summary")
944
+
945
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{utility} summary failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
946
+
947
+ monitoring_info = utility_cmd.stdout.split("\n")
948
+ monitoring_info.map! { |info| info.strip.squeeze(" ") }
949
+ is_monitored = false
950
+ monitoring_info.each do |info|
951
+ if info =~ /^#{service_name} OK.*/
952
+ is_monitored = true
953
+ break
954
+ end
955
+ end
956
+ is_monitored
957
+ end
958
+ end
959
+
960
+ class God < MonitoringTool
961
+ def is_service_monitored?
962
+ utility = find_utility_or_error("god")
963
+ utility_cmd = inspec.command("#{utility} status #{service_name}")
964
+
965
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{utility} status #{service_name} failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
966
+
967
+ monitoring_info = utility_cmd.stdout.strip
968
+ monitoring_info =~ /^#{service_name}: up/
969
+ end
970
+ end
883
971
  end
@@ -1 +1,13 @@
1
1
  require "inspec/resources/users"
2
+ # user resource belong_to matcher for serverspec compatibility
3
+ RSpec::Matchers.define :belong_to_primary_group do |group|
4
+ match do |user|
5
+ user.belongs_to_primary_group?(group)
6
+ end
7
+ end
8
+
9
+ RSpec::Matchers.define :belong_to_group do |group|
10
+ match do |user|
11
+ user.belongs_to_group?(group)
12
+ end
13
+ end
@@ -137,20 +137,17 @@ module Inspec::Resources
137
137
  # its('badpasswordattempts') { should eq 0 }
138
138
  # end
139
139
  #
140
- # The following Serverspec matchers are deprecated in favor for direct value access
140
+ # The following Serverspec matchers were deprecated in favor for direct value access
141
+ # but are made available as part of Serverspec compatibility in March, 2022.
141
142
  #
142
143
  # describe user('root') do
143
144
  # it { should belong_to_group 'root' }
145
+ # it { should belong_to_primary_group 'root' }
144
146
  # it { should have_uid 0 }
145
147
  # it { should have_home_directory '/root' }
146
148
  # it { should have_login_shell '/bin/bash' }
147
149
  # its('minimum_days_between_password_change') { should eq 0 }
148
150
  # its('maximum_days_between_password_change') { should eq 99 }
149
- # end
150
- #
151
- # ServerSpec tests that are not supported:
152
- #
153
- # describe user('root') do
154
151
  # it { should have_authorized_key 'ssh-rsa ADg54...3434 user@example.local' }
155
152
  # its(:encrypted_password) { should eq 1234 }
156
153
  # end
@@ -258,36 +255,56 @@ module Inspec::Resources
258
255
 
259
256
  # implement 'mindays' method to be compatible with serverspec
260
257
  def minimum_days_between_password_change
261
- Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `minimum_days_between_password_change` property is deprecated. Please use `mindays`.")
262
258
  mindays
263
259
  end
264
260
 
265
261
  # implement 'maxdays' method to be compatible with serverspec
266
262
  def maximum_days_between_password_change
267
- Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `maximum_days_between_password_change` property is deprecated. Please use `maxdays`.")
268
263
  maxdays
269
264
  end
270
265
 
271
266
  # implements rspec has matcher, to be compatible with serverspec
272
267
  # @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
268
+ # has_uid matcher: compatibility with serverspec
273
269
  def has_uid?(compare_uid)
274
- Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `has_uid?` matcher is deprecated.")
275
270
  uid == compare_uid
276
271
  end
277
272
 
273
+ # has_home_directory matcher: compatibility with serverspec
278
274
  def has_home_directory?(compare_home)
279
- Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `has_home_directory?` matcher is deprecated. Please use `its('home')`.")
280
275
  home == compare_home
281
276
  end
282
277
 
278
+ # has_login_shell matcher: compatibility with serverspec
283
279
  def has_login_shell?(compare_shell)
284
- Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `has_login_shell?` matcher is deprecated. Please use `its('shell')`.")
285
280
  shell == compare_shell
286
281
  end
287
282
 
288
- def has_authorized_key?(_compare_key)
289
- Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `has_authorized_key?` matcher is deprecated. There is no currently implemented alternative")
290
- raise NotImplementedError
283
+ # has_authorized_key matcher: compatibility with serverspec
284
+ def has_authorized_key?(compare_key)
285
+ # get_authorized_keys returns the list of key, check if given key is included.
286
+ get_authorized_keys.include?(compare_key)
287
+ end
288
+
289
+ # belongs_to_primary_group matcher: compatibility with serverspec
290
+ def belongs_to_primary_group?(group_name)
291
+ groupname == group_name
292
+ end
293
+
294
+ # belongs_to_group matcher: compatibility with serverspec
295
+ def belongs_to_group?(group_name)
296
+ groups.include?(group_name)
297
+ end
298
+
299
+ # encrypted_password property: compatibility with serverspec
300
+ # it allows to run test against the hashed passwords of the given user
301
+ # applicable for unix/linux systems with getent utility.
302
+ def encrypted_password
303
+ raise Inspec::Exceptions::ResourceSkipped, "encrypted_password property is not applicable for your system" if inspec.os.windows? || inspec.os.darwin?
304
+
305
+ # shadow_information returns array of the information from the shadow file
306
+ # the value at 1st index is the encrypted_password information
307
+ shadow_information[1]
291
308
  end
292
309
 
293
310
  def to_s
@@ -314,6 +331,54 @@ module Inspec::Resources
314
331
 
315
332
  @cred_cache = @user_provider.credentials(@username) unless @user_provider.nil?
316
333
  end
334
+
335
+ # helper method for has_authorized_key matcher
336
+ # get_authorized_keys return the key/keys stored in the authorized_keys path
337
+ def get_authorized_keys
338
+ # cat is used in unix system to display content of file; similarly type is used for windows
339
+ bin = inspec.os.windows? ? "type" : "cat"
340
+
341
+ # auth_path gets assigned with the valid path for authorized_keys
342
+ auth_path = ""
343
+
344
+ # possible paths where authorized_keys are stored
345
+ # inspec.command is used over inspec.file because inspec.file requires absolute path
346
+ %w{~/.ssh/authorized_keys ~/.ssh/authorized_keys2}.each do |path|
347
+ if inspec.command("#{bin} #{path}").exit_status == 0
348
+ auth_path = path
349
+ break
350
+ end
351
+ end
352
+
353
+ # if auth_path is empty, no valid path was found, hence raise exception
354
+ raise Inspec::Exceptions::ResourceSkipped, "Can't find any valid path for authorized_keys" if auth_path.empty?
355
+
356
+ # authorized_keys are obtained in the standard output;
357
+ # split keys on newline if more than one keys are part of authorized_keys
358
+ inspec.command("#{bin} #{auth_path}").stdout.split("\n").map(&:strip)
359
+ end
360
+
361
+ # Helper method for encrypted_password property
362
+ def shadow_information
363
+ # check if getent is available on the system
364
+ bin = find_getent_utility
365
+
366
+ # fetch details of the passwd file for the current user using getent
367
+ cmd = inspec.command("#{bin} shadow #{@username}")
368
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{bin} shadow #{@username} failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
369
+
370
+ # shadow information are : separated values, split and return
371
+ cmd.stdout.split(":").map(&:strip)
372
+ end
373
+
374
+ # check if getent exist in the system
375
+ def find_getent_utility
376
+ %w{/usr/bin/getent /bin/getent getent}.each do |cmd|
377
+ return cmd if inspec.command(cmd).exist?
378
+ end
379
+
380
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `getent` on your system."
381
+ end
317
382
  end
318
383
 
319
384
  # Class defined to compare for groups without case-sensitivity
@@ -0,0 +1,93 @@
1
+ require "inspec/resources/file"
2
+
3
+ module Inspec::Resources
4
+ class X509PrivateKey < Inspec.resource(1)
5
+ # Resource internal name.
6
+ name "x509_private_key"
7
+
8
+ # Restrict to only run on the below platforms (if none were given,
9
+ # all OS's and cloud API's supported)
10
+ supports platform: "unix"
11
+ supports platform: "windows"
12
+
13
+ desc "Use the x509_private_key InSpec audit resource to test the x509 private key"
14
+
15
+ example <<~EXAMPLE
16
+ # With passphrase
17
+ describe x509_private_key("/home/openssl_activity/alice_private.pem", "password@123") do
18
+ it { should be_valid }
19
+ it { should be_encrypted }
20
+ it { should have_matching_certificate("/home/openssl_activity/alice_certificate.crt") }
21
+ end
22
+
23
+ # Without passphrase
24
+ describe x509_private_key("/home/openssl_activity/bob_private.pem") do
25
+ it { should be_valid }
26
+ it { should_not be_encrypted }
27
+ it { should have_matching_certificate("/home/openssl_activity/bob_certificate.crt") }
28
+ end
29
+ EXAMPLE
30
+
31
+ # Resource initialization.
32
+ attr_reader :secret_key_path, :passphrase, :openssl_utility
33
+
34
+ def initialize(secret_key_path, passphrase = nil)
35
+ @openssl_utility = check_openssl_or_error
36
+ @secret_key_path = secret_key_path
37
+ @passphrase = passphrase
38
+ end
39
+
40
+ # Resource appearance in test reports.
41
+ def to_s
42
+ "x509_private_key"
43
+ end
44
+
45
+ # Matcher to check if the given key is valid.
46
+ def valid?
47
+ # Below is the command to check if the key is valid.
48
+ openssl_key_validity_cmd = "#{openssl_utility} rsa -in #{secret_key_path} -check -noout"
49
+
50
+ # Additionally, if key is password protected, passphrase needs to be given with -passin argument
51
+ openssl_key_validity_cmd.concat(" -passin pass:#{passphrase}") if passphrase
52
+
53
+ openssl_key_validity = inspec.command(openssl_key_validity_cmd)
54
+ openssl_key_validity.exit_status.to_i == 0
55
+ end
56
+
57
+ # Matcher to check if the given key is encrypted.
58
+ def encrypted?
59
+ raise Inspec::Exceptions::ResourceFailed, "The given secret key #{secret_key_path} does not exist." unless inspec.file(secret_key_path).exist?
60
+
61
+ # All encrypted keys have the header of Proc-Type: 4,ENCRYPTED
62
+ key_file = inspec.file(secret_key_path)
63
+ key_file.content =~ /Proc-Type: 4,ENCRYPTED/
64
+ end
65
+
66
+ # Matcher to verify if the private key maatches the certificate
67
+ def has_matching_certificate?(cert_file_or_path)
68
+ cert_hash_cmd = "openssl x509 -noout -modulus -in #{cert_file_or_path} | openssl md5"
69
+ cert_hash = inspec.command(cert_hash_cmd)
70
+
71
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{cert_hash_cmd} failed: #{cert_hash.stderr}" if cert_hash.exit_status.to_i != 0
72
+
73
+ key_hash_cmd = "openssl rsa -noout -modulus -in #{secret_key_path}"
74
+ passphrase ? key_hash_cmd.concat(" -passin pass:#{passphrase} | openssl md5") : key_hash_cmd.concat(" | openssl md5")
75
+ key_hash = inspec.command(key_hash_cmd)
76
+
77
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{key_hash_cmd} failed: #{key_hash.stderr}" if key_hash.exit_status.to_i != 0
78
+
79
+ cert_hash.stdout == key_hash.stdout
80
+ end
81
+
82
+ private
83
+
84
+ # This resource requires openssl to be available on the system
85
+ def check_openssl_or_error
86
+ %w{/usr/sbin/openssl /usr/bin/openssl /sbin/openssl /bin/openssl openssl}.each do |cmd|
87
+ return cmd if inspec.command(cmd).exist?
88
+ end
89
+
90
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `openssl` on your system."
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,48 @@
1
+ require "inspec/resources/zfs_pool"
2
+
3
+ module Inspec::Resources
4
+ class Zfs < ZfsPool
5
+ # resource's internal name.
6
+ name "zfs"
7
+
8
+ # Restrict to only run on the below platforms
9
+ supports platform: "unix"
10
+
11
+ desc "Use the zfs InSpec audit resource to test if the named ZFS Pool is present and/or has certain properties."
12
+
13
+ example <<~EXAMPLE
14
+ describe zfs("new-pool") do
15
+ it { should exist }
16
+ it { should have_property({ "failmode" => "wait", "capacity" => "0" }) }
17
+ end
18
+ EXAMPLE
19
+
20
+ # Resource initialization is done in the parent class i.e. ZfsPool
21
+
22
+ # Unique identity for the resource.
23
+ def resource_id
24
+ # @zfs_pool is the zfs pool name assigned during initialization in the parent class i.e. ZfsPool
25
+ @zfs_pool
26
+ end
27
+
28
+ # Resource appearance in test reports.
29
+ def to_s
30
+ "zfs #{resource_id}"
31
+ end
32
+
33
+ # The below matcher checks if the given properties are valid properties of the zfs pool.
34
+ def has_property?(properties_hash)
35
+ raise Inspec::Exceptions::ResourceSkipped, "Provide a valid key-value pair of the zfs properties." if properties_hash.empty?
36
+
37
+ # Transform all the key & values provided by user to string,
38
+ # since hash keys can be symbols or strings & values can be integers or strings.
39
+ # @params is a hash populated in the parent class with the properties(key-value) of the current zfs pool.
40
+ # and the key-value in @params are of string type.
41
+ properties_hash.transform_keys(&:to_s)
42
+ properties_hash.transform_values(&:to_s)
43
+
44
+ # check if the given properties is a subset of @params
45
+ properties_hash <= @params
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "5.10.5".freeze
2
+ VERSION = "5.17.4".freeze
3
3
  end
@@ -41,9 +41,9 @@ module InspecPlugins::StreamingReporterProgressBar
41
41
  # Groovy UTF-8 characters for everyone else...
42
42
  # ...even though they probably only work on Mac
43
43
  INDICATORS = {
44
- "failed" => "×",
45
- "skipped" => "↺",
46
- "passed" => "✔",
44
+ "failed" => "× [FAILED] ",
45
+ "skipped" => "↺ [SKIPPED]",
46
+ "passed" => "✔ [PASSED] ",
47
47
  }.freeze
48
48
  end
49
49
 
@@ -54,58 +54,69 @@ module InspecPlugins::StreamingReporterProgressBar
54
54
  end
55
55
 
56
56
  def example_passed(notification)
57
- control_id = notification.example.metadata[:id]
58
- set_status_mapping(control_id, "passed")
59
- show_progress(control_id) if control_ended?(control_id)
57
+ set_example(notification, "passed")
60
58
  end
61
59
 
62
60
  def example_failed(notification)
63
- control_id = notification.example.metadata[:id]
64
- set_status_mapping(control_id, "failed")
65
- show_progress(control_id) if control_ended?(control_id)
61
+ set_example(notification, "failed")
66
62
  end
67
63
 
68
64
  def example_pending(notification)
69
- control_id = notification.example.metadata[:id]
70
- set_status_mapping(control_id, "skipped")
71
- show_progress(control_id) if control_ended?(control_id)
65
+ set_example(notification, "skipped")
72
66
  end
73
67
 
74
68
  private
75
69
 
76
- def show_progress(control_id)
70
+ def set_example(notification, status)
71
+ control_id = notification.example.metadata[:id]
72
+ title = notification.example.metadata[:title]
73
+ full_description = notification.example.metadata[:full_description]
74
+ control_impact = notification.example.metadata[:impact]
75
+ set_status_mapping(control_id, status)
76
+ show_progress(control_id, title, full_description, control_impact) if control_ended?(control_id)
77
+ end
78
+
79
+ def show_progress(control_id, title, full_description, control_impact)
77
80
  @bar ||= ProgressBar.new(controls_count, :bar, :counter, :percentage)
78
81
  sleep 0.1
79
82
  @bar.increment!
80
- @bar.puts format_it(control_id)
81
- rescue Exception => ex
82
- raise "Exception in Progress Bar streaming reporter: #{ex}"
83
+ @bar.puts format_it(control_id, title, full_description, control_impact)
84
+ rescue StandardError => e
85
+ raise "Exception in Progress Bar streaming reporter: #{e}"
83
86
  end
84
87
 
85
- def format_it(control_id)
88
+ def format_it(control_id, title, full_description, control_impact)
86
89
  control_status = if @status_mapping[control_id].include? "failed"
87
90
  "failed"
88
- elsif @status_mapping[control_id].include? "skipped"
89
- "skipped"
90
91
  elsif @status_mapping[control_id].include? "passed"
91
92
  "passed"
93
+ else
94
+ @status_mapping[control_id].include? "skipped"
95
+ "skipped"
92
96
  end
93
-
94
97
  indicator = INDICATORS[control_status]
95
98
  message_to_format = ""
96
99
  message_to_format += "#{indicator} "
97
- message_to_format += control_id.to_s.lstrip.force_encoding(Encoding::UTF_8)
100
+ message_to_format += "#{control_id.to_s.strip.dup.force_encoding(Encoding::UTF_8)} "
101
+ message_to_format += "#{title.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " if title
102
+ message_to_format += "#{full_description.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " unless title
98
103
  format_with_color(control_status, message_to_format)
104
+ rescue Exception => e
105
+ raise "Exception in show_progress: #{e}"
99
106
  end
100
107
 
101
108
  def format_with_color(color_name, text)
102
109
  "#{COLORS[color_name]}#{text}#{COLORS["reset"]}"
110
+ rescue StandardError => e
111
+ raise "Exception in format_with_color: #{e}"
103
112
  end
104
113
 
105
114
  # status mapping with control id to decide the final state of the control
106
115
  def set_status_mapping(control_id, status)
107
116
  @status_mapping[control_id] = [] if @status_mapping[control_id].nil?
108
117
  @status_mapping[control_id].push(status)
118
+ rescue StandardError => e
119
+ raise "Exception in format_with_color: #{e}"
109
120
  end
110
121
 
111
122
  end