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 +4 -4
- data/CHANGELOG.md +28 -2
- data/README.md +2 -2
- data/docs/resources/iis_site.md.erb +1 -1
- data/lib/inspec/profile.rb +4 -1
- data/lib/inspec/rspec_json_formatter.rb +456 -276
- data/lib/inspec/runner.rb +6 -1
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +9 -0
- data/lib/resources/iis_site.rb +23 -0
- data/lib/resources/json.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9e7420ad169ccfbbc41d2c3228ae4a4b1c0a4e8
|
4
|
+
data.tar.gz: bdfcc40cedc98aa75bca9cfc8625e4206e491003
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
4
|
-
[Full Changelog](https://github.com/chef/inspec/compare/v1.7.
|
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
|
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
|
-
*
|
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
|
data/lib/inspec/profile.rb
CHANGED
@@ -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].
|
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, :
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
140
|
-
@
|
127
|
+
def all_unique_controls
|
128
|
+
Array(@all_controls).uniq
|
141
129
|
end
|
142
130
|
|
143
|
-
def
|
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
|
-
|
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
|
-
|
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
|
-
|
193
|
+
def examples
|
194
|
+
@output_hash[:controls]
|
195
|
+
end
|
207
196
|
|
208
|
-
|
209
|
-
|
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
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
232
|
-
profile =
|
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
|
-
@
|
285
|
-
@control_tests = []
|
267
|
+
@all_controls = []
|
286
268
|
@profile_printed = false
|
287
269
|
super(*args)
|
288
270
|
end
|
289
271
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
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
|
-
|
297
|
-
|
298
|
-
|
294
|
+
if switching_to_new_control?(control)
|
295
|
+
print_last_control_with_examples unless last_control_is_anonymous?
|
296
|
+
end
|
299
297
|
|
300
|
-
|
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
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
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
|
-
#
|
316
|
-
#
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
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
|
-
|
362
|
+
control = Control.new(control_data, profile)
|
363
|
+
control.add_example(example)
|
364
|
+
|
365
|
+
control
|
335
366
|
end
|
336
367
|
|
337
|
-
#
|
338
|
-
#
|
339
|
-
#
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
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
|
-
#
|
353
|
-
#
|
354
|
-
|
355
|
-
|
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
|
-
|
358
|
-
|
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
|
-
|
361
|
-
|
431
|
+
def store_last_profile(new_profile)
|
432
|
+
@last_profile = new_profile
|
433
|
+
end
|
362
434
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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
|
-
|
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:
|
371
|
-
|
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
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
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
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
def
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
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
|
-
|
437
|
-
|
438
|
-
].
|
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
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
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
|
-
|
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
|
-
#
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
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
|
-
|
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
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
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
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
678
|
+
[
|
679
|
+
!fails.empty? ? "#{fails.uniq.length} failed" : nil,
|
680
|
+
!skips.empty? ? "#{skips.uniq.length} skipped" : nil,
|
681
|
+
].compact.join(' ')
|
506
682
|
end
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
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
|
-
|
703
|
+
private
|
520
704
|
|
521
|
-
|
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
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
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
|
-
|
544
|
-
|
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
|
-
|
553
|
-
|
554
|
-
@
|
555
|
-
|
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
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
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
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
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
|
data/lib/inspec/version.rb
CHANGED
data/lib/matchers/matchers.rb
CHANGED
@@ -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)
|
data/lib/resources/iis_site.rb
CHANGED
@@ -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
|
data/lib/resources/json.rb
CHANGED
@@ -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 \"#{@
|
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 \"#{@
|
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.
|
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-
|
11
|
+
date: 2016-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: train
|