inspec 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8af69e7763f2f41cea47b857805512d1987e8565
4
- data.tar.gz: 74e15150e9b1f2b6ad90040f3f7e1d07da788e3a
3
+ metadata.gz: b9e7420ad169ccfbbc41d2c3228ae4a4b1c0a4e8
4
+ data.tar.gz: bdfcc40cedc98aa75bca9cfc8625e4206e491003
5
5
  SHA512:
6
- metadata.gz: 615bfc38930bc317de961e0d8d48a4f4f0e7143a010417d8fd3af9db2d62ee0ade358d1e5d182e5d1dcc73c142ec46d14c9bcc4588c4ffde81a98b878418fca6
7
- data.tar.gz: e40becad8f8b2277293c1bea194863a97b425d2c757f64394a542b264d8ea82cd33a091209d48792202767a0f2c67318b535c760cbfdc0d48ecc9d36e9f183de
6
+ metadata.gz: 08855a6d58f7b60a0ddab0a219fedcabf1bb79bec29c296e7002f20671527e018abd1e66c9e720c7d8c736b63ab4732f582e6579a0ba29780a3dadc6c173006c
7
+ data.tar.gz: f38a61a25c35899a5761ed90ef52d0093bf6ed40ad8226315f9a12b0878d4f0eac8b96af6e1815c63f15d497d3a7646dd54ef7ecfd713d833547bfcee64aba8b
data/CHANGELOG.md CHANGED
@@ -1,7 +1,33 @@
1
1
  # Change Log
2
2
 
