inspec 0.15.0 → 0.16.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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -2
  3. data/Gemfile +2 -3
  4. data/README.md +2 -0
  5. data/Rakefile +8 -0
  6. data/bin/inspec +1 -157
  7. data/docs/resources.rst +79 -78
  8. data/examples/profile/controls/example.rb +3 -1
  9. data/lib/fetchers/mock.rb +27 -0
  10. data/lib/fetchers/tar.rb +3 -2
  11. data/lib/fetchers/zip.rb +3 -1
  12. data/lib/inspec/cli.rb +164 -0
  13. data/lib/inspec/plugins/resource.rb +6 -2
  14. data/lib/inspec/profile.rb +28 -17
  15. data/lib/inspec/resource.rb +5 -1
  16. data/lib/inspec/rspec_json_formatter.rb +42 -0
  17. data/lib/inspec/rule.rb +24 -1
  18. data/lib/inspec/runner.rb +15 -7
  19. data/lib/inspec/runner_mock.rb +6 -1
  20. data/lib/inspec/runner_rspec.rb +29 -1
  21. data/lib/inspec/version.rb +1 -1
  22. data/lib/resources/{script.rb → powershell.rb} +19 -5
  23. data/lib/resources/registry_key.rb +1 -1
  24. data/test/{integration/cookbooks → cookbooks}/os_prepare/files/empty.iso +0 -0
  25. data/test/{integration/cookbooks → cookbooks}/os_prepare/files/example.csv +0 -0
  26. data/test/{integration/cookbooks → cookbooks}/os_prepare/files/example.ini +0 -0
  27. data/test/{integration/cookbooks → cookbooks}/os_prepare/files/example.json +0 -0
  28. data/test/{integration/cookbooks → cookbooks}/os_prepare/files/example.yml +0 -0
  29. data/test/{integration/cookbooks → cookbooks}/os_prepare/metadata.rb +0 -0
  30. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/_runit_service_centos.rb +0 -0
  31. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/_upstart_service_centos.rb +0 -0
  32. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/apache.rb +0 -0
  33. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/apt.rb +0 -0
  34. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/auditctl.rb +0 -0
  35. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/default.rb +0 -0
  36. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/file.rb +0 -0
  37. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/iptables.rb +0 -0
  38. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/json_yaml_csv_ini.rb +0 -0
  39. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/mount.rb +2 -2
  40. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/package.rb +0 -0
  41. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/postgres.rb +6 -0
  42. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/registry_key.rb +0 -0
  43. data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/service.rb +0 -0
  44. data/test/{integration/cookbooks → cookbooks}/os_prepare/templates/default/sv-default-svlog-run.erb +0 -0
  45. data/test/functional/command_test.rb +390 -0
  46. data/test/helper.rb +6 -0
  47. data/test/integration/{test/integration/default → default}/_debug_spec.rb +0 -0
  48. data/test/integration/{test/integration/default → default}/apache_conf_spec.rb +0 -0
  49. data/test/integration/{test/integration/default → default}/apt_spec.rb +0 -0
  50. data/test/integration/{test/integration/default → default}/auditd_rules_spec.rb +0 -0
  51. data/test/integration/{test/integration/default → default}/compare_matcher_spec.rb +0 -0
  52. data/test/integration/{test/integration/default → default}/csv_spec.rb +0 -0
  53. data/test/integration/{test/integration/default → default}/etc_group_spec.rb +0 -0
  54. data/test/integration/{test/integration/default → default}/file_spec.rb +3 -2
  55. data/test/integration/{test/integration/default → default}/group_spec.rb +0 -0
  56. data/test/integration/{test/integration/default → default}/ini_spec.rb +0 -0
  57. data/test/integration/{test/integration/default → default}/iptables_spec.rb +0 -0
  58. data/test/integration/{test/integration/default → default}/json_spec.rb +0 -0
  59. data/test/integration/{test/integration/default → default}/kernel_module_spec.rb +0 -0
  60. data/test/integration/{test/integration/default → default}/kernel_parameter_spec.rb +0 -0
  61. data/test/integration/{test/integration/default → default}/mount_spec.rb +1 -1
  62. data/test/integration/{test/integration/default → default}/os_spec.rb +0 -0
  63. data/test/integration/{test/integration/default → default}/package_spec.rb +0 -0
  64. data/test/integration/{test/integration/default → default}/port_spec.rb +0 -0
  65. data/test/integration/{test/integration/default → default}/postgres_session_spec.rb +0 -0
  66. data/test/integration/default/powershell_spec.rb +13 -0
  67. data/test/integration/{test/integration/default → default}/registry_key_spec.rb +0 -0
  68. data/test/integration/{test/integration/default → default}/secpol_spec.rb +0 -0
  69. data/test/integration/{test/integration/default → default}/service_spec.rb +0 -0
  70. data/test/integration/{test/integration/default → default}/user_spec.rb +0 -0
  71. data/test/integration/{test/integration/default → default}/yaml_spec.rb +0 -0
  72. data/test/unit/control_test.rb +58 -0
  73. data/test/unit/fetchers/mock_test.rb +43 -0
  74. data/test/unit/plugins/resource_test.rb +60 -0
  75. data/test/unit/resources/{script_test.rb → powershell_test.rb} +10 -1
  76. metadata +107 -101
  77. data/test/integration/.kitchen.ec2.yml +0 -75
  78. data/test/integration/.kitchen.yml +0 -45
  79. data/test/integration/Berksfile +0 -5
