inspec 0.31.0 → 0.32.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- elsif File.exist?("#{path}.tar.gz")
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"
@@ -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
- # get all spec files
36
- target = opts[:dependencies].list[opts[:profile_id]] ||
37
- fail("Can't find profile #{opts[:profile_id].inspect}, please add it as a dependency.")
38
- profile = Inspec::Profile.for_target(target.path, opts)
39
- context = load_profile_context(opts[:profile_id], profile, opts)
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, opts, &block) if !opts[:include_all]
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
- # finally register all combined rules
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, opts, &block)
62
+ def self.filter_included_controls(context, profile, &block)
55
63
  mock = Inspec::Backend.create({ backend: 'mock' })
56
- include_ctx = Inspec::ProfileContext.new(opts[:profile_id], mock, opts[:conf])
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(id, profile, opts)
67
- ctx = Inspec::ProfileContext.new(id, opts[:backend], opts[:conf])
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
@@ -7,11 +7,5 @@ module Inspec
7
7
 
8
8
  # dependency resolution
9
9
  class CyclicDependencyError < Error; end
10
- class VersionConflict < Error
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
@@ -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 'rainbow/ext/string'
5
+ require 'mixlib/log'
7
6
 
8
7
  module Inspec
9
8
  class Log
10
- def initialize(opts = {})
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
@@ -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
- 'specify the name in metadata or use --output to create the archive.')
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
- if @runner_context.nil?
248
- # we're checking a profile, we don't care if it runs on the host machine
249
- opts = @options.dup
250
- opts[:ignore_supports] = true
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
- cwd = File.directory?(@target) ? @target : nil
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
- attr_reader :rules
14
- attr_reader :attributes
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
- 'Profile Context Run'
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
- }.freeze
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 print_fails_and_skips(all, color)
346
+ def print_results(all)
348
347
  all.each do |x|
349
- indicator = @test_indicators[x[:status]]
350
- indicator = @test_indicators['empty'] if all.length == 1 || indicator.nil?
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: color,
354
- indicator: indicator,
355
- summary: format_lines(msg, @test_indicators['empty']),
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
- control_id = '' if control_id.start_with? '(generated from '
374
-
375
- print_line(
376
- color: @colors[summary_indicator] || '',
377
- indicator: @indicators[summary_indicator] || @indicators['unknown'],
378
- summary: format_lines(summary, @indicators['empty']),
379
- id: control_id,
380
- profile: @current_control[:profile_id],
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
- print_fails_and_skips(fails + skips, @colors[summary_indicator] || '')
405
+ print_results(fails + skips + passes)
406
+ end
384
407
  end
385
408
 
386
409
  def print_target(before, after)