inspec 0.35.0 → 1.0.0.beta2

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -2
  3. data/Gemfile +6 -0
  4. data/Rakefile +3 -55
  5. data/docs/README.md +20 -0
  6. data/docs/cli.rst +6 -0
  7. data/docs/dsl_inspec.md +245 -0
  8. data/docs/dsl_resource.md +93 -0
  9. data/docs/inspec_and_friends.md +102 -0
  10. data/docs/matchers.md +136 -0
  11. data/docs/plugin_kitchen_inspec.html.md +55 -0
  12. data/docs/profiles.md +271 -0
  13. data/docs/resources.rst +1 -1
  14. data/docs/shell.md +150 -0
  15. data/inspec.gemspec +1 -1
  16. data/lib/bundles/inspec-compliance/api.rb +28 -18
  17. data/lib/bundles/inspec-compliance/cli.rb +19 -27
  18. data/lib/fetchers/git.rb +4 -0
  19. data/lib/fetchers/local.rb +16 -1
  20. data/lib/fetchers/mock.rb +4 -0
  21. data/lib/fetchers/url.rb +40 -12
  22. data/lib/inspec/base_cli.rb +4 -0
  23. data/lib/inspec/cli.rb +6 -8
  24. data/lib/inspec/control_eval_context.rb +8 -0
  25. data/lib/inspec/dependencies/{vendor_index.rb → cache.rb} +5 -4
  26. data/lib/inspec/dependencies/dependency_set.rb +8 -14
  27. data/lib/inspec/dependencies/requirement.rb +10 -20
  28. data/lib/inspec/dependencies/resolver.rb +2 -2
  29. data/lib/inspec/dsl.rb +9 -0
  30. data/lib/inspec/fetcher.rb +1 -1
  31. data/lib/inspec/objects/test.rb +8 -2
  32. data/lib/inspec/plugins/fetcher.rb +11 -12
  33. data/lib/inspec/plugins/resource.rb +3 -0
  34. data/lib/inspec/profile.rb +60 -14
  35. data/lib/inspec/profile_context.rb +28 -7
  36. data/lib/inspec/resource.rb +17 -2
  37. data/lib/inspec/rspec_json_formatter.rb +80 -35
  38. data/lib/inspec/runner.rb +42 -18
  39. data/lib/inspec/shell.rb +5 -16
  40. data/lib/inspec/version.rb +1 -1
  41. data/lib/resources/apache_conf.rb +1 -1
  42. data/lib/resources/gem.rb +1 -0
  43. data/lib/resources/oneget.rb +1 -0
  44. data/lib/resources/os.rb +1 -1
  45. data/lib/resources/package.rb +3 -1
  46. data/lib/resources/pip.rb +1 -1
  47. data/lib/resources/ssl.rb +9 -11
  48. metadata +15 -15
  49. data/docs/dsl_inspec.rst +0 -259
  50. data/docs/dsl_resource.rst +0 -90
  51. data/docs/inspec_and_friends.rst +0 -85
  52. data/docs/matchers.rst +0 -137
  53. data/docs/profiles.rst +0 -169
  54. data/docs/readme.rst +0 -105
  55. data/docs/shell.rst +0 -130
  56. data/docs/template.rst +0 -51
@@ -17,7 +17,8 @@ module Inspec
17
17
  'attributes' => attributes })
18
18
  end
19
19
 
20
- attr_reader :attributes, :rules, :profile_id, :resource_registry
20
+ attr_reader :attributes, :profile_id, :resource_registry, :backend
21
+ attr_accessor :rules
21
22
  def initialize(profile_id, backend, conf)
22
23
  if backend.nil?
23
24
  fail 'ProfileContext is initiated with a backend == nil. ' \
@@ -27,7 +28,8 @@ module Inspec
27
28
  @backend = backend
28
29
  @conf = conf.dup
29
30
  @rules = {}
30
- @subcontexts = []
31
+ @control_subcontexts = []
32
+ @lib_subcontexts = []
31
33
  @require_loader = ::Inspec::RequireLoader.new
32
34
  @attributes = []
33
35
  # A local resource registry that only contains resources defined
@@ -45,7 +47,7 @@ module Inspec
45
47
  end
46
48
 