data/lib/inspec/runner.rb CHANGED
@@ -55,25 +55,26 @@ module Inspec
55
55
  def add_profile(profile, options = {})
56
56
  return unless options[:ignore_supports] ||
57
57
  profile.metadata.supports_transport?(@backend)
58
+ @test_collector.add_profile(profile)
58
59
 
59
60
  libs = profile.libraries.map do |k, v|
60
61
  { ref: k, content: v }
61
62
  end
62
63
 
63
- profile.tests.each do |ref, content|
64
+ tests = profile.tests.map do |ref, content|
64
65
  r = profile.source_reader.target.abs_path(ref)
65
- test = { ref: r, content: content }
66
- add_content(test, libs, options)
66
+ { ref: r, content: content }
67
67
  end
68
+
69
+ add_content(tests, libs, options)
68
70
  end
69
71
 
70
72
  def create_context(options = {})
71
73
  Inspec::ProfileContext.new(@profile_id, @backend, @conf.merge(options))
72
74
  end
73
75
 
74
- def add_content(test, libs, options = {})
75
- content = test[:content]
76
- return if content.nil? || content.empty?
76
+ def add_content(tests, libs, options = {})
77
+ return if tests.nil? || tests.empty?
77
78
 
78
79
  # load all libraries
79
80
  ctx = create_context(options)
@@ -83,7 +84,8 @@ module Inspec
83
84
  end
84
85
 
85
86
  # evaluate the test content
86
- ctx.load(content, test[:ref], test[:line] || 1)
87
+ tests = [tests] unless tests.is_a? Array
88
+ tests.each { |t| add_test_to_context(t, ctx) }
87
89
 
88
90
  # process the resulting rules
89
91
  filter_controls(ctx.rules, options[:controls]).each do |rule_id, rule|
@@ -96,6 +98,12 @@ module Inspec
96
98
 
97
99
  private
98
100
 
101
+ def add_test_to_context(test, ctx)
102
+ content = test[:content]
103
+ return if content.nil? || content.empty?
104
+ ctx.load(content, test[:ref], test[:line] || 1)
105
+ end
106
+
99
107
  def filter_controls(controls_map, include_list)
100
108
  return controls_map if include_list.nil? || include_list.empty?
101
109
  controls_map.select { |k, _| include_list.include?(k) }
@@ -4,9 +4,14 @@
4
4
 
5
5
  module Inspec
6
6
  class RunnerMock
7
- attr_reader :tests
7
+ attr_reader :tests, :profiles
8
8
  def initialize
9
9
  @tests = []
10
+ @profiles = []
11
+ end
12
+
13
+ def add_profile(profile)
14
+ @profiles.push(profile)
10
15
  end
11
16
 
12
17
  def add_test(example, _rule_id, _rule)
@@ -29,6 +29,18 @@ module Inspec
29
29
  RSpec::Core::ExampleGroup.describe(*args, &block)
30
30
  end
31
31
 
32
+ # Add a full profile to the runner. Only pulls in metadata
33
+ #
34
+ # @param [Inspec::Profile] profile
35
+ # @return [nil]
36
+ def add_profile(profile)
37
+ RSpec.configuration.formatters
38
+ .find_all { |c| c.is_a? InspecRspecFormatter }
39
+ .each do |fmt|
40
+ fmt.add_profile(profile)
41
+ end
42
+ end
43
+
32
44
  # Add an example group to the list of registered tests.
