inspec 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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