3
- ## [1.7.2](https://github.com/chef/inspec/tree/1.7.2) (2016-12-08)
4
- [Full Changelog](https://github.com/chef/inspec/compare/v1.7.1...1.7.2)
3
+ ## [1.8.0](https://github.com/chef/inspec/tree/1.8.0) (2016-12-16)
4
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.7.2...1.8.0)
5
+
6
+ **Fixed bugs:**
7
+
8
+ - JSON resource's error message is missing filename when file is not found [\#1358](https://github.com/chef/inspec/issues/1358)
9
+
10
+ **Closed issues:**
11
+
12
+ - inspec exec doesn't work for SSH [\#1361](https://github.com/chef/inspec/issues/1361)
13
+ - inspec-archive should create a zip with version string [\#1350](https://github.com/chef/inspec/issues/1350)
14
+ - https and security conserns on inspec.io [\#1217](https://github.com/chef/inspec/issues/1217)
15
+ - Resource for windows scheduled task [\#1214](https://github.com/chef/inspec/issues/1214)
16
+ - Last steps for inspec.io launch [\#1114](https://github.com/chef/inspec/issues/1114)
17
+
18
+ **Merged pull requests:**
19
+
20
+ - add fallback syntax for serverspec tests [\#1367](https://github.com/chef/inspec/pull/1367) ([chris-rock](https://github.com/chris-rock))
21
+ - JSON resource's error message is missing filename when file is not found [\#1366](https://github.com/chef/inspec/pull/1366) ([makotots](https://github.com/makotots))
22
+ - extending cmp to support better version ops [\#1364](https://github.com/chef/inspec/pull/1364) ([jeremymv2](https://github.com/jeremymv2))
23
+ - add windows 10 and windows 2016 as supported platform [\#1359](https://github.com/chef/inspec/pull/1359) ([chris-rock](https://github.com/chris-rock))
24
+ - Remove trailing `\\` from iis\_site example [\#1354](https://github.com/chef/inspec/pull/1354) ([jerryaldrichiii](https://github.com/jerryaldrichiii))
25
+ - Adds additional tutorials created by Annie Hedgie [\#1352](https://github.com/chef/inspec/pull/1352) ([burtlo](https://github.com/burtlo))
26
+ - Add profile version to archive [\#1351](https://github.com/chef/inspec/pull/1351) ([jaxxstorm](https://github.com/jaxxstorm))
27
+ - burtlo/cleaner cli formatter [\#1331](https://github.com/chef/inspec/pull/1331) ([burtlo](https://github.com/burtlo))
28
+
29
+ ## [v1.7.2](https://github.com/chef/inspec/tree/v1.7.2) (2016-12-08)
30
+ [Full Changelog](https://github.com/chef/inspec/compare/v1.7.1...v1.7.2)
5
31
 
6
32
  **Fixed bugs:**
7
33
 
data/README.md CHANGED
@@ -265,7 +265,7 @@ Mac OS X | 10.9, 10.10, 10.11 | x86_64
265
265
  Oracle Enterprise Linux | 5, 6, 7 | i386, x86_64
266
266
  Red Hat Enterprise Linux | 5, 6, 7 | i386, x86_64
267
267
  Solaris | 10, 11 | sparc, x86
268
- Windows | 7, 8, 8.1, 2008*, 2008R2* , 2012, 2012R2 | x86, x86_64
268
+ Windows | 7, 8, 8.1, 10, 2008, 2008R2 , 2012, 2012R2, 2016 | x86, x86_64
269
269
  Ubuntu Linux | | x86, x86_64
270
270
  SUSE Linux Enterprise Server | 11, 12 | x86_64
271
271
  Scientific Linux | 5.x, 6.x and 7.x | i386, x86_64
@@ -276,7 +276,7 @@ Gentoo Linux | | x86_64
276
276
  Arch Linux | | x86_64
277
277
  HP-UX | 11.31 | ia64
278
278
 
279
- * For Windows 2008 and 2008 R2 an updated Powershell (Windows Management Framework 5.0) is required.
279
+ *For Windows 2008 and 2008 R2 an updated Powershell (Windows Management Framework 5.0) is required.*
280
280
 
281
281
  In addition, runtime support is provided for:
282
282
 
@@ -131,7 +131,7 @@ The following examples show how to use this InSpec audit resource.
131
131
  it { should be_running }
132
132
  it { should have_app_pool('DefaultAppPool') }
133
133
  it { should have_binding('http *:80:') }
134
- it { should have_path('%SystemDrive%\\inetpub\\wwwroot\\') }
134
+ it { should have_path('%SystemDrive%\\inetpub\\wwwroot') }
135
135
  end
136
136
 
137
137
  ### Test if IIS service is running
@@ -399,9 +399,12 @@ module Inspec
399
399
  name = params[:name] ||
400
400
  fail('Cannot create an archive without a profile name! Please '\
401
401
  'specify the name in metadata or use --output to create the archive.')
402
+ version = params[:version] ||
403
+ fail('Cannot create an archive without a profile version! Please '\
404
+ 'specify the version in metadata or use --output to create the archive.')
402
405
  ext = opts[:zip] ? 'zip' : 'tar.gz'
403
406
  slug = name.downcase.strip.tr(' ', '-').gsub(/[^\w-]/, '_')
404
- Pathname.new(Dir.pwd).join("#{slug}.#{ext}")
407
+ Pathname.new(Dir.pwd).join("#{slug}-#{version}.#{ext}")
405
408
  end
406
409
 
407
410
  def load_params
@@ -65,7 +65,7 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
65
65
  private
66
66
 
67
67
  def format_example(example)
68
- if example.metadata[:description_args].length > 0 && !example.metadata[:skip].nil?
68
+ if !example.metadata[:description_args].empty? && example.metadata[:skip]
69
69
  # For skipped profiles, rspec returns in full_description the skip_message as well. We don't want
70
70
  # to mix the two, so we pick the full_description from the example.metadata[:example_group] hash.
71
71
  code_description = example.metadata[:example_group][:description]
@@ -75,6 +75,7 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
75
75
 
76
76
  res = {
77
77
  id: example.metadata[:id],
78
+ profile_id: example.metadata[:profile_id],
78
79
  status: example.execution_result.status.to_s,
79
80
  code_desc: code_description,
80
81
  }
@@ -94,13 +95,12 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
94
95
  end
95
96
 
96
97
  class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLength
97
- RSpec::Core::Formatters.register self, :start, :stop, :dump_summary
98
+ RSpec::Core::Formatters.register self, :stop, :dump_summary
98
99
  attr_writer :backend
99
100
 
100
101
  def initialize(*args)
101
102
  super(*args)
102
103
  @profiles = []
103
- # Will be valid after "start" state is reached.
104
104
  @profiles_info = nil
105
105
  @backend = nil
106
106
  end
@@ -110,37 +110,25 @@ class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLengt
110
110
  @profiles.push(profile)
111
111
  end
112
112
 
113
- # Called after all examples have been collected but before rspec
114
- # test execution has begun.
115
- def start(_notification)
116
- # Note that the default profile may have no name - therefore
117
- # the hash may have a valid nil => entry.
118
- @profiles_info = @profiles.map(&:info!).map(&:dup)
119
- end
120
-
121
- def dump_one_example(example, control)
122
- control[:results] ||= []
123
- example.delete(:id)
124
- example.delete(:profile_id)
125
- control[:results].push(example)
126
- end
127
-
128
113
  def stop(notification)
129
114
  super(notification)
130
- examples = @output_hash.delete(:controls)
131
- missing = []
132
115
 
133
- examples.each do |example|
134
- control = example2control(example, @profiles_info)
135
- next missing.push(example) if control.nil?
136
- dump_one_example(example, control)
116
+ @output_hash[:other_checks] = examples_without_controls
117
+ @output_hash[:profiles] = profiles_info
118
+
119
+ examples_with_controls.each do |example|
120
+ control = example2control(example)
121
+ move_example_into_control(example, control)
137
122
  end
123
+ end
124
+
125
+ private
138
126
 
139
- @output_hash[:profiles] = @profiles_info
140
- @output_hash[:other_checks] = missing
127
+ def all_unique_controls
128
+ Array(@all_controls).uniq
141
129
  end
142
130
 
143
- def controls_summary
131
+ def profile_summary
144
132
  failed = 0
145
133
  skipped = 0
146
134
  passed = 0
@@ -148,7 +136,7 @@ class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLengt
148
136
  major = 0
149
137
  minor = 0
150
138
 
151
- @control_tests.each do |control|
139
+ all_unique_controls.each do |control|
152
140
  next if control[:id].start_with? '(generated from '
153
141
  next unless control[:results]
154
142
  if control[:results].any? { |r| r[:status] == 'failed' }
@@ -186,8 +174,7 @@ class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLengt
186
174
  skipped = 0
187
175
  passed = 0
188
176
 
189
- all_tests = @anonymous_tests + @control_tests
190
- all_tests.each do |control|
177
+ all_unique_controls.each do |control|
191
178
  next unless control[:results]
192
179
  control[:results].each do |result|
193
180
  if result[:status] == 'failed'
@@ -203,37 +190,43 @@ class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLengt
203
190
  { 'total' => total, 'failed' => failed, 'skipped' => skipped, 'passed' => passed }
204
191
  end
205
192
 
206
- private
193
+ def examples
194
+ @output_hash[:controls]
195
+ end
207
196
 
208
- #
209
- # TODO(ssd+vj): We should probably solve this by either ensuring the example has
210
- # the profile_id of the top level profile when it is included as a dependency, or
211
- # by registrying all dependent profiles with the formatter. The we could remove
212
- # this heuristic matching.
213
- #
214
- def example2profile(example, profiles)
215
- profiles.find { |p| profile_contains_example?(p, example) }
197
+ def examples_without_controls
198
+ examples.find_all { |example| example2control(example).nil? }
216
199
  end
217
200
 
218
- def profile_contains_example?(profile, example)
219
- # Heuristic for finding the profile an example came from:
220
- # Case 1: The profile_id on the example matches the name of the profile
221
- # Case 2: The profile contains a control that matches the id of the example
222
- if profile[:name] == example[:profile_id]
223
- true
224
- elsif profile[:controls] && profile[:controls].any? { |x| x[:id] == example[:id] }
225
- true
226
- else
227
- false
228
- end
201
+ def examples_with_controls
202
+ (examples - examples_without_controls)
203
+ end
204
+
205
+ def profiles_info
206
+ @profiles_info ||= @profiles.map(&:info!).map(&:dup)
229
207
  end
230
208
 
231
- def example2control(example, profiles)
232
- profile = example2profile(example, profiles)
209
+ def example2control(example)
210
+ profile = profile_from_example(example)
233
211
  return nil unless profile && profile[:controls]
234
212
  profile[:controls].find { |x| x[:id] == example[:id] }
235
213
  end
236
214
 
215
+ def profile_from_example(example)
216
+ profiles_info.find { |p| profile_contains_example?(p, example) }
217
+ end
218
+
219
+ def profile_contains_example?(profile, example)
220
+ profile[:name] == example[:profile_id]
221
+ end
222
+
223
+ def move_example_into_control(example, control)
224
+ control[:results] ||= []
225
+ example.delete(:id)
226
+ example.delete(:profile_id)
227
+ control[:results].push(example)
228
+ end
229
+
237
230
  def format_example(example)
238
231
  super(example).tap do |res|
239
232
  res[:run_time] = example.execution_result.run_time
@@ -245,16 +238,6 @@ end
245
238
  class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
246
239
  RSpec::Core::Formatters.register self, :close
247
240
 
248
- STATUS_TYPES = {
249
- 'unknown' => -3,
250
- 'passed' => -2,
251
- 'skipped' => -1,
252
- 'minor' => 1,
253
- 'major' => 2,
254
- 'failed' => 2.5,
255
- 'critical' => 3,
256
- }.freeze
257
-
258
241
  COLORS = {
259
242
  'critical' => "\033[38;5;9m",
260
243
  'major' => "\033[38;5;208m",
@@ -281,170 +264,303 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
281
264
 
282
265
  def initialize(*args)
283
266
  @current_control = nil
284
- @anonymous_tests = []
285
- @control_tests = []
267
+ @all_controls = []
286
268
  @profile_printed = false
287
269
  super(*args)
288
270
  end
289
271
 
290
- def close(_notification) # rubocop:disable Metrics/AbcSize
291
- flush_current_control(@current_control)
292
- output.puts('') unless @current_control.nil?
293
- print_tests(@anonymous_tests)
294
- output.puts('')
272
+ #
273
+ # This method is called through the RSpec Formatter interface for every
274
+ # example found in the test suite.
275
+ #
276
+ # Within #format_example we are getting and example and:
277
+ # * if this is an example, within a control, within a profile then we want
278
+ # to display the profile header, display the control, and then display
279
+ # the example.
280
+ # * if this is another example, within the same control, within the same
281
+ # profile we want to display the example.
282
+ # * if this is an example that does not map to a control (anonymous) then
283
+ # we want to store it for later to displayed at the end of a profile.
284
+ #
285
+ def format_example(example)
286
+ example_data = super(example)
287
+ control = create_or_find_control(example_data)
288
+
289
+ # If we are switching to a new control then we want to print the control
290
+ # we were previously collecting examples unless the last control is
291
+ # anonymous (no control). Anonymous controls and their examples are handled
292
+ # later on the profile change.
295
293
 
296
- print_profiles_info(@current_control) if !@profile_printed
297
- controls_res = controls_summary
298
- tests_res = tests_summary
294
+ if switching_to_new_control?(control)
295
+ print_last_control_with_examples unless last_control_is_anonymous?
296
+ end
299
297
 
300
- s = format('Profile Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
301
- COLORS['passed'], controls_res['passed'], COLORS['reset'],
302
- COLORS['failed'], controls_res['failed']['total'], COLORS['reset'],
303
- COLORS['skipped'], controls_res['skipped'], COLORS['reset'])
304
- output.puts(s) if controls_res['total'] > 0
298
+ store_last_control(control)
305
299
 
306
- s = format('Test Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
307
- COLORS['passed'], tests_res['passed'], COLORS['reset'],
308
- COLORS['failed'], tests_res['failed'], COLORS['reset'],
309
- COLORS['skipped'], tests_res['skipped'], COLORS['reset'])
310
- output.puts(s) if !@anonymous_tests.empty? || @current_control.nil?
300
+ # Each profile may have zero or more anonymous examples. These are examples
301
+ # that defined in a profile but outside of a control. They may be defined
302
+ # at the start, in-between, or end of list of examples. To display them
303
+ # at the very end of a profile, which means we have to wait for the profile
304
+ # to change to know we are done with a profile.
305
+
306
+ if switching_to_new_profile?(control.profile)
307
+ output.puts ''
308
+ print_anonymous_examples_associated_with_last_profile
309
+ clear_anonymous_examples_associated_with_last_profile
310
+ end
311
+
312
+ print_profile(control.profile)
313
+ store_last_profile(control.profile)
314
+
315
+ # The anonymous controls should be added to a hash that we will display
316
+ # when we are done examining all the examples within this profile.
317
+
318
+ if control.anonymous?
319
+ add_anonymous_example_within_this_profile(control.as_hash)
320
+ end
321
+
322
+ @all_controls.push(control.as_hash)
323
+ example_data
324
+ end
325
+
326
+ #
327
+ # This is the last method is invoked through the formatter interface.
328
+ # Because the profile
329
+ # we may have some remaining anonymous examples so we want to display them
330
+ # as well as a summary of the profile and test stats.
331
+ #
332
+ def close(_notification)
333
+ # when the profile has no controls or examples it will not have been printed.
334
+ # then we want to ensure we print all the profiles
335
+ print_last_control_with_examples unless last_control_is_anonymous?
336
+ output.puts ''
337
+ print_anonymous_examples_associated_with_last_profile
338
+ print_profiles_without_examples
339
+ print_profile_summary
340
+ print_tests_summary
311
341
  end
312
342
 
313
343
  private
314
344
 
315
- # Formats example; calls example2control, gets a 'status_type', and calls
316
- # flush current_control; returns control data
317
- def format_example(example)
318
- data = super(example)
319
- control = example2control(data, @profiles_info) || {}
320
- control[:id] = data[:id]
321
- control[:profile_id] = data[:profile_id]
322
-
323
- data[:status_type] = status_type(data, control)
324
- dump_one_example(data, control)
325
-
326
- @current_control ||= control
327
- if !control[:id].nil?
328
- if @current_control[:id] != control[:id]
329
- flush_current_control(@current_control)
330
- @current_control = control
331
- end
345
+ #
346
+ # With the example we can find the profile associated with it and if there
347
+ # is already a control defined. If there is one then we will use that data
348
+ # to build our control object. If there isn't we simply create a new hash of
349
+ # controld data that will be populated from the examples that are found.
350
+ #
351
+ # @return [Control] A new control or one found associated with the example.
352
+ #
353
+ def create_or_find_control(example)
354
+ profile = profile_from_example(example)
355
+
356
+ control_data = {}
357
+
358
+ if profile && profile[:controls]
359
+ control_data = profile[:controls].find { |ctrl| ctrl[:id] == example[:id] }
332
360
  end
333
361
 
334
- data
362
+ control = Control.new(control_data, profile)
363
+ control.add_example(example)
364
+
365
+ control
335
366
  end
336
367
 
337
- # Determines 'status_type' (critical, major, minor) of control given
338
- # status (failed/passed/skipped) and impact value (0.0 - 1.0).
339
- # Called from format_example, sets the 'status_type' for each 'example'
340
- def status_type(data, control)
341
- status = data[:status]
342
- return status if status != 'failed' || control[:impact].nil?
343
- if control[:impact] >= 0.7
344
- 'critical'
345
- elsif control[:impact] >= 0.4
346
- 'major'
347
- else
348
- 'minor'
368
+ #
369
+ # If there is already a control we have have seen before and it is different
370
+ # than the new control then we are indeed switching controls.
371
+ #
372
+ def switching_to_new_control?(control)
373
+ @last_control && @last_control != control
374
+ end
375
+
376
+ def store_last_control(control)
377
+ @last_control = control
378
+ end
379
+
380
+ def print_last_control_with_examples
381
+ if @last_control
382
+ print_control(@last_control)
383
+ @last_control.examples.each { |example| print_result(example) }
384
+ end
385
+ end
386
+
387
+ def last_control_is_anonymous?
388
+ @last_control && @last_control.anonymous?
389
+ end
390
+
391
+ #
392
+ # If there is a profile we have seen before and it is different than the
393
+ # new profile then we are indeed switching profiles.
394
+ #
395
+ def switching_to_new_profile?(new_profile)
396
+ @last_profile && @last_profile != new_profile
397
+ end
398
+
399
+ #
400
+ # Print all the anonymous examples that have been found for this profile
401
+ #
402
+ def print_anonymous_examples_associated_with_last_profile
403
+ Array(anonymous_examples_within_this_profile).uniq.each do |control|
404
+ print_anonymous_control(control)
349
405
  end
406
+ output.puts '' unless Array(anonymous_examples_within_this_profile).empty?
407
+ end
408
+
409
+ #
410
+ # As we process examples we need an accumulator that will allow us to store
411
+ # all the examples that do not have a named control associated with them.
412
+ #
413
+ def anonymous_examples_within_this_profile
414
+ @anonymous_examples_within_this_profile ||= []
350
415
  end
351
416
 
352
- # TODO: does a lot of stuff!
353
- # Called from format_example and close
354
- def flush_current_control(current_control)
355
- return if current_control.nil?
417
+ #
418
+ # Remove all controls from the anonymous examples that are tracked.
419
+ #
420
+ def clear_anonymous_examples_associated_with_last_profile
421
+ @anonymous_examples_within_this_profile = []
422
+ end
356
423
 
357
- profile = @profiles_info.find { |i| i[:id] == current_control[:profile_id] }
358
- print_current_profile(profile) if !@profile_printed
424
+ #
425
+ # Append a new control to the anonymous examples
426
+ #
427
+ def add_anonymous_example_within_this_profile(control)
428
+ anonymous_examples_within_this_profile.push(control)
429
+ end
359
430
 
360
- fails, skips, passes, summary_indicator = current_control_infos(current_control)
361
- summary = current_control_summary(fails, skips, current_control)
431
+ def store_last_profile(new_profile)
432
+ @last_profile = new_profile
433
+ end
362
434
 
363
- control_id = current_control[:id].to_s
364
- control_id += ': '
365
- if control_id.start_with? '(generated from '
366
- @anonymous_tests.push(current_control)
435
+ #
436
+ # Print the profile
437
+ #
438
+ # * For anonymous profiles, where are generated for examples and controls
439
+ # defined outside of a profile, simply display the target information
440
+ # * For profiles without a title use the name (or 'unknown'), version,
441
+ # and target information.
442
+ # * For all other profiles display the title with name (or 'unknown'),
443
+ # version, and target information.
444
+ #
445
+ def print_profile(profile)
446
+ return if profile.nil? || profile[:already_printed]
447
+ output.puts ''
448
+
449
+ if profile[:name].nil?
450
+ print_target
451
+ profile[:already_printed] = true
452
+ return
453
+ end
454
+
455
+ if profile[:title].nil?
456
+ output.puts "Profile: #{profile[:name] || 'unknown'}"
367
457
  else
368
- @control_tests.push(current_control)
458
+ output.puts "Profile: #{profile[:title]} (#{profile[:name] || 'unknown'})"
459
+ end
460
+
461
+ output.puts 'Version: ' + (profile[:version] || 'unknown')
462
+ print_target
463
+ profile[:already_printed] = true
464
+ end
465
+
466
+ def print_profiles_without_examples
467
+ profiles_info.reject { |p| p[:already_printed] }.each do |profile|
468
+ print_profile(profile)
369
469
  print_line(
370
- color: COLORS[summary_indicator] || '',
371
- indicator: INDICATORS[summary_indicator] || INDICATORS['unknown'],
372
- summary: format_lines(summary, INDICATORS['empty']),
373
- id: control_id,
374
- profile: current_control[:profile_id],
470
+ color: '', indicator: INDICATORS['empty'], id: '', profile: '',
471
+ summary: 'No tests executed.'
375
472
  )
473
+ output.puts ''
474
+ end
475
+ end
476
+
477
+ #
478
+ # This target information displays which system that came under test
479
+ #
480
+ def print_target
481
+ return if @backend.nil?
482
+ connection = @backend.backend
483
+ return unless connection.respond_to?(:uri)
484
+ output.puts('Target: ' + connection.uri + "\n\n")
485
+ end
376
486
 
377
- print_results(fails + skips + passes)
378
- end
379
- end
380
-
381
- ############# Current control methods #############
382
-
383
- # Takes current_control (called from flush_current_control) and returns
384
- # fails, skips, passes.
385
- def current_control_infos(current_control)
386
- summary_status = STATUS_TYPES['unknown']
387
- skips = []
388
- fails = []
389
- passes = []
390
- current_control[:results].each do |r|
391
- i = STATUS_TYPES[r[:status_type]]
392
- summary_status = i if i > summary_status
393
- fails.push(r) if i > 0
394
- passes.push(r) if i == STATUS_TYPES['passed']
395
- skips.push(r) if i == STATUS_TYPES['skipped']
396
- end
397
- [fails, skips, passes, STATUS_TYPES.key(summary_status)]
398
- end
399
-
400
- # Determine title for control given current_control.
401
- # Called from current_control_summary.
402
- def current_control_title(current_control)
403
- title = current_control[:title]
404
- res = current_control[:results]
405
- if title
406
- title
407
- elsif res.length == 1
408
- # If it's an anonymous control, just go with the only description
409
- # available for the underlying test.
410
- res[0][:code_desc].to_s
411
- elsif res.length == 0
412
- # Empty control block - if it's anonymous, there's nothing we can do.
413
- # Is this case even possible?
414
- 'Empty anonymous control'
487
+ #
488
+ # We want to print the details about the control
489
+ #
490
+ def print_control(control)
491
+ print_line(
492
+ color: COLORS[control.summary_indicator] || '',
493
+ indicator: INDICATORS[control.summary_indicator] || INDICATORS['unknown'],
494
+ summary: format_lines(control.summary, INDICATORS['empty']),
495
+ id: "#{control.id}: ",
496
+ profile: control.profile_id,
497
+ )
498
+ end
499
+
500
+ def print_result(result)
501
+ test_status = result[:status_type]
502
+ test_color = COLORS[test_status]
503
+ indicator = INDICATORS[result[:status]]
504
+ indicator = INDICATORS['empty'] if indicator.nil?
505
+ if result[:message]
506
+ msg = result[:code_desc] + "\n" + result[:message]
415
507
  else
416
- # Multiple tests - but no title. Do our best and generate some form of
417
- # identifier or label or name.
418
- title = (res.map { |r| r[:code_desc] }).join('; ')
419
- max_len = MULTI_TEST_CONTROL_SUMMARY_MAX_LEN
420
- title = title[0..(max_len-1)] + '...' if title.length > max_len
421
- title
422
- end
423
- end
424
-
425
- # Return summary of current_control, called from flush_current_control
426
- def current_control_summary(fails, skips, current_control)
427
- title = current_control_title(current_control)
428
- res = current_control[:results]
429
- suffix =
430
- if res.length == 1
431
- # Single test - be nice and just print the exception message if the test
432
- # failed. No need to say "1 failed".
433
- res[0][:message].to_s
508
+ msg = result[:skip_message] || result[:code_desc]
509
+ end
510
+ print_line(
511
+ color: test_color,
512
+ indicator: INDICATORS['small'] + indicator,
513
+ summary: format_lines(msg, INDICATORS['empty']),
514
+ id: nil, profile: nil
515
+ )
516
+ end
517
+
518
+ def print_anonymous_control(control)
519
+ control_result = control[:results]
520
+ title = control_result[0][:code_desc].split[0..1].join(' ')
521
+ puts ' ' + title
522
+ # iterate over all describe blocks in anonoymous control block
523
+ control_result.each do |test|
524
+ control_id = ''
525
+ # display exceptions
526
+ unless test[:exception].nil?
527
+ test_result = test[:message]
434
528
  else
435
- [
436
- (fails.length > 0) ? "#{fails.length} failed" : nil,
437
- (skips.length > 0) ? "#{skips.length} skipped" : nil,
438
- ].compact.join(' ')
529
+ # determine title
530
+ test_result = test[:skip_message] || test[:code_desc].split[2..-1].join(' ')
531
+ # show error message
532
+ test_result += "\n" + test[:message] unless test[:message].nil?
439
533
  end
440
- if suffix == ''
441
- title
442
- else
443
- title + ' (' + suffix + ')'
534
+ status_indicator = test[:status_type]
535
+ print_line(
536
+ color: COLORS[status_indicator] || '',
537
+ indicator: INDICATORS['small'] + INDICATORS[status_indicator] || INDICATORS['unknown'],
538
+ summary: format_lines(test_result, INDICATORS['empty']),
539
+ id: control_id,
540
+ profile: control[:profile_id],
541
+ )
444
542
  end
445
543
  end
446
544
 
447
- ############# Print results and lines methods #############
545
+ def print_profile_summary
546
+ summary = profile_summary
547
+
548
+ s = format('Profile Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
549
+ COLORS['passed'], summary['passed'], COLORS['reset'],
550
+ COLORS['failed'], summary['failed']['total'], COLORS['reset'],
551
+ COLORS['skipped'], summary['skipped'], COLORS['reset'])
552
+ output.puts(s) if summary['total'] > 0
553
+ end
554
+
555
+ def print_tests_summary
556
+ summary = tests_summary
557
+
558
+ s = format('Test Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
559
+ COLORS['passed'], summary['passed'], COLORS['reset'],
560
+ COLORS['failed'], summary['failed'], COLORS['reset'],
561
+ COLORS['skipped'], summary['skipped'], COLORS['reset'])
562
+ output.puts(s)
563
+ end
448
564
 
449
565
  # Formats the line (called from print_line)
450
566
  def format_line(fields)
@@ -465,106 +581,170 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
465
581
  lines.gsub(/\n/, "\n" + indentation)
466
582
  end
467
583
 
468
- # Sorts through results, calls print_line (called from flush_current_control)
469
- def print_results(all)
470
- all.each do |x|
471
- test_status = x[:status_type]
472
- test_color = COLORS[test_status]
473
- indicator = INDICATORS[x[:status]]
474
- indicator = INDICATORS['empty'] if indicator.nil?
475
- if x[:message]
476
- msg = x[:code_desc] + "\n" + x[:message]
584
+ #
585
+ # This class wraps a control hash object to provide a useful inteface for
586
+ # maintaining the associated profile, ids, results, title, etc.
587
+ #
588
+ class Control # rubocop:disable Metrics/ClassLength
589
+ include Comparable
590
+
591
+ STATUS_TYPES = {
592
+ 'unknown' => -3,
593
+ 'passed' => -2,
594
+ 'skipped' => -1,
595
+ 'minor' => 1,
596
+ 'major' => 2,
597
+ 'failed' => 2.5,
598
+ 'critical' => 3,
599
+ }.freeze
600
+
601
+ def initialize(control, profile)
602
+ @control = control
603
+ @profile = profile
604
+ summary_calculation_is_needed
605
+ end
606
+
607
+ attr_reader :control, :profile
608
+
609
+ alias as_hash control
610
+
611
+ def id
612
+ control[:id]
613
+ end
614
+
615
+ def anonymous?
616
+ control[:id].to_s.start_with? '(generated from '
617
+ end
618
+
619
+ def profile_id
620
+ control[:profile_id]
621
+ end
622
+
623
+ def examples
624
+ control[:results]
625
+ end
626
+
627
+ def summary_indicator
628
+ calculate_summary! if summary_calculation_needed?
629
+ STATUS_TYPES.key(@summary_status)
630
+ end
631
+
632
+ def add_example(example)
633
+ control[:id] = example[:id]
634
+ control[:profile_id] = example[:profile_id]
635
+
636
+ example[:status_type] = status_type(example)
637
+ example.delete(:id)
638
+ example.delete(:profile_id)
639
+
640
+ control[:results] ||= []
641
+ control[:results].push(example)
642
+ summary_calculation_is_needed
643
+ end
644
+
645
+ # Determine title for control given current_control.
646
+ # Called from current_control_summary.
647
+ def title
648
+ title = control[:title]
649
+ if title
650
+ title
651
+ elsif examples.length == 1
652
+ # If it's an anonymous control, just go with the only description
653
+ # available for the underlying test.
654
+ examples[0][:code_desc].to_s
655
+ elsif examples.empty?
656
+ # Empty control block - if it's anonymous, there's nothing we can do.
657
+ # Is this case even possible?
658
+ 'Empty anonymous control'
477
659
  else
478
- msg = x[:skip_message] || x[:code_desc]
660
+ # Multiple tests - but no title. Do our best and generate some form of
661
+ # identifier or label or name.
662
+ title = (examples.map { |example| example[:code_desc] }).join('; ')
663
+ max_len = MULTI_TEST_CONTROL_SUMMARY_MAX_LEN
664
+ title = title[0..(max_len-1)] + '...' if title.length > max_len
665
+ title
479
666
  end
480
- print_line(
481
- color: test_color,
482
- indicator: INDICATORS['small'] + indicator,
483
- summary: format_lines(msg, INDICATORS['empty']),
484
- id: nil, profile: nil
485
- )
486
667
  end
487
- end
488
668
 
489
- # Prints anonymous describe blocks; called from close method
490
- def print_tests(anonymous_tests) # rubocop:disable Metrics/AbcSize
491
- anonymous_tests.each do |control|
492
- control_result = control[:results]
493
- title = control_result[0][:code_desc].split[0..1].join(' ')
494
- puts ' ' + title
495
- # iterate over all describe blocks in anonoymous control block
496
- control_result.each do |test|
497
- control_id = ''
498
- # display exceptions
499
- unless test[:exception].nil?
500
- test_result = test[:message]
669
+ # Return summary of the control which is usually a title with fails and skips
670
+ def summary
671
+ calculate_summary! if summary_calculation_needed?
672
+ suffix =
673
+ if examples.length == 1
674
+ # Single test - be nice and just print the exception message if the test
675
+ # failed. No need to say "1 failed".
676
+ examples[0][:message].to_s
501
677
  else
502
- # determine title
503
- test_result = test[:skip_message] || test[:code_desc].split[2..-1].join(' ')
504
- # show error message
505
- test_result += "\n" + test[:message] unless test[:message].nil?
678
+ [
679
+ !fails.empty? ? "#{fails.uniq.length} failed" : nil,
680
+ !skips.empty? ? "#{skips.uniq.length} skipped" : nil,
681
+ ].compact.join(' ')
506
682
  end
507
- status_indicator = test[:status_type]
508
- print_line(
509
- color: COLORS[status_indicator] || '',
510
- indicator: INDICATORS['small'] + INDICATORS[status_indicator] || INDICATORS['unknown'],
511
- summary: format_lines(test_result, INDICATORS['empty']),
512
- id: control_id,
513
- profile: control[:profile_id],
514
- )
683
+
684
+ suffix == '' ? title : title + ' (' + suffix + ')'
685
+ end
686
+
687
+ # We are interested in comparing controls against other controls. It is
688
+ # important to compare their id values and the id values of their profiles.
689
+ # In the event that a control has the same id in a different profile we
690
+ # do not want them to be considered the same.
691
+ #
692
+ # Controls are never ordered so we don't care about the remaining
693
+ # implementation of the spaceship operator.
694
+ #
695
+ def <=>(other)
696
+ if id == other.id && profile_id == other.profile_id
697
+ 0
698
+ else
699
+ -1
515
700
  end
516
701
  end
517
- end
518
702
 
519
- ############# Print profile info methods #############
703
+ private
520
704
 
521
- # Prints target information; called from print_current_profile
522
- def print_target
523
- return if @backend.nil?
524
- connection = @backend.backend
525
- return unless connection.respond_to?(:uri)
526
- output.puts('Target: ' + connection.uri + "\n\n")
527
- end
705
+ attr_reader :summary_calculation_needed, :skips, :fails, :passes
528
706
 
529
- # Prints blank info is no current_control is defined
530
- # Called from print_current_profile and close
531
- def print_profiles_info(current_control)
532
- @profiles_info.each do |profile|
533
- next if profile[:already_printed]
534
- next unless print_current_profile(profile)
535
- print_line(
536
- color: '', indicator: INDICATORS['empty'], id: '', profile: '',
537
- summary: 'No tests executed.'
538
- ) if current_control.nil?
539
- output.puts('')
707
+ alias summary_calculation_needed? summary_calculation_needed
708
+
709
+ def summary_calculation_is_needed
710
+ @summary_calculation_needed = true
540
711
  end
541
- end
542
712
 
543
- def print_current_profile(profile)
544
- if profile.nil?
545
- print_profiles_info(@current_control)
546
- @profile_printed = true
547
- return true
713
+ def summary_has_been_calculated
714
+ @summary_calculation_needed = false
548
715
  end
549
- output.puts ''
550
- profile[:already_printed] = true
551
716
 
552
- if profile[:name].nil?
553
- print_target
554
- @profile_printed = true
555
- return true
717
+ def calculate_summary!
718
+ @summary_status = STATUS_TYPES['unknown']
719
+ @skips = []
720
+ @fails = []
721
+ @passes = []
722
+ examples.each { |example| update_summary(example) }
723
+ summary_has_been_calculated
556
724
  end
557
725
 
558
- if profile[:title].nil?
559
- output.puts "Profile: #{profile[:name] || 'unknown'}"
560
- else
561
- output.puts "Profile: #{profile[:title]} (#{profile[:name] || 'unknown'})"
726
+ def update_summary(example)
727
+ example_status = STATUS_TYPES[example[:status_type]]
728
+ @summary_status = example_status if example_status > @summary_status
729
+ fails.push(example) if example_status > 0
730
+ passes.push(example) if example_status == STATUS_TYPES['passed']
731
+ skips.push(example) if example_status == STATUS_TYPES['skipped']
562
732
  end
563
733
 
564
- output.puts 'Version: ' + (profile[:version] || 'unknown')
565
- print_target
566
- @profile_printed = true
567
- true
734
+ # Determines 'status_type' (critical, major, minor) of control given
735
+ # status (failed/passed/skipped) and impact value (0.0 - 1.0).
736
+ # Called from format_example, sets the 'status_type' for each 'example'
737
+ def status_type(example)
738
+ status = example[:status]
739
+ return status if status != 'failed' || control[:impact].nil?
740
+ if control[:impact] >= 0.7
741
+ 'critical'
742
+ elsif control[:impact] >= 0.4
743
+ 'major'
744
+ else
745
+ 'minor'
746
+ end
747
+ end
568
748
  end
569
749
  end
570
750
 
data/lib/inspec/runner.rb CHANGED
@@ -80,7 +80,12 @@ module Inspec
80
80
  @test_collector.add_profile(profile)
81
81
  write_lockfile(profile) if @create_lockfile
82
82
  profile.locked_dependencies
83
- profile.load_libraries
83
+ profile_context = profile.load_libraries
84
+
85
+ profile_context.dependencies.list.values.each do |requirement|
86
+ @test_collector.add_profile(requirement.profile)
87
+ end
88
+
84
89
  @attributes |= profile.runner_context.attributes
85
90
  all_controls += profile.collect_tests
86
91
  end
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '1.7.2'.freeze
7
+ VERSION = '1.8.0'.freeze
8
8
  end
@@ -252,6 +252,13 @@ RSpec::Matchers.define :cmp do |first_expected|
252
252
  %w{true false}.include?(value.downcase)
253
253
  end
254
254
 
255
+ def version?(value)
256
+ Gem::Version.new(value)
257
+ true
258
+ rescue ArgumentError => _ex
259
+ false
260
+ end
261
+
255
262
  # expects that the values have been checked with boolean?
256
263
  def to_boolean(value)
257
264
  value.casecmp('true') == 0
@@ -261,6 +268,8 @@ RSpec::Matchers.define :cmp do |first_expected|
261
268
  # if actual and expected are strings
262
269
  if expected.is_a?(String) && actual.is_a?(String)
263
270
  return actual.casecmp(expected) == 0 if op == :==
271
+ return Gem::Version.new(actual).method(op).call(Gem::Version.new(expected)) if
272
+ version?(expected) && version?(actual)
264
273
  elsif expected.is_a?(Regexp) && (actual.is_a?(String) || actual.is_a?(Integer))
265
274
  return !actual.to_s.match(expected).nil?
266
275
  elsif expected.is_a?(String) && integer?(expected) && actual.is_a?(Integer)
@@ -121,4 +121,27 @@ module Inspec::Resources
121
121
  info
122
122
  end
123
123
  end
124
+
125
+ # for compatability with serverspec
126
+ # this is deprecated syntax and will be removed in future versions
127
+ class IisSiteServerSpec < IisSite
128
+ name 'iis_website'
129
+ desc 'Tests IIS site configuration on windows. Deprecated, use `iis_site` instead.'
130
+ example "
131
+ describe iis_website('Default Website') do
132
+ it{ should exist }
133
+ it{ should be_running }
134
+ it{ should be_in_app_pool('Default App Pool') }
135
+ end
136
+ "
137
+
138
+ def initialize(site_name)
139
+ super(site_name)
140
+ warn '[DEPRECATION] `iis_website(site_name)` is deprecated. Please use `iis_site(site_name)` instead.'
141
+ end
142
+
143
+ def in_app_pool?(app_pool)
144
+ has_app_pool?(app_pool)
145
+ end
146
+ end
124
147
  end
@@ -44,13 +44,13 @@ module Inspec::Resources
44
44
 
45
45
  # check if file is available
46
46
  if !@file.file?
47
- skip_resource "Can't find file \"#{@conf_path}\""
47
+ skip_resource "Can't find file \"#{@path}\""
48
48
  return @params = {}
49
49
  end
50
50
 
51
51
  # check if file is readable
52
52
  if @file_content.empty? && @file.size > 0
53
- skip_resource "Can't read file \"#{@conf_path}\""
53
+ skip_resource "Can't read file \"#{@path}\""
54
54
  return @params = {}
55
55
  end
56
56
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-08 00:00:00.000000000 Z
11
+ date: 2016-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: train