33
45
  #
34
46
  # @param [RSpecExampleGroup] example test
@@ -75,7 +87,15 @@ module Inspec
75
87
  #
76
88
  # @return [nil]
77
89
  def configure_output
78
- RSpec.configuration.add_formatter(@conf['format'] || 'progress')
90
+ if !@conf['output'] || @conf['output'] == '-'
91
+ RSpec.configuration.output_stream = $stdout
92
+ else
93
+ RSpec.configuration.output_stream = @conf['output']
94
+ end
95
+
96
+ format = @conf['format'] || 'progress'
97
+ format = 'InspecRspecFormatter' if format == 'fulljson'
98
+ RSpec.configuration.add_formatter(format)
79
99
  RSpec.configuration.color = @conf['color']
80
100
 
81
101
  setup_reporting if @conf['report']
@@ -96,9 +116,17 @@ module Inspec
96
116
  def set_rspec_ids(example, id, rule)
97
117
  example.metadata[:id] = id
98
118
  example.metadata[:impact] = rule.impact
119
+ example.metadata[:title] = rule.title
120
+ example.metadata[:desc] = rule.desc
121
+ example.metadata[:code] = rule.instance_variable_get(:@__code)
122
+ example.metadata[:source_location] = rule.instance_variable_get(:@__source_location)
99
123
  example.filtered_examples.each do |e|
100
124
  e.metadata[:id] = id
101
125
  e.metadata[:impact] = rule.impact
126
+ e.metadata[:title] = rule.title
127
+ e.metadata[:desc] = rule.desc
128
+ e.metadata[:code] = rule.instance_variable_get(:@__code)
129
+ e.metadata[:source_location] = rule.instance_variable_get(:@__source_location)
102
130
  end
103
131
  example.children.each do |child|
104
132
  set_rspec_ids(child, id, rule)
@@ -3,5 +3,5 @@
3
3
  # author: Christoph Hartmann
4
4
 
5
5
  module Inspec
6
- VERSION = '0.15.0'.freeze
6
+ VERSION = '0.16.0'.freeze
7
7
  end
@@ -5,15 +5,15 @@
5
5
  # license: All rights reserved
6
6
 
7
7
  module Inspec::Resources
8
- class Script < Cmd
9
- name 'script'
10
- desc 'Use the script InSpec audit resource to test a Windows PowerShell script on the Microsoft Windows platform.'
8
+ class PowershellScript < Cmd
9
+ name 'powershell'
10
+ desc 'Use the powershell InSpec audit resource to test a Windows PowerShell script on the Microsoft Windows platform.'
11
11
  example "
12
12
  script = <<-EOH
13
13
  # you powershell script
14
14
  EOH
15
15
 
16
- describe script(script) do
16
+ describe powershell(script) do
17
17
  its('matcher') { should eq 'output' }
18
18
  end
19
19
  "
@@ -37,7 +37,21 @@ module Inspec::Resources
37
37
  end
38
38
 
39
39
  def to_s
40
- 'Script'
40
+ 'Powershell'
41
+ end
42
+ end
43
+
44
+ # this is deprecated syntax and will be removed in future versions
45
+ class LegacyPowershellScript < PowershellScript
46
+ name 'script'
47
+
48
+ def initialize(script)
49
+ deprecated
50
+ super(script)
51
+ end
52
+
53
+ def deprecated
54
+ warn '[DEPRECATION] `script(script)` is deprecated. Please use `powershell(script)` instead.'
41
55
  end
42
56
  end
43
57
  end
@@ -111,7 +111,7 @@ module Inspec::Resources
111
111
  $object | ConvertTo-Json
112
112
  EOH
113
113
 
114
- cmd = inspec.script(script)
114
+ cmd = inspec.powershell(script)
115
115
 
116
116
  # cannot rely on exit code for now, successful command returns exit code 1
117
117
  # return nil if cmd.exit_status != 0, try to parse json
@@ -9,7 +9,7 @@ when 'ubuntu', 'rhel', 'centos', 'fedora'
9
9
 
10
10
  # copy iso file for mount tests
11
11
  # NB created using `mkdir empty; mkisofs -o empty.iso empty/`