47
49
  def to_resources_dsl
48
- Inspec::Resource.create_dsl(@backend, @resource_registry)
50
+ Inspec::Resource.create_dsl(self)
49
51
  end
50
52
 
51
53
  def control_eval_context
@@ -65,20 +67,34 @@ module Inspec
65
67
  @conf['profile'].supports_os?
66
68
  end
67
69
 
68
- def all_rules
70
+ def all_controls
69
71
  ret = @rules.values
70
- ret += @subcontexts.map(&:all_rules).flatten
72
+ ret += @control_subcontexts.map(&:all_rules).flatten
71
73
  ret
72
74
  end
75
+ alias all_rules all_controls
76
+
77
+ def subcontext_by_name(name)
78
+ found = @lib_subcontexts.find { |c| c.profile_id == name }
79
+ if !found
80
+ @lib_subcontexts.each do |c|
81
+ found = c.subcontext_by_name(name)
82
+ break if found
83
+ end
84
+ end
85
+
86
+ found
87
+ end
73
88
 
74
89
  def add_resources(context)
75
90
  @resource_registry.merge!(context.resource_registry)
76
91
  control_eval_context.add_resources(context)
92
+ @lib_subcontexts << context
77
93
  reload_dsl
78
94
  end
79
95
 
80
96
  def add_subcontext(context)
81
- @subcontexts << context
97
+ @control_subcontexts << context
82
98
  end
83
99
 
84
100
  def load_libraries(libs)
@@ -132,7 +148,12 @@ module Inspec
132
148
 
133
149
  def register_rule(r)
134
150
  # get the full ID
135
- r.instance_variable_set(:@__file, current_load[:file])
151
+ file = if @current_load.nil?
152
+ 'unknown'
153
+ else
154
+ @current_load[:file] || 'unknown'
155
+ end
156
+ r.instance_variable_set(:@__file, file)
136
157
  r.instance_variable_set(:@__group_title, current_load[:title])
137
158
 
138
159
  # add the rule to the registry
@@ -6,6 +6,8 @@
6
6
  require 'inspec/plugins'
7
7
 
8
8
  module Inspec
9
+ class ProfileNotFound < StandardError; end
10
+
9
11
  class Resource
10
12
  def self.default_registry
11
13
  @default_registry ||= {}
@@ -25,9 +27,22 @@ module Inspec
25
27
  #
26
28
  # @param backend [BackendRunner] exposing the target to resources
27
29
  # @return [ResourcesDSL]
28
- def self.create_dsl(backend, my_registry = registry)
29
- # need the local name, to use it in the module creation further down
30
+ def self.create_dsl(profile_context)
31
+ backend = profile_context.backend
32
+ my_registry = profile_context.resource_registry
33
+
30
34
  Module.new do
35
+ define_method :resource_class do |profile_name, resource_name|
36
+ inner_context = if profile_name == profile_context.profile_id
37
+ profile_context
38
+ else
39
+ profile_context.subcontext_by_name(profile_name)
40
+ end
41
+
42
+ fail ProfileNotFound, "Cannot find profile named: #{profile_name}" if inner_context.nil?
43
+ inner_context.resource_registry[resource_name]
44
+ end
45
+
31
46
  my_registry.each do |id, r|
32
47
  define_method id.to_sym do |*args|
33
48
  r.new(backend, id.to_s, *args)
@@ -36,11 +36,8 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
36
36
  # Called after stop has been called and the run is complete.
37
37
  def dump_summary(summary)
38
38
  @output_hash[:version] = Inspec::VERSION
