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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -2
- data/Gemfile +6 -0
- data/Rakefile +3 -55
- data/docs/README.md +20 -0
- data/docs/cli.rst +6 -0
- data/docs/dsl_inspec.md +245 -0
- data/docs/dsl_resource.md +93 -0
- data/docs/inspec_and_friends.md +102 -0
- data/docs/matchers.md +136 -0
- data/docs/plugin_kitchen_inspec.html.md +55 -0
- data/docs/profiles.md +271 -0
- data/docs/resources.rst +1 -1
- data/docs/shell.md +150 -0
- data/inspec.gemspec +1 -1
- data/lib/bundles/inspec-compliance/api.rb +28 -18
- data/lib/bundles/inspec-compliance/cli.rb +19 -27
- data/lib/fetchers/git.rb +4 -0
- data/lib/fetchers/local.rb +16 -1
- data/lib/fetchers/mock.rb +4 -0
- data/lib/fetchers/url.rb +40 -12
- data/lib/inspec/base_cli.rb +4 -0
- data/lib/inspec/cli.rb +6 -8
- data/lib/inspec/control_eval_context.rb +8 -0
- data/lib/inspec/dependencies/{vendor_index.rb → cache.rb} +5 -4
- data/lib/inspec/dependencies/dependency_set.rb +8 -14
- data/lib/inspec/dependencies/requirement.rb +10 -20
- data/lib/inspec/dependencies/resolver.rb +2 -2
- data/lib/inspec/dsl.rb +9 -0
- data/lib/inspec/fetcher.rb +1 -1
- data/lib/inspec/objects/test.rb +8 -2
- data/lib/inspec/plugins/fetcher.rb +11 -12
- data/lib/inspec/plugins/resource.rb +3 -0
- data/lib/inspec/profile.rb +60 -14
- data/lib/inspec/profile_context.rb +28 -7
- data/lib/inspec/resource.rb +17 -2
- data/lib/inspec/rspec_json_formatter.rb +80 -35
- data/lib/inspec/runner.rb +42 -18
- data/lib/inspec/shell.rb +5 -16
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/apache_conf.rb +1 -1
- data/lib/resources/gem.rb +1 -0
- data/lib/resources/oneget.rb +1 -0
- data/lib/resources/os.rb +1 -1
- data/lib/resources/package.rb +3 -1
- data/lib/resources/pip.rb +1 -1
- data/lib/resources/ssl.rb +9 -11
- metadata +15 -15
- data/docs/dsl_inspec.rst +0 -259
- data/docs/dsl_resource.rst +0 -90
- data/docs/inspec_and_friends.rst +0 -85
- data/docs/matchers.rst +0 -137
- data/docs/profiles.rst +0 -169
- data/docs/readme.rst +0 -105
- data/docs/shell.rst +0 -130
- 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, :
|
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
|
-
@
|
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(
|
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
|
70
|
+
def all_controls
|
69
71
|
ret = @rules.values
|
70
|
-
ret += @
|
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
|
-
@
|
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
|
-
|
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
|
data/lib/inspec/resource.rb
CHANGED
@@ -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(
|
29
|
-
|
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[:
|
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
|
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
|
137
|
-
|
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
|
-
@
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
if
|
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
|
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
|
-
|
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.
|
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].
|
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
|
-
|
192
|
-
|
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 |
|
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
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
COLORS['
|
274
|
-
COLORS['
|
275
|
-
|
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
|
-
|
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
|
68
|
-
|
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)
|