12
- cookbook_file '/root/empty.iso' do
12
+ cookbook_file '/tmp/empty.iso' do
13
13
  owner 'root'
14
14
  group 'root'
15
15
  mode '0755'
@@ -26,7 +26,7 @@ when 'ubuntu', 'rhel', 'centos', 'fedora'
26
26
 
27
27
  # mount -o loop /root/empty.iso /mnt/iso-disk
28
28
  mount '/mnt/iso-disk' do
29
- device '/root/empty.iso'
29
+ device '/tmp/empty.iso'
30
30
  options 'loop'
31
31
  action [:mount, :enable]
32
32
  end
@@ -6,6 +6,12 @@
6
6
  # hw-cookbooks/postgresql is tested on these platforms
7
7
  case node['platform']
8
8
  when 'ubuntu', 'centos'
9
+
10
+ # also skip it on ubuntu 15.10, because the cookbook is not supported
11
+ # with `enable_pgdg_apt` yet
12
+ return if node['platform_version'] == "15.10"
13
+
14
+ node.default['postgresql']['enable_pgdg_apt'] = true
9
15
  node.default['postgresql']['config']['listen_addresses'] = 'localhost'
10
16
  node.default['postgresql']['password']['postgres'] = 'md506be11be01439cb4abd537e454df34ea' # "inspec"
11
17
  include_recipe 'postgresql::server'