39
- @output_hash[:summary] = {
39
+ @output_hash[:statistics] = {
40
40
  duration: summary.duration,
41
- example_count: summary.example_count,
42
- failure_count: summary.failure_count,
43
- skip_count: summary.pending_count,
44
41
  }
45
42
  end
46
43
 
@@ -86,7 +83,7 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
86
83
  end
87
84
  end
88
85
 
89
- class InspecRspecJson < InspecRspecMiniJson
86
+ class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLength
90
87
  RSpec::Core::Formatters.register self, :start, :stop, :dump_summary
91
88
  attr_writer :backend
92
89
 
@@ -108,7 +105,7 @@ class InspecRspecJson < InspecRspecMiniJson
108
105
  def start(_notification)
109
106
  # Note that the default profile may have no name - therefore
110
107
  # the hash may have a valid nil => entry.
111
- @profiles_info ||= Hash[@profiles.map { |x| profile_info(x) }]
108
+ @profiles_info = @profiles.map(&:info!).map(&:dup)
112
109
  end
113
110
 
114
111
  def dump_one_example(example, control)
@@ -133,20 +130,59 @@ class InspecRspecJson < InspecRspecMiniJson
133
130
  @output_hash[:other_checks] = missing
134
131
  end
135
132
 
136
- def dump_summary(summary)
137
- super(summary)
133
+ def controls_summary
134
+ failed = 0
135
+ skipped = 0
136
+ passed = 0
137
+ critical = 0
138
+ major = 0
139
+ minor = 0
140
+
141
+ @control_tests.each do |control|
142
+ next if control[:id].start_with? '(generated from '
143
+ next unless control[:results]
144
+ if control[:results].any? { |r| r[:status] == 'failed' }
145
+ failed += 1
146
+ if control[:impact] >= 0.7
147
+ critical += 1
148
+ elsif control[:impact] >= 0.4
149
+ major += 1
150
+ else
151
+ minor += 1
152
+ end
153
+ elsif control[:results].any? { |r| r[:status] == 'skipped' }
154
+ skipped += 1
155
+ else
156
+ passed += 1
157
+ end
158
+ end
159
+
160
+ total = failed + passed + skipped
161
+
162
+ { 'total' => total,
163
+ 'failed' => {
164
+ 'total' => failed,
165
+ 'critical' => critical,
166
+ 'major' => major,
167
+ 'minor' => minor,
168
+ },
169
+ 'skipped' => skipped,
170
+ 'passed' => passed }
171
+ end
172
+
173
+ def tests_summary
138
174
  total = 0
139
175
  failed = 0
140
176
  skipped = 0
141
177
  passed = 0
142
178
 
143
- @profiles_info.each do |_name, profile|
144
- total += profile[:controls].length
145
- profile[:controls].each do |_control_name, control|
146
- next unless control[:results]
147
- if control[:results].any? { |r| r[:status] == 'failed' }
179
+ all_tests = @anonymous_tests + @control_tests
180
+ all_tests.each do |control|
181
+ next unless control[:results]
182
+ control[:results].each do |result|
183
+ if result[:status] == 'failed'
148
184
  failed += 1
149
- elsif control[:results].any? { |r| r[:status] == 'skipped' }
185
+ elsif result[:status] == 'skipped'
150
186
  skipped += 1
151
187
  else
152
188
  passed += 1
@@ -154,16 +190,11 @@ class InspecRspecJson < InspecRspecMiniJson
154
190
  end
155
191
  end
156
192
 
157
- # TODO: provide this information in the output
193
+ { 'total' => total, 'failed' => failed, 'skipped' => skipped, 'passed' => passed }
158
194
  end
159
195
 
160
196
  private
161
197
 
162
- def profile_info(profile)
163
- info = profile.info.dup
164
- [info[:name], info]
165
- end
166
-
167
198
  #
168
199
  # TODO(ssd+vj): We should probably solve this by either ensuring the example has
169
200
  # the profile_id of the top level profile when it is included as a dependency, or
@@ -171,7 +202,7 @@ class InspecRspecJson < InspecRspecMiniJson
171
202
  # this heuristic matching.
172
203
  #
173
204
  def example2profile(example, profiles)
174
- profiles.values.find { |p| profile_contains_example?(p, example) }
205
+ profiles.find { |p| profile_contains_example?(p, example) }
175
206
  end
176
207
 
177
208
  def profile_contains_example?(profile, example)
@@ -180,7 +211,7 @@ class InspecRspecJson < InspecRspecMiniJson
180
211
  # Case 2: The profile contains a control that matches the id of the example
181
212
  if profile[:name] == example[:profile_id]
182
213
  true
183
- elsif profile[:controls] && profile[:controls].key?(example[:id])
214
+ elsif profile[:controls] && profile[:controls].any? { |x| x[:id] == example[:id] }
184
215
  true
185
216
  else
186
217
  false
@@ -188,8 +219,9 @@ class InspecRspecJson < InspecRspecMiniJson
188
219
  end
189
220
 
190
221
  def example2control(example, profiles)
191
- p = example2profile(example, profiles)
192
- p[:controls][example[:id]] if p && p[:controls]
222
+ profile = example2profile(example, profiles)
223
+ return nil unless profile && profile[:controls]
224
+ profile[:controls].find { |x| x[:id] == example[:id] }
193
225
  end
194
226
 
195
227
  def format_example(example)
@@ -246,16 +278,17 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
246
278
  @current_profile = nil
247
279
  @missing_controls = []
248
280
  @anonymous_tests = []
281
+ @control_tests = []
249
282
  super(*args)
250
283
  end
251
284
 
252
- def close(_notification)
285
+ def close(_notification) # rubocop:disable Metrics/AbcSize
253
286
  flush_current_control
254
287
  output.puts('') unless @current_control.nil?
255
288
  print_tests
256
289
  output.puts('')
257
290
 
258
- @profiles_info.each do |_id, profile|
291
+ @profiles_info.each do |profile|
259
292
  next if profile[:already_printed]
260
293
  @current_profile = profile
261
294
  next unless print_current_profile
@@ -266,13 +299,20 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
266
299
  output.puts('')
267
300
  end
268
301
 
269
- res = @output_hash[:summary]
270
- passed = res[:example_count] - res[:failure_count] - res[:skip_count]
271
- s = format('Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
272
- COLORS['passed'], passed, COLORS['reset'],
273
- COLORS['failed'], res[:failure_count], COLORS['reset'],
274
- COLORS['skipped'], res[:skip_count], COLORS['reset'])
275
- output.puts(s)
302
+ controls_res = controls_summary
303
+ tests_res = tests_summary
304
+
305
+ s = format('Profile Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
306
+ COLORS['passed'], controls_res['passed'], COLORS['reset'],
307
+ COLORS['failed'], controls_res['failed']['total'], COLORS['reset'],
308
+ COLORS['skipped'], controls_res['skipped'], COLORS['reset'])
309
+ output.puts(s) if controls_res['total'] > 0
310
+
311
+ s = format('Test Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
312
+ COLORS['passed'], tests_res['passed'], COLORS['reset'],
313
+ COLORS['failed'], tests_res['failed'], COLORS['reset'],
314
+ COLORS['skipped'], tests_res['skipped'], COLORS['reset'])
315
+ output.puts(s) if !@anonymous_tests.empty? || @current_control.nil?
276
316
  end
277
317
 
278
318
  private
@@ -371,7 +411,11 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
371
411
  test_color = @colors[test_status]
372
412
  indicator = @indicators[x[:status]]
373
413
  indicator = @indicators['empty'] if indicator.nil?
374
- msg = x[:message] || x[:skip_message] || x[:code_desc]
414
+ if x[:message]
415
+ msg = x[:code_desc] + "\n" + x[:message]
416
+ else
417
+ msg = x[:skip_message] || x[:code_desc]
418
+ end
375
419
  print_line(
376
420
  color: test_color,
377
421
  indicator: @indicators['small'] + indicator,
@@ -414,7 +458,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
414
458
  return if @current_control.nil?
415
459
 
416
460
  prev_profile = @current_profile
417
- @current_profile = @profiles_info[@current_control[:profile_id]]
461
+ @current_profile = @profiles_info.find { |i| i[:id] == @current_control[:profile_id] }
418
462
  print_current_profile if prev_profile != @current_profile
419
463
 
420
464
  fails, skips, passes, summary_indicator = current_control_infos
@@ -425,6 +469,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
425
469
  if control_id.start_with? '(generated from '
426
470
  @anonymous_tests.push(@current_control)
427
471
  else
472
+ @control_tests.push(@current_control)
428
473
  print_line(
429
474
  color: @colors[summary_indicator] || '',
430
475
  indicator: @indicators[summary_indicator] || @indicators['unknown'],
data/lib/inspec/runner.rb CHANGED
@@ -11,6 +11,7 @@ require 'inspec/profile_context'
11
11
  require 'inspec/profile'
12
12
  require 'inspec/metadata'
13
13
  require 'inspec/secrets'
14
+ require 'inspec/dependencies/cache'
14
15
  # spec requirements
15
16
 
16
17
  module Inspec
@@ -32,7 +33,6 @@ module Inspec
32
33
  extend Forwardable
33
34
 
34
35
  def_delegator :@test_collector, :report
35
- def_delegator :@test_collector, :reset
36
36
 
37
37
  attr_reader :backend, :rules, :attributes
38
38
  def initialize(conf = {})
@@ -42,7 +42,8 @@ module Inspec
42
42
  @target_profiles = []
43
43
  @controls = @conf[:controls] || []
44
44
  @ignore_supports = @conf[:ignore_supports]
45
-
45
+ @create_lockfile = @conf[:create_lockfile]
46
+ @cache = Inspec::Cache.new(@conf[:cache])
46
47
  @test_collector = @conf.delete(:test_collector) || begin
47
48
  require 'inspec/runner_rspec'
48
49
  RunnerRspec.new(@conf)
@@ -64,12 +65,20 @@ module Inspec
64
65
  @test_collector.backend = @backend
65
66
  end
66
67
 
67
- def run(with = nil)
68
- Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
68
+ def reset
69
+ @test_collector.reset
70
+ @target_profiles.each do |profile|
71
+ profile.runner_context.rules = {}
72
+ end
73
+ @rules = []
74
+ end
75
+
76
+ def load
69
77
  all_controls = []
70
78
 
71
79
  @target_profiles.each do |profile|
72
80
  @test_collector.add_profile(profile)
81
+ write_lockfile(profile) if @create_lockfile
73
82
  profile.locked_dependencies
74
83
  profile.load_libraries
75
84
  @attributes |= profile.runner_context.attributes
@@ -79,7 +88,27 @@ module Inspec
79
88
  all_controls.each do |rule|
80
89
  register_rule(rule)
81
90
  end
91
+ end
92
+
93
+ def run(with = nil)
94
+ Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
95
+ load
96
+ run_tests(with)
97
+ end
82
98
 
99
+ def write_lockfile(profile)
100
+ return false if !profile.writable?
101
+
102
+ if profile.lockfile_exists?
103
+ Inspec::Log.debug "Using existing lockfile #{profile.lockfile_path}"
104
+ else
105
+ Inspec::Log.debug "Creating lockfile: #{profile.lockfile_path}"
106
+ lockfile = profile.generate_lockfile
107
+ File.write(profile.lockfile_path, lockfile.to_yaml)
108
+ end
109
+ end
110
+
111
+ def run_tests(with = nil)
83
112
  @test_collector.run(with)
84
113
  end
85
114
 
@@ -126,6 +155,7 @@ module Inspec
126
155
  #
127
156
  def add_target(target, _opts = [])
128
157
  profile = Inspec::Profile.for_target(target,
158
+ cache: @cache,
129
159
  backend: @backend,
130
160
  controls: @controls,
131
161
  attributes: @conf[:attributes])
@@ -133,20 +163,6 @@ module Inspec
133
163
  @target_profiles << profile if supports_profile?(profile)
134
164
  end
135
165
 
136
- #
137
- # This is used by inspec-shell and inspec-detect. This should
138
- # probably be cleaned up a bit.
139
- #
140
- # @params [Hash] Options
141
- # @returns [Inspec::ProfileContext]
142
- #
143
- def create_context(options = {})
144
- meta = options[:metadata]
145
- profile_id = nil
146
- profile_id = meta.params[:name] unless meta.nil?
147
- Inspec::ProfileContext.new(profile_id, @backend, @conf.merge(options))
148
- end
149
-
150
166
  def supports_profile?(profile)
151
167
  return true if @ignore_supports
152
168
 
@@ -180,6 +196,14 @@ module Inspec
180
196
  new_tests
181
197
  end
182
198
 
199
+ def eval_with_virtual_profile(command)
200
+ require 'fetchers/mock'
201
+ add_target({ 'inspec.yml' => 'name: inspec-shell' })
202
+ our_profile = @target_profiles.first
203
+ ctx = our_profile.runner_context
204
+ ctx.load(command)
205
+ end
206
+
183
207
  private
184
208
 
185
209
  def block_source_info(block)