inspec 0.31.0 → 0.32.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +56 -2
- data/Gemfile +6 -2
- data/MAINTAINERS.md +3 -1
- data/MAINTAINERS.toml +1 -1
- data/README.md +20 -1
- data/Rakefile +8 -0
- data/docs/cli.rst +18 -2
- data/docs/resources.rst +55 -3
- data/inspec.gemspec +2 -2
- data/lib/bundles/inspec-supermarket/api.rb +1 -0
- data/lib/fetchers/local.rb +12 -1
- data/lib/fetchers/tar.rb +4 -0
- data/lib/fetchers/url.rb +4 -0
- data/lib/inspec/base_cli.rb +17 -0
- data/lib/inspec/cli.rb +33 -12
- data/lib/inspec/dependencies/dependency_set.rb +50 -5
- data/lib/inspec/dependencies/lockfile.rb +94 -0
- data/lib/inspec/dependencies/requirement.rb +93 -53
- data/lib/inspec/dependencies/resolver.rb +53 -170
- data/lib/inspec/dependencies/vendor_index.rb +11 -4
- data/lib/inspec/dsl.rb +23 -15
- data/lib/inspec/errors.rb +1 -7
- data/lib/inspec/log.rb +2 -25
- data/lib/inspec/profile.rb +68 -28
- data/lib/inspec/profile_context.rb +28 -5
- data/lib/inspec/rspec_json_formatter.rb +48 -25
- data/lib/inspec/rule.rb +7 -0
- data/lib/inspec/runner.rb +26 -15
- data/lib/inspec/runner_rspec.rb +2 -6
- data/lib/inspec/shell.rb +35 -26
- data/lib/inspec/version.rb +2 -1
- data/lib/resources/host.rb +13 -6
- data/lib/resources/iis_site.rb +1 -0
- data/lib/resources/os.rb +1 -1
- data/lib/resources/package.rb +22 -6
- data/lib/resources/port.rb +1 -11
- data/lib/resources/service.rb +9 -0
- data/lib/resources/user.rb +8 -8
- metadata +14 -7
@@ -18,9 +18,9 @@ module Inspec
|
|
18
18
|
#
|
19
19
|
class VendorIndex
|
20
20
|
attr_reader :path
|
21
|
-
def initialize(path)
|
22
|
-
@path = path
|
23
|
-
FileUtils.mkdir_p(path) unless File.directory?(path)
|
21
|
+
def initialize(path = nil)
|
22
|
+
@path = path || File.join(Dir.home, '.inspec', 'cache')
|
23
|
+
FileUtils.mkdir_p(@path) unless File.directory?(@path)
|
24
24
|
end
|
25
25
|
|
26
26
|
def add(name, source, path_from)
|
@@ -42,7 +42,14 @@ module Inspec
|
|
42
42
|
path = base_path_for(name, source_url)
|
43
43
|
if File.directory?(path)
|
44
44
|
path
|
45
|
-
|
45
|
+
else
|
46
|
+
archive_entry_for(name, source_url)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def archive_entry_for(name, source_url)
|
51
|
+
path = base_path_for(name, source_url)
|
52
|
+
if File.exist?("#{path}.tar.gz")
|
46
53
|
"#{path}.tar.gz"
|
47
54
|
elsif File.exist?("#{path}.zip")
|
48
55
|
"#{path}.zip"
|
data/lib/inspec/dsl.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# copyright: 2015, Dominik Richter
|
3
|
-
# license: All rights reserved
|
4
3
|
# author: Dominik Richter
|
5
4
|
# author: Christoph Hartmann
|
5
|
+
require 'inspec/log'
|
6
6
|
|
7
7
|
module Inspec::DSL
|
8
8
|
def require_controls(id, &block)
|
@@ -32,28 +32,36 @@ module Inspec::DSL
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def self.load_spec_files_for_profile(bind_context, opts, &block)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
dependencies = opts[:dependencies]
|
36
|
+
profile_id = opts[:profile_id]
|
37
|
+
|
38
|
+
dep_entry = dependencies.list[profile_id]
|
39
|
+
if dep_entry.nil?
|
40
|
+
fail <<EOF
|
41
|
+
Cannot load #{profile_id} since it is not listed as a dependency
|
42
|
+
of #{bind_context.profile_name}.
|
43
|
+
|
44
|
+
Dependencies available from this context are:
|
45
|
+
|
46
|
+
#{dependencies.list.keys.join("\n ")}
|
47
|
+
EOF
|
48
|
+
end
|
49
|
+
|
50
|
+
context = load_profile_context(dep_entry.profile, opts[:backend])
|
40
51
|
|
41
52
|
# if we don't want all the rules, then just make 1 pass to get all rule_IDs
|
42
53
|
# that we want to keep from the original
|
43
|
-
filter_included_controls(context,
|
54
|
+
filter_included_controls(context, dep_entry.profile, &block) if !opts[:include_all]
|
44
55
|
|
45
56
|
# interpret the block and skip/modify as required
|
46
57
|
context.load(block) if block_given?
|
47
58
|
|
48
|
-
|
49
|
-
context.rules.values.each do |control|
|
50
|
-
bind_context.register_control(control)
|
51
|
-
end
|
59
|
+
bind_context.add_subcontext(context)
|
52
60
|
end
|
53
61
|
|
54
|
-
def self.filter_included_controls(context,
|
62
|
+
def self.filter_included_controls(context, profile, &block)
|
55
63
|
mock = Inspec::Backend.create({ backend: 'mock' })
|
56
|
-
include_ctx = Inspec::ProfileContext.
|
64
|
+
include_ctx = Inspec::ProfileContext.for_profile(profile, mock)
|
57
65
|
include_ctx.load(block) if block_given?
|
58
66
|
# remove all rules that were not registered
|
59
67
|
context.rules.keys.each do |id|
|
@@ -63,8 +71,8 @@ module Inspec::DSL
|
|
63
71
|
end
|
64
72
|
end
|
65
73
|
|
66
|
-
def self.load_profile_context(
|
67
|
-
ctx = Inspec::ProfileContext.
|
74
|
+
def self.load_profile_context(profile, backend)
|
75
|
+
ctx = Inspec::ProfileContext.for_profile(profile, backend)
|
68
76
|
profile.libraries.each do |path, content|
|
69
77
|
ctx.load(content.to_s, path, 1)
|
70
78
|
ctx.reload_dsl
|
data/lib/inspec/errors.rb
CHANGED
@@ -7,11 +7,5 @@ module Inspec
|
|
7
7
|
|
8
8
|
# dependency resolution
|
9
9
|
class CyclicDependencyError < Error; end
|
10
|
-
class
|
11
|
-
attr_reader :conflicts
|
12
|
-
def initialize(conflicts, msg = nil)
|
13
|
-
super(msg)
|
14
|
-
@conflicts = conflicts
|
15
|
-
end
|
16
|
-
end
|
10
|
+
class UnsatisfiedVersionSpecification < Error; end
|
17
11
|
end
|
data/lib/inspec/log.rb
CHANGED
@@ -1,34 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
# Copyright 2015 Dominik Richter. All rights reserved.
|
3
2
|
# author: Dominik Richter
|
4
3
|
# author: Christoph Hartmann
|
5
4
|
|
6
|
-
require '
|
5
|
+
require 'mixlib/log'
|
7
6
|
|
8
7
|
module Inspec
|
9
8
|
class Log
|
10
|
-
|
11
|
-
@quiet = opts[:quiet] || false
|
12
|
-
end
|
13
|
-
|
14
|
-
def show(msg)
|
15
|
-
puts msg unless @quiet
|
16
|
-
end
|
17
|
-
|
18
|
-
def info(msg)
|
19
|
-
show ' . '.color(:white) + msg
|
20
|
-
end
|
21
|
-
|
22
|
-
def error(msg)
|
23
|
-
show ' ✖ '.color(:red).bright + msg
|
24
|
-
end
|
25
|
-
|
26
|
-
def warn(msg)
|
27
|
-
show ' ! '.color(:yellow).bright + msg
|
28
|
-
end
|
29
|
-
|
30
|
-
def ok(msg)
|
31
|
-
show ' ✔ '.color(:green).bright + msg
|
32
|
-
end
|
9
|
+
extend Mixlib::Log
|
33
10
|
end
|
34
11
|
end
|
data/lib/inspec/profile.rb
CHANGED
@@ -8,6 +8,7 @@ require 'inspec/polyfill'
|
|
8
8
|
require 'inspec/fetcher'
|
9
9
|
require 'inspec/source_reader'
|
10
10
|
require 'inspec/metadata'
|
11
|
+
require 'inspec/dependencies/lockfile'
|
11
12
|
require 'inspec/dependencies/dependency_set'
|
12
13
|
|
13
14
|
module Inspec
|
@@ -52,6 +53,10 @@ module Inspec
|
|
52
53
|
Metadata.finalize(@source_reader.metadata, @profile_id)
|
53
54
|
end
|
54
55
|
|
56
|
+
def name
|
57
|
+
metadata.params[:name]
|
58
|
+
end
|
59
|
+
|
55
60
|
def params
|
56
61
|
@params ||= load_params
|
57
62
|
end
|
@@ -211,6 +216,44 @@ module Inspec
|
|
211
216
|
@locked_dependencies ||= load_dependencies
|
212
217
|
end
|
213
218
|
|
219
|
+
def lockfile_exists?
|
220
|
+
File.exist?(lockfile_path)
|
221
|
+
end
|
222
|
+
|
223
|
+
def lockfile_path
|
224
|
+
File.join(cwd, 'inspec.lock')
|
225
|
+
end
|
226
|
+
|
227
|
+
#
|
228
|
+
# TODO(ssd): Relative path handling really needs to be carefully
|
229
|
+
# thought through, especially with respect to relative paths in
|
230
|
+
# tarballs.
|
231
|
+
#
|
232
|
+
def cwd
|
233
|
+
@target.is_a?(String) && File.directory?(@target) ? @target : './'
|
234
|
+
end
|
235
|
+
|
236
|
+
def lockfile
|
237
|
+
@lockfile ||= if lockfile_exists?
|
238
|
+
Inspec::Lockfile.from_file(lockfile_path)
|
239
|
+
else
|
240
|
+
generate_lockfile
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
#
|
245
|
+
# Generate an in-memory lockfile. This won't render the lock file
|
246
|
+
# to disk, it must be explicitly written to disk by the caller.
|
247
|
+
#
|
248
|
+
# @param vendor_path [String] Path to the on-disk vendor dir
|
249
|
+
# @return [Inspec::Lockfile]
|
250
|
+
#
|
251
|
+
def generate_lockfile(vendor_path = nil)
|
252
|
+
res = Inspec::DependencySet.new(cwd, vendor_path)
|
253
|
+
res.vendor(metadata.dependencies)
|
254
|
+
Inspec::Lockfile.from_dependency_set(res)
|
255
|
+
end
|
256
|
+
|
214
257
|
private
|
215
258
|
|
216
259
|
# Create an archive name for this profile and an additional options
|
@@ -225,7 +268,7 @@ module Inspec
|
|
225
268
|
|
226
269
|
name = params[:name] ||
|
227
270
|
fail('Cannot create an archive without a profile name! Please '\
|
228
|
-
|
271
|
+
'specify the name in metadata or use --output to create the archive.')
|
229
272
|
ext = opts[:zip] ? 'zip' : 'tar.gz'
|
230
273
|
slug = name.downcase.strip.tr(' ', '-').gsub(/[^\w-]/, '_')
|
231
274
|
Pathname.new(Dir.pwd).join("#{slug}.#{ext}")
|
@@ -239,34 +282,34 @@ module Inspec
|
|
239
282
|
params
|
240
283
|
end
|
241
284
|
|
285
|
+
#
|
286
|
+
# Returns a new runner for the current profile.
|
287
|
+
#
|
288
|
+
# @params [Symbol] The type of backend to use when constructing a
|
289
|
+
# new runner.
|
290
|
+
# @returns [Inspec::Runner]
|
291
|
+
#
|
292
|
+
def runner_for_profile(backend = :mock)
|
293
|
+
opts = @options.dup
|
294
|
+
opts[:ignore_supports] = true
|
295
|
+
r = Runner.new(id: @profile_id,
|
296
|
+
backend: backend,
|
297
|
+
test_collector: opts.delete(:test_collector))
|
298
|
+
r.add_profile(self, opts)
|
299
|
+
r
|
300
|
+
end
|
301
|
+
|
242
302
|
def load_checks_params(params)
|
243
303
|
params[:controls] = controls = {}
|
244
304
|
params[:groups] = groups = {}
|
245
305
|
prefix = @source_reader.target.prefix || ''
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
runner = Runner.new(
|
252
|
-
id: @profile_id,
|
253
|
-
backend: :mock,
|
254
|
-
test_collector: opts.delete(:test_collector),
|
255
|
-
)
|
256
|
-
runner.add_profile(self, opts)
|
257
|
-
runner.rules.values.each do |rule|
|
258
|
-
f = load_rule_filepath(prefix, rule)
|
259
|
-
load_rule(rule, f, controls, groups)
|
260
|
-
end
|
261
|
-
params[:attributes] = runner.attributes
|
262
|
-
else
|
263
|
-
# load from context
|
264
|
-
@runner_context.rules.values.each do |rule|
|
265
|
-
f = load_rule_filepath(prefix, rule)
|
266
|
-
load_rule(rule, f, controls, groups)
|
267
|
-
end
|
268
|
-
params[:attributes] = @runner_context.attributes
|
306
|
+
# Load from the runner_context if it exists
|
307
|
+
runner = @runner_context || runner_for_profile
|
308
|
+
runner.all_rules.each do |rule|
|
309
|
+
f = load_rule_filepath(prefix, rule)
|
310
|
+
load_rule(rule, f, controls, groups)
|
269
311
|
end
|
312
|
+
params[:attributes] = runner.attributes
|
270
313
|
params
|
271
314
|
end
|
272
315
|
|
@@ -297,10 +340,7 @@ module Inspec
|
|
297
340
|
end
|
298
341
|
|
299
342
|
def load_dependencies
|
300
|
-
|
301
|
-
res = Inspec::DependencySet.new(cwd, nil)
|
302
|
-
res.vendor(metadata.dependencies)
|
303
|
-
res
|
343
|
+
Inspec::DependencySet.from_lockfile(lockfile, cwd, nil)
|
304
344
|
end
|
305
345
|
end
|
306
346
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# author: Dominik Richter
|
3
3
|
# author: Christoph Hartmann
|
4
|
-
|
4
|
+
require 'inspec/log'
|
5
5
|
require 'inspec/rule'
|
6
6
|
require 'inspec/dsl'
|
7
7
|
require 'inspec/require_loader'
|
@@ -10,18 +10,21 @@ require 'inspec/objects/attribute'
|
|
10
10
|
|
11
11
|
module Inspec
|
12
12
|
class ProfileContext # rubocop:disable Metrics/ClassLength
|
13
|
-
|
14
|
-
|
13
|
+
def self.for_profile(profile, backend)
|
14
|
+
new(profile.name, backend, { 'profile' => profile })
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :attributes, :rules, :profile_id
|
15
18
|
def initialize(profile_id, backend, conf)
|
16
19
|
if backend.nil?
|
17
20
|
fail 'ProfileContext is initiated with a backend == nil. ' \
|
18
21
|
'This is a backend error which must be fixed upstream.'
|
19
22
|
end
|
20
|
-
|
21
23
|
@profile_id = profile_id
|
22
24
|
@backend = backend
|
23
25
|
@conf = conf.dup
|
24
26
|
@rules = {}
|
27
|
+
@subcontexts = []
|
25
28
|
@dependencies = {}
|
26
29
|
@dependencies = conf['profile'].locked_dependencies unless conf['profile'].nil?
|
27
30
|
@require_loader = ::Inspec::RequireLoader.new
|
@@ -35,6 +38,16 @@ module Inspec
|
|
35
38
|
@profile_context = ctx.new(@backend, @conf, @dependencies, @require_loader)
|
36
39
|
end
|
37
40
|
|
41
|
+
def all_rules
|
42
|
+
ret = @rules.values
|
43
|
+
ret += @subcontexts.map(&:all_rules).flatten
|
44
|
+
ret
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_subcontext(context)
|
48
|
+
@subcontexts << context
|
49
|
+
end
|
50
|
+
|
38
51
|
def load_libraries(libs)
|
39
52
|
lib_prefix = 'libraries' + File::SEPARATOR
|
40
53
|
autoloads = []
|
@@ -59,6 +72,7 @@ module Inspec
|
|
59
72
|
end
|
60
73
|
|
61
74
|
def load(content, source = nil, line = nil)
|
75
|
+
Inspec::Log.debug("Loading #{source || '<anonymous content>'} into #{self}")
|
62
76
|
@current_load = { file: source }
|
63
77
|
if content.is_a? Proc
|
64
78
|
@profile_context.instance_eval(&content)
|
@@ -167,7 +181,11 @@ module Inspec
|
|
167
181
|
end
|
168
182
|
|
169
183
|
def to_s
|
170
|
-
|
184
|
+
"Profile Context Run #{profile_name}"
|
185
|
+
end
|
186
|
+
|
187
|
+
define_method :profile_name do
|
188
|
+
profile_id
|
171
189
|
end
|
172
190
|
|
173
191
|
define_method :control do |*args, &block|
|
@@ -185,9 +203,14 @@ module Inspec
|
|
185
203
|
res = describe(*args, &block)
|
186
204
|
end
|
187
205
|
register_control(rule, &block)
|
206
|
+
|
188
207
|
res
|
189
208
|
end
|
190
209
|
|
210
|
+
define_method :add_subcontext do |context|
|
211
|
+
profile_context_owner.add_subcontext(context)
|
212
|
+
end
|
213
|
+
|
191
214
|
define_method :register_control do |control, &block|
|
192
215
|
::Inspec::Rule.set_skip_rule(control, true) if @skip_profile
|
193
216
|
|
@@ -210,12 +210,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
210
210
|
'passed' => ' ✔ ',
|
211
211
|
'unknown' => ' ? ',
|
212
212
|
'empty' => ' ',
|
213
|
-
|
214
|
-
|
215
|
-
TEST_INDICATORS = {
|
216
|
-
'failed' => ' fail: ',
|
217
|
-
'skipped' => ' skip: ',
|
218
|
-
'empty' => ' ',
|
213
|
+
'small' => ' ',
|
219
214
|
}.freeze
|
220
215
|
|
221
216
|
MULTI_TEST_CONTROL_SUMMARY_MAX_LEN = 60
|
@@ -223,18 +218,20 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
223
218
|
def initialize(*args)
|
224
219
|
@colors = COLORS
|
225
220
|
@indicators = INDICATORS
|
226
|
-
@test_indicators = TEST_INDICATORS
|
227
221
|
|
228
222
|
@format = '%color%indicator%id%summary'
|
229
223
|
@current_control = nil
|
230
224
|
@current_profile = nil
|
231
225
|
@missing_controls = []
|
226
|
+
@anonymous_tests = []
|
232
227
|
super(*args)
|
233
228
|
end
|
234
229
|
|
235
230
|
def close(_notification)
|
236
231
|
flush_current_control
|
237
232
|
output.puts('') unless @current_control.nil?
|
233
|
+
print_tests
|
234
|
+
output.puts('')
|
238
235
|
|
239
236
|
@profiles_info.each do |_id, profile|
|
240
237
|
next if profile[:already_printed]
|
@@ -274,13 +271,15 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
274
271
|
summary_status = STATUS_TYPES['unknown']
|
275
272
|
skips = []
|
276
273
|
fails = []
|
274
|
+
passes = []
|
277
275
|
@current_control[:results].each do |r|
|
278
276
|
i = STATUS_TYPES[r[:status_type]]
|
279
277
|
summary_status = i if i > summary_status
|
280
278
|
fails.push(r) if i > 0
|
279
|
+
passes.push(r) if i == STATUS_TYPES['passed']
|
281
280
|
skips.push(r) if i == STATUS_TYPES['skipped']
|
282
281
|
end
|
283
|
-
[fails, skips, STATUS_TYPES.key(summary_status)]
|
282
|
+
[fails, skips, passes, STATUS_TYPES.key(summary_status)]
|
284
283
|
end
|
285
284
|
|
286
285
|
def current_control_title
|
@@ -344,20 +343,42 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
344
343
|
lines.gsub(/\n/, "\n" + indentation)
|
345
344
|
end
|
346
345
|
|
347
|
-
def
|
346
|
+
def print_results(all)
|
348
347
|
all.each do |x|
|
349
|
-
|
350
|
-
|
348
|
+
test_status = x[:status_type]
|
349
|
+
test_color = @colors[test_status]
|
350
|
+
indicator = @indicators[x[:status]]
|
351
|
+
indicator = @indicators['empty'] if all.length == 1 || indicator.nil?
|
351
352
|
msg = x[:message] || x[:skip_message] || x[:code_desc]
|
352
353
|
print_line(
|
353
|
-
color:
|
354
|
-
indicator: indicator,
|
355
|
-
summary: format_lines(msg, @
|
354
|
+
color: test_color,
|
355
|
+
indicator: @indicators['small'] + indicator,
|
356
|
+
summary: format_lines(msg, @indicators['empty']),
|
356
357
|
id: nil, profile: nil
|
357
358
|
)
|
358
359
|
end
|
359
360
|
end
|
360
361
|
|
362
|
+
def print_tests
|
363
|
+
@anonymous_tests.each do |control|
|
364
|
+
control_result = control[:results]
|
365
|
+
title = control_result[0][:code_desc].split[0..1].join(' ')
|
366
|
+
puts ' ' + title
|
367
|
+
control_result.each do |test|
|
368
|
+
control_id = ''
|
369
|
+
test_result = test[:code_desc].split[2..-1].join(' ')
|
370
|
+
status_indicator = test[:status_type]
|
371
|
+
print_line(
|
372
|
+
color: @colors[status_indicator] || '',
|
373
|
+
indicator: @indicators['small'] + @indicators[status_indicator] || @indicators['unknown'],
|
374
|
+
summary: format_lines(test_result, @indicators['empty']),
|
375
|
+
id: control_id,
|
376
|
+
profile: control[:profile_id],
|
377
|
+
)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
361
382
|
def flush_current_control
|
362
383
|
return if @current_control.nil?
|
363
384
|
|
@@ -365,22 +386,24 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
365
386
|
@current_profile = @profiles_info[@current_control[:profile_id]]
|
366
387
|
print_current_profile if prev_profile != @current_profile
|
367
388
|
|
368
|
-
fails, skips, summary_indicator = current_control_infos
|
389
|
+
fails, skips, passes, summary_indicator = current_control_infos
|
369
390
|
summary = current_control_summary(fails, skips)
|
370
391
|
|
371
392
|
control_id = @current_control[:id].to_s
|
372
393
|
control_id += ': '
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
394
|
+
if control_id.start_with? '(generated from '
|
395
|
+
@anonymous_tests.push(@current_control)
|
396
|
+
else
|
397
|
+
print_line(
|
398
|
+
color: @colors[summary_indicator] || '',
|
399
|
+
indicator: @indicators[summary_indicator] || @indicators['unknown'],
|
400
|
+
summary: format_lines(summary, @indicators['empty']),
|
401
|
+
id: control_id,
|
402
|
+
profile: @current_control[:profile_id],
|
403
|
+
)
|
382
404
|
|
383
|
-
|
405
|
+
print_results(fails + skips + passes)
|
406
|
+
end
|
384
407
|
end
|
385
408
|
|
386
409
|
def print_target(before, after)
|