@@ -0,0 +1,390 @@
1
+ # encoding: utf-8
2
+ # author: Dominik Richter
3
+ # author: Christoph Hartmann
4
+
5
+ require 'helper'
6
+ require 'minitest/hell'
7
+ class Minitest::Test
8
+ parallelize_me!
9
+ end
10
+
11
+ describe 'Inspec::InspecCLI' do
12
+ let(:repo_path) { File.expand_path(File.join( __FILE__, '..', '..', '..')) }
13
+ let(:exec_inspec) { File.join(repo_path, 'bin', 'inspec') }
14
+ let(:profile_path) { File.join(repo_path, 'test', 'unit', 'mock', 'profiles') }
15
+ let(:examples_path) { File.join(repo_path, 'examples') }
16
+ let(:dst) {
17
+ # create a temporary path, but we only want an auto-clean helper
18
+ # so remove the file and give back the path
19
+ res = Tempfile.new('inspec-shred')
20
+ FileUtils.rm(res.path)
21
+ TMP_CACHE[res.path] = res
22
+ }
23
+
24
+ def inspec(commandline)
25
+ CMD.run_command("#{exec_inspec} #{commandline}")
26
+ end
27
+
28
+ describe 'detect' do
29
+ it 'runs well on all nodes' do
30
+ out = inspec('detect')
31
+ out.stderr.must_equal ''
32
+ out.exit_status.must_equal 0
33
+ j = JSON.load(out.stdout)
34
+ j.keys.must_include 'name'
35
+ j.keys.must_include 'family'
36
+ j.keys.must_include 'arch'
37
+ j.keys.must_include 'release'
38
+ end
39
+ end
40
+
41
+ describe 'version' do
42
+ it 'provides the version number on stdout' do
43
+ out = inspec('version')
44
+ out.stderr.must_equal ''
45
+ out.exit_status.must_equal 0
46
+ out.stdout.must_equal Inspec::VERSION+"\n"
47
+ end
48
+ end
49
+
50
+ describe 'shell' do
51
+ it 'provides a help command' do
52
+ out = CMD.run_command("echo \"help\nexit\" | #{exec_inspec} shell")
53
+ out.exit_status.must_equal 0
54
+ out.stdout.must_include 'Available commands:'
55
+ out.stdout.must_include 'You are currently running on:'
56
+ end
57
+
58
+ it 'exposes all resources' do
59
+ out = CMD.run_command("echo \"os\nexit\" | #{exec_inspec} shell")
60
+ out.exit_status.must_equal 0
61
+ out.stdout.must_match /^=> .*Operating.* .*System.* .*Detection.*$/
62
+ end
63
+ end
64
+
65
+ describe 'example profile' do
66
+ let(:path) { File.join(examples_path, 'profile') }
67
+
68
+ it 'check is successful' do
69
+ out = inspec('check ' + path)
70
+ out.stdout.must_match /Valid.*true/
71
+ out.exit_status.must_equal 0
72
+ end
73
+
74
+ it 'archive is successful' do
75
+ out = inspec('archive ' + path + ' --overwrite')
76
+ out.exit_status.must_equal 0
77
+ out.stdout.must_match /Generate archive [^ ]*profile.tar.gz/
78
+ out.stdout.must_include 'Finished archive generation.'
79
+ end
80
+
81
+ it 'archives to output file' do
82
+ out = inspec('archive ' + path + ' --output ' + dst.path)
83
+ out.stderr.must_equal ''
84
+ out.stdout.must_include 'Generate archive '+dst.path
85
+ out.stdout.must_include 'Finished archive generation.'
86
+ out.exit_status.must_equal 0
87
+ File.exist?(dst.path).must_equal true
88
+ end
89
+
90
+ it 'auto-archives when no --output is given' do
91
+ auto_dst = File.join(repo_path, 'profile.tar.gz')
92
+ out = inspec('archive ' + path + ' --overwrite')
93
+ out.stderr.must_equal ''
94
+ out.stdout.must_include 'Generate archive '+auto_dst
95
+ out.stdout.must_include 'Finished archive generation.'
96
+ out.exit_status.must_equal 0
97
+ File.exist?(auto_dst).must_equal true
98
+ end
99
+
100
+ it 'archive on invalid archive' do
101
+ out = inspec('archive /proc --output ' + dst.path)
102
+ # out.stdout.must_equal '' => we have partial stdout output right now
103
+ out.stderr.must_include "Don't understand inspec profile in \"/proc\""
104
+ out.exit_status.must_equal 1
105
+ File.exist?(dst.path).must_equal false
106
+ end
107
+
108
+ it 'archive wont overwrite existing files' do
109
+ x = rand.to_s
110
+ File.write(dst.path, x)
111
+ out = inspec('archive ' + path + ' --output ' + dst.path)
112
+ out.stderr.must_equal '' # uh...
113
+ out.stdout.must_include "Archive #{dst.path} exists already. Use --overwrite."
114
+ out.exit_status.must_equal 1
115
+ File.read(dst.path).must_equal x
116
+ end
117
+
118
+ it 'archive will overwrite files if necessary' do
119
+ x = rand.to_s
120
+ File.write(dst.path, x)
121
+ out = inspec('archive ' + path + ' --output ' + dst.path + ' --overwrite')
122
+ out.stderr.must_equal ''
123
+ out.stdout.must_include 'Generate archive '+dst.path
124
+ out.exit_status.must_equal 0
125
+ File.read(dst.path).wont_equal x
126
+ end
127
+
128
+ it 'creates valid tar.gz archives' do
129
+ out = inspec('archive ' + path + ' --output ' + dst.path + ' --tar')
130
+ out.stderr.must_equal ''
131
+ out.stdout.must_include 'Generate archive '+dst.path
132
+ out.exit_status.must_equal 0
133
+ t = Zlib::GzipReader.open(dst.path)
134
+ Gem::Package::TarReader.new(t).entries.map(&:header).map(&:name).must_include 'inspec.yml'
135
+ end
136
+
137
+ it 'creates valid zip archives' do
138
+ out = inspec('archive ' + path + ' --output ' + dst.path + ' --zip')
139
+ out.stderr.must_equal ''
140
+ out.stdout.must_include 'Generate archive '+dst.path
141
+ out.exit_status.must_equal 0
142
+ Zip::File.new(dst.path).entries.map(&:name).must_include 'inspec.yml'
143
+ end
144
+
145
+ it 'read the profile json' do
146
+ out = inspec('json ' + path)
147
+ out.stderr.must_equal ''
148
+ out.exit_status.must_equal 0
149
+ s = out.stdout
150
+ JSON.load(s).must_be_kind_of Hash
151
+ end
152
+
153
+ describe 'json profile data' do
154
+ let(:json) { JSON.load(inspec('json '+path).stdout) }
155
+
156
+ it 'has a name' do
157
+ json['name'].must_equal 'profile'
158
+ end
159
+
160
+ it 'has a title' do
161
+ json['title'].must_equal 'InSpec Example Profile'
162
+ end
163
+
164
+ it 'has a summary' do
165
+ json['summary'].must_equal 'Demonstrates the use of InSpec Compliance Profile'
166
+ end
167
+
168
+ it 'has a version' do
169
+ json['version'].must_equal '1.0.0'
170
+ end
171
+
172
+ it 'has a maintainer' do
173
+ json['maintainer'].must_equal 'Chef Software, Inc.'
174
+ end
175
+
176
+ it 'has a copyright' do
177
+ json['copyright'].must_equal 'Chef Software, Inc.'
178
+ end
179
+
180
+ it 'has rules' do
181
+ json['rules'].length.must_equal 2 # TODO: flatten out or search deeper!
182
+ end
183
+
184
+ describe 'a rule' do
185
+ let(:rule) { json['rules']['controls/example.rb']['rules']['tmp-1.0'] }
186
+
187
+ it 'has a title' do
188
+ rule['title'].must_equal 'Create /tmp directory'
189
+ end
190
+
191
+ it 'has a description' do
192
+ rule['desc'].must_equal 'An optional description...'
193
+ end
194
+
195
+ it 'has an impact' do
196
+ rule['impact'].must_equal 0.7
197
+ end
198
+
199
+ it 'has a ref' do
200
+ rule['refs'].must_equal([{'ref' => 'Document A-12', 'url' => 'http://...'}])
201
+ end
202
+
203
+ it 'has a source location' do
204
+ loc = File.join(path, '/controls/example.rb')
205
+ rule['source_location'].must_equal [loc, 8]
206
+ end
207
+
208
+ it 'has a the source code' do
209
+ rule['code'].must_match /\Acontrol \"tmp-1.0\" do.*end\n\Z/m
210
+ end
211
+ end
212
+ end
213
+
214
+ it 'writes json to file' do
215
+ out = inspec('json ' + path + ' --output ' + dst.path)
216
+ out.stderr.must_equal ''
217
+ out.exit_status.must_equal 0
218
+ hm = JSON.load(File.read(dst.path))
219
+ hm['name'].must_equal 'profile'
220
+ hm['rules'].length.must_equal 2 # TODO: flatten out or search deeper!
221
+ end
222
+
223
+ it 'can execute the profile' do
224
+ out = inspec('exec ' + path)
225
+ out.stderr.must_equal ''
226
+ out.exit_status.must_equal 0
227
+ out.stdout.must_match /^Pending: /
228
+ out.stdout.must_include '3 examples, 0 failures, 1 pending'
229
+ end
230
+
231
+ it 'can execute the profile with the json formatter' do
232
+ out = inspec('exec ' + path + ' --format json')
233
+ out.stderr.must_equal ''
234
+ out.exit_status.must_equal 0
235
+ JSON.load(out.stdout).must_be_kind_of Hash
236
+ end
237
+
238
+ describe 'execute a profile with json formatting' do
239
+ let(:json) { JSON.load(inspec('exec ' + path + ' --format json').stdout) }
240
+ let(:examples) { json['examples'] }
241
+ let(:ex1) { examples.find{|x| x['id'] == 'tmp-1.0'} }
242
+ let(:ex2) { examples.find{|x| x['id'] =~ /generated/} }
243
+ let(:ex3) { examples.find{|x| x['id'] == 'gordon-1.0'} }
244
+
245
+ it 'must have 3 examples' do
246
+ json['examples'].length.must_equal 3
247
+ end
248
+
249
+ it 'id in json' do
250
+ examples.find { |ex| !ex.key? 'id' }.must_be :nil?
251
+ end
252
+
253
+ it 'impact in json' do
254
+ ex1['impact'].must_equal 0.7
255
+ ex2['impact'].must_be :nil?
256
+ end
257
+
258
+ it 'status in json' do
259
+ ex1['status'].must_equal 'passed'
260
+ ex3['status'].must_equal 'pending'
261
+ end
262
+
263
+ it 'pending message in json' do
264
+ ex1['pending_message'].must_be :nil?
265
+ ex3['pending_message'].must_equal 'Not yet implemented'
266
+ end
267
+ end
268
+
269
+ describe 'execute a profile with fulljson formatting' do
270
+ let(:json) { JSON.load(inspec('exec ' + path + ' --format fulljson').stdout) }
271
+ let(:examples) { json['examples'] }
272
+ let(:metadata) { json['profiles'][0] }
273
+ let(:ex1) { examples.find{|x| x['id'] == 'tmp-1.0'} }
274
+ let(:ex2) { examples.find{|x| x['id'] =~ /generated/} }
275
+ let(:ex3) { examples.find{|x| x['id'] == 'gordon-1.0'} }
276
+
277
+ it 'has all the metadata' do
278
+ metadata.must_equal({
279
+ "name" => "profile",
280
+ "title" => "InSpec Example Profile",
281
+ "maintainer" => "Chef Software, Inc.",
282
+ "copyright" => "Chef Software, Inc.",
283
+ "copyright_email" => "support@chef.io",
284
+ "license" => "Apache 2 license",
285
+ "summary" => "Demonstrates the use of InSpec Compliance Profile",
286
+ "version" => "1.0.0",
287
+ "supports" => [{"os-family" => "linux"}]
288
+ })
289
+ end
290
+
291
+ it 'must have 3 examples' do
292
+ json['examples'].length.must_equal 3
293
+ end
294
+
295
+ it 'id in json' do
296
+ examples.find { |ex| !ex.key? 'id' }.must_be :nil?
297
+ end
298
+
299
+ it 'title in json' do
300
+ ex3['title'].must_equal 'Verify the version number of Gordon'
301
+ end
302
+
303
+ it 'desc in json' do
304
+ ex3['desc'].must_equal 'An optional description...'
305
+ end
306
+
307
+ it 'code in json' do
308
+ ex3['code'].wont_be :nil?
309
+ end
310
+
311
+ it 'code_desc in json' do
312
+ ex3['code_desc'].wont_be :nil?
313
+ end
314
+
315
+ it 'impact in json' do
316
+ ex1['impact'].must_equal 0.7
317
+ ex2['impact'].must_be :nil?
318
+ end
319
+
320
+ it 'status in json' do
321
+ ex1['status'].must_equal 'passed'
322
+ ex3['status'].must_equal 'pending'
323
+ end
324
+
325
+ it 'ref in json' do
326
+ ex1['ref'].must_match %r{examples/profile/controls/example.rb$}
327
+ end
328
+
329
+ it 'ref_line in json' do
330
+ ex1['ref_line'].must_equal 14
331
+ end
332
+
333
+ it 'run_time in json' do
334
+ ex1['run_time'].wont_be :nil?
335
+ end
336
+
337
+ it 'start_time in json' do
338
+ ex1['start_time'].wont_be :nil?
339
+ end
340
+
341
+ it 'pending message in json' do
342
+ ex1['pending'].must_be :nil?
343
+ ex3['pending'].must_equal "Can't find file \"/etc/gordon/config.yaml\""
344
+ end
345
+ end
346
+ end
347
+
348
+ describe 'example inheritance profile' do
349
+ let(:path) { File.join(examples_path, 'inheritance') }
350
+
351
+ [
352
+ 'archive %s --overwrite',
353
+ 'check %s',
354
+ 'json %s',
355
+ ].each do |cmd|
356
+ it cmd[/^\w/] + ' fails without --profiles-path' do
357
+ out = inspec(format(cmd, path))
358
+ out.stderr.must_include 'You must supply a --profiles-path to inherit'
359
+ # out.stdout.must_equal '' => we still get partial output
360
+ out.exit_status.must_equal 1
361
+ end
362
+ end
363
+
364
+ it 'check succeeds with --profiles-path' do
365
+ out = inspec('check ' + path + ' --profiles-path ' + examples_path)
366
+ out.stderr.must_equal ''
367
+ out.stdout.must_match /Valid.*true/
368
+ out.exit_status.must_equal 0
369
+ end
370
+
371
+ it 'archive is successful with --profiles-path' do
372
+ out = inspec('archive ' + path + ' --output ' + dst.path + ' --profiles-path ' + examples_path)
373
+ out.stderr.must_equal ''
374
+ out.stdout.must_include 'Generate archive '+dst.path
375
+ out.stdout.must_include 'Finished archive generation.'
376
+ out.exit_status.must_equal 0
377
+ File.exist?(dst.path).must_equal true
378
+ end
379
+
380
+ it 'read the profile json with --profiles-path' do
381
+ out = inspec('json ' + path + ' --profiles-path '+examples_path)
382
+ out.stderr.must_equal ''
383
+ out.exit_status.must_equal 0
384
+ s = out.stdout
385
+ hm = JSON.load(s)
386
+ hm['name'].must_equal 'inheritance'
387
+ hm['rules'].length.must_equal 1 # TODO: flatten out or search deeper!
388
+ end
389
+ end
390
+ end