chef-dk 0.12.0 → 0.13.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,60 +1,60 @@
1
- #
2
- # Copyright:: Copyright (c) 2014 Chef Software Inc.
3
- # License:: Apache License, Version 2.0
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
- #
17
-
18
-
19
- ChefDK.commands do |c|
20
- c.builtin "exec", :Exec, require_path: "chef-dk/command/exec",
21
- desc: "Runs the command in context of the embedded ruby"
22
-
23
- c.builtin "env", :Env, require_path: "chef-dk/command/env",
24
- desc: "Prints environment variables used by ChefDK"
25
-
26
- c.builtin "gem", :GemForwarder, require_path: "chef-dk/command/gem",
27
- desc: "Runs the `gem` command in context of the embedded ruby"
28
-
29
- c.builtin "generate", :Generate, desc: "Generate a new app, cookbook, or component"
30
-
31
- c.builtin "shell-init", :ShellInit, desc: "Initialize your shell to use ChefDK as your primary ruby"
32
-
33
- c.builtin "install", :Install, desc: "Install cookbooks from a Policyfile and generate a locked cookbook set"
34
-
35
- c.builtin "update", :Update, desc: "Updates a Policyfile.lock.json with latest run_list and cookbooks"
36
-
37
- c.builtin "push", :Push, desc: "Push a local policy lock to a policy group on the server"
38
-
39
- c.builtin "push-archive", :PushArchive, desc: "Push a policy archive to a policy group on the server"
40
-
41
- c.builtin "show-policy", :ShowPolicy, desc: "Show policyfile objects on your Chef Server"
42
-
43
- c.builtin "diff", :Diff, desc: "Generate an itemized diff of two Policyfile lock documents"
44
-
45
- c.builtin "provision", :Provision, desc: "Provision VMs and clusters via cookbook"
46
-
47
- c.builtin "export", :Export, desc: "Export a policy lock as a Chef Zero code repo"
48
-
49
- c.builtin "clean-policy-revisions", :CleanPolicyRevisions, desc: "Delete unused policy revisions on the server"
50
-
51
- c.builtin "clean-policy-cookbooks", :CleanPolicyCookbooks, desc: "Delete unused policyfile cookbooks on the server"
52
-
53
- c.builtin "delete-policy-group", :DeletePolicyGroup, desc: "Delete a policy group on the server"
54
-
55
- c.builtin "delete-policy", :DeletePolicy, desc: "Delete all revisions of a policy on the server"
56
-
57
- c.builtin "undelete", :Undelete, desc: "Undo a delete command"
58
-
59
- c.builtin "verify", :Verify, desc: "Test the embedded ChefDK applications"
60
- end
1
+ #
2
+ # Copyright:: Copyright (c) 2016 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+
19
+ ChefDK.commands do |c|
20
+ c.builtin "exec", :Exec, require_path: "chef-dk/command/exec",
21
+ desc: "Runs the command in context of the embedded ruby"
22
+
23
+ c.builtin "env", :Env, require_path: "chef-dk/command/env",
24
+ desc: "Prints environment variables used by ChefDK"
25
+
26
+ c.builtin "gem", :GemForwarder, require_path: "chef-dk/command/gem",
27
+ desc: "Runs the `gem` command in context of the embedded ruby"
28
+
29
+ c.builtin "generate", :Generate, desc: "Generate a new app, cookbook, or component"
30
+
31
+ c.builtin "shell-init", :ShellInit, desc: "Initialize your shell to use ChefDK as your primary ruby"
32
+
33
+ c.builtin "install", :Install, desc: "Install cookbooks from a Policyfile and generate a locked cookbook set"
34
+
35
+ c.builtin "update", :Update, desc: "Updates a Policyfile.lock.json with latest run_list and cookbooks"
36
+
37
+ c.builtin "push", :Push, desc: "Push a local policy lock to a policy group on the server"
38
+
39
+ c.builtin "push-archive", :PushArchive, desc: "Push a policy archive to a policy group on the server"
40
+
41
+ c.builtin "show-policy", :ShowPolicy, desc: "Show policyfile objects on your Chef Server"
42
+
43
+ c.builtin "diff", :Diff, desc: "Generate an itemized diff of two Policyfile lock documents"
44
+
45
+ c.builtin "provision", :Provision, desc: "Provision VMs and clusters via cookbook"
46
+
47
+ c.builtin "export", :Export, desc: "Export a policy lock as a Chef Zero code repo"
48
+
49
+ c.builtin "clean-policy-revisions", :CleanPolicyRevisions, desc: "Delete unused policy revisions on the server"
50
+
51
+ c.builtin "clean-policy-cookbooks", :CleanPolicyCookbooks, desc: "Delete unused policyfile cookbooks on the server"
52
+
53
+ c.builtin "delete-policy-group", :DeletePolicyGroup, desc: "Delete a policy group on the server"
54
+
55
+ c.builtin "delete-policy", :DeletePolicy, desc: "Delete all revisions of a policy on the server"
56
+
57
+ c.builtin "undelete", :Undelete, desc: "Undo a delete command"
58
+
59
+ c.builtin "verify", :Verify, desc: "Test the embedded ChefDK applications"
60
+ end
@@ -1,534 +1,564 @@
1
- #
2
- # Copyright:: Copyright (c) 2014 Chef Software Inc.
3
- # License:: Apache License, Version 2.0
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
- #
17
-
18
- require 'chef-dk/command/base'
19
- require 'chef-dk/exceptions'
20
- require 'chef-dk/component_test'
21
-
22
- module ChefDK
23
- module Command
24
- class Verify < ChefDK::Command::Base
25
-
26
- include ChefDK::Helpers
27
-
28
- banner "Usage: chef verify [component, ...] [options]"
29
-
30
- option :omnibus_dir,
31
- :long => "--omnibus-dir OMNIBUS_DIR",
32
- :description => "Alternate path to omnibus install (used for testing)"
33
-
34
- option :unit,
35
- :long => "--unit",
36
- :description => "Run bundled app unit tests (only smoke tests run by default)"
37
-
38
- option :integration,
39
- :long => "--integration",
40
- :description => "Run integration tests. Possibly dangerous, for development systems only"
41
-
42
- option :verbose,
43
- :long => "--verbose",
44
- :description => "Display all test output, not just failing tests"
45
-
46
- class << self
47
- def add_component(name, _delete_me=nil)
48
- component = ComponentTest.new(name)
49
- yield component if block_given? #delete this conditional
50
- component_map[name] = component
51
- end
52
-
53
- def component(name)
54
- component_map[name]
55
- end
56
-
57
- def components
58
- component_map.values
59
- end
60
-
61
- def component_map
62
- @component_map ||= {}
63
- end
64
- end
65
-
66
- def components
67
- self.class.components
68
- end
69
-
70
- #
71
- # Components included in Chef Development kit:
72
- # :base_dir => Relative path of the component w.r.t. omnibus_apps_dir
73
- # :gem_base_dir => Takes a gem name instead and uses first gem found
74
- # :test_cmd => Test command to be launched for the component
75
- #
76
- add_component "berkshelf" do |c|
77
- c.gem_base_dir = "berkshelf"
78
- # For berks the real command to run is "bundle exec thor spec:ci"
79
- # We can't run it right now since graphviz specs are included in the
80
- # test suite by default. We will be able to switch to that command when/if
81
- # Graphviz is added to omnibus.
82
- c.unit_test { sh("bundle exec rspec --color --format progress spec/unit --tag ~graphviz") }
83
- c.integration_test { sh("bundle exec cucumber --color --format progress --tags ~@no_run --tags ~@spawn --tags ~@graphviz --strict") }
84
-
85
- c.smoke_test do
86
- tmpdir do |cwd|
87
- FileUtils.touch(File.join(cwd,"Berksfile"))
88
- sh("berks install", cwd: cwd)
89
- end
90
- end
91
- end
92
-
93
- add_component "test-kitchen" do |c|
94
- c.gem_base_dir = "test-kitchen"
95
- c.unit_test { sh("bundle exec rake unit") }
96
- c.integration_test { sh("bundle exec rake features") }
97
-
98
- # NOTE: By default, kitchen tries to be helpful and install a driver
99
- # gem for you. This causes a race condition when running the tests
100
- # concurrently, because rubygems breaks when there are partially
101
- # installed gems in the gem repository. Instructing kitchen to create a
102
- # gemfile instead avoids the gem installation.
103
- c.smoke_test { run_in_tmpdir("kitchen init --create-gemfile") }
104
- end
105
-
106
- add_component "tk-policyfile-provisioner" do |c|
107
-
108
- c.gem_base_dir = "chef-dk"
109
-
110
- c.smoke_test do
111
- tmpdir do |cwd|
112
- File.open(File.join(cwd, ".kitchen.yml"), "w+") do |f|
113
- f.print(<<-KITCHEN_YML)
114
- ---
115
- driver:
116
- name: dummy
117
- network:
118
- - ["forwarded_port", {guest: 80, host: 8080}]
119
-
120
- provisioner:
121
- name: policyfile_zero
122
- require_chef_omnibus: 12.3.0
123
-
124
- platforms:
125
- - name: ubuntu-14.04
126
-
127
- suites:
128
- - name: default
129
- run_list:
130
- - recipe[aar::default]
131
- attributes:
132
-
133
- KITCHEN_YML
134
- end
135
-
136
- sh("kitchen list", cwd: cwd)
137
-
138
- end
139
- end
140
-
141
- end
142
-
143
- add_component "chef-client" do |c|
144
- c.gem_base_dir = "chef"
145
- c.unit_test { sh("bundle exec rspec -fp -t '~volatile_from_verify' spec/unit") }
146
- c.integration_test { sh("bundle exec rspec -fp spec/integration spec/functional") }
147
-
148
- c.smoke_test do
149
- tmpdir do |cwd|
150
- FileUtils.touch(File.join(cwd, "apply.rb"))
151
- sh("chef-apply apply.rb", cwd: cwd)
152
- end
153
- end
154
- end
155
-
156
- add_component "chef-dk" do |c|
157
- c.gem_base_dir = "chef-dk"
158
- c.unit_test { sh("bundle exec rspec") }
159
- c.smoke_test { run_in_tmpdir("chef generate cookbook example") }
160
- end
161
-
162
- # entirely possible this needs to be driven by a utility method in chef-provisioning.
163
- add_component "chef-provisioning" do |c|
164
- c.gem_base_dir = "chef-dk"
165
-
166
- c.smoke_test do
167
- # ------------
168
- # we want to avoid hard-coding driver names, but calling Gem::Specification produces a warning;
169
- # changing $VERBOSE seems to be the best way to silence it.
170
- verbose = $VERBOSE
171
- $VERBOSE = nil
172
-
173
- # construct a hash of { driver_name => [version1, version2, ...]}
174
- driver_versions = {}
175
- Gem::Specification.all.map { |gs| [gs.name, gs.version] }.
176
- select { |n| n[0] =~ /^chef-provisioning-/ }.
177
- each { |gem, version| (driver_versions[gem] ||= []) << version }
178
-
179
- drivers = Gem::Specification.all.map { |gs| gs.name }.
180
- select { |n| n =~ /^chef-provisioning-/ }.
181
- uniq
182
-
183
- versions = Gem::Specification.find_all_by_name("chef-provisioning").map { |s| s.version }
184
- $VERBOSE = verbose
185
- # ------------
186
- failures = []
187
-
188
- # ------------
189
- # fail the verify if we have more than one version of chef-provisioning or any of its drivers.
190
- def format_gem_failure(name, versions)
191
- <<-EOS
192
- #{name} has multiple versions installed:
193
- #{versions.sort.map { |gv| " #{gv.to_s}" }.join("\n")}
194
- EOS
195
- end
196
-
197
- failures << format_gem_failure("chef-provisioning", versions) if versions.size > 1
198
-
199
- driver_versions.keys.sort.each do |driver_name|
200
- v = driver_versions[driver_name]
201
- failures << format_gem_failure(driver_name, v) if v.size > 1
202
- end
203
-
204
- if failures.size > 0
205
- failures << <<-EOS
206
-
207
- Some applications may need or prefer different versions of the chef-provisioning gem or its drivers, so
208
- this multiple-version check can fail if a user has installed new versions of those libraries.
209
- EOS
210
- end
211
-
212
- # ------------
213
- # load the core gem and all of the drivers (ignoring versions).
214
- require "chef/provisioning"
215
- drivers.map { |d| "#{d.gsub('-', '/')}_driver" }.each do |driver_gem|
216
- begin
217
- begin
218
- require driver_gem
219
- rescue LoadError
220
- # anomalously, chef-provisioning-fog does not have a fog_driver.rb. (9/2015)
221
- require "#{driver_gem}/driver.rb"
222
- end
223
- rescue LoadError => ex
224
- puts ex
225
- end
226
- end
227
-
228
- # ------------
229
- # look for version dependency conflicts.
230
- tmpdir do |cwd|
231
- versions.each do |provisioning_version|
232
- gemfile = "chef-provisioning-#{provisioning_version}-chefdk-test.gemfile"
233
-
234
- # write out the gemfile for this chef-provisioning version, and see if Bundler can make it go.
235
- with_file(File.join(cwd, gemfile)) do |f|
236
- f.puts %Q(gem "chef-provisioning", "= #{provisioning_version}")
237
- drivers.each { |d| f.puts %Q(gem "#{d}") }
238
- end
239
-
240
- result = sh("bundle install --local --quiet", cwd: cwd, env: {"BUNDLE_GEMFILE" => gemfile })
241
-
242
- if result.exitstatus != 0
243
- failures << result.stdout
244
- end
245
- end # end provisioning versions.
246
-
247
- failures.each { |fail| puts fail }
248
-
249
- # dubious on Windows.
250
- # this is weird, but we seem to require a Mixlib::ShellOut as the return value. suggestions
251
- # welcome.
252
- sh(failures.size > 0 ? "false" : "true")
253
- end
254
- end
255
- end
256
-
257
- add_component "chefspec" do |c|
258
- c.gem_base_dir = "chefspec"
259
- c.unit_test { sh("rake unit") }
260
- c.smoke_test do
261
- tmpdir do |cwd|
262
- FileUtils.mkdir(File.join(cwd, "spec"))
263
- with_file(File.join(cwd, "spec", "spec_helper.rb")) do |f|
264
- f.write <<-EOF
265
- require 'chefspec'
266
- require 'chefspec/berkshelf'
267
- require 'chefspec/cacher'
268
-
269
- RSpec.configure do |config|
270
- config.expect_with(:rspec) { |c| c.syntax = :expect }
271
- end
272
- EOF
273
- end
274
- FileUtils.touch(File.join(cwd, "Berksfile"))
275
- with_file(File.join(cwd, "spec", "foo_spec.rb")) do |f|
276
- f.write <<-EOF
277
- require 'spec_helper'
278
- EOF
279
- end
280
- sh("rspec", cwd: cwd)
281
- end
282
- end
283
- end
284
-
285
- add_component "generated-cookbooks-pass-chefspec" do |c|
286
-
287
- c.gem_base_dir = "chef-dk"
288
- c.smoke_test do
289
- tmpdir do |cwd|
290
- sh("chef generate cookbook example", cwd: cwd)
291
- cb_cwd = File.join(cwd, "example")
292
- sh("rspec", cwd: cb_cwd)
293
- end
294
- end
295
- end
296
-
297
- add_component "rubocop" do |c|
298
- c.gem_base_dir = "rubocop"
299
- c.smoke_test do
300
- tmpdir do |cwd|
301
- with_file(File.join(cwd, 'foo.rb')) do |f|
302
- f.write <<-EOF
303
- def foo
304
- puts 'foo'
305
- end
306
- EOF
307
- end
308
- sh("rubocop foo.rb -l", cwd: cwd)
309
- end
310
- end
311
- end
312
-
313
- add_component "fauxhai" do |c|
314
- c.gem_base_dir = "fauxhai"
315
- c.smoke_test { sh("gem list fauxhai") }
316
- end
317
-
318
- add_component "knife-spork" do |c|
319
- c.gem_base_dir = "knife-spork"
320
- c.smoke_test { sh('knife spork info')}
321
- end
322
-
323
- add_component "kitchen-vagrant" do |c|
324
- c.gem_base_dir = "kitchen-vagrant"
325
- # The build is not passing in travis, so no tests
326
- c.smoke_test { sh("gem list kitchen-vagrant") }
327
- end
328
-
329
- add_component "package installation" do |c|
330
-
331
- c.gem_base_dir = "chef-dk"
332
-
333
- c.smoke_test do
334
-
335
- if File.directory?(usr_bin_prefix)
336
- sh!("#{usr_bin_path("berks")} -v")
337
-
338
- sh!("#{usr_bin_path("chef")} -v")
339
-
340
- sh!("#{usr_bin_path("chef-client")} -v")
341
- sh!("#{usr_bin_path("chef-solo")} -v")
342
-
343
- # In `knife`, `knife -v` follows a different code path that skips
344
- # command/plugin loading; `knife -h` loads commands and plugins, but
345
- # it exits with code 1, which is the same as a load error. Running
346
- # `knife exec` forces command loading to happen and this command
347
- # exits 0, which runs most of the code.
348
- #
349
- # See also: https://github.com/chef/chef-dk/issues/227
350
- sh!("#{usr_bin_path("knife")} exec -E true")
351
-
352
- tmpdir do |dir|
353
- # Kitchen tries to create a .kitchen dir even when just running
354
- # `kitchen -v`:
355
- sh!("#{usr_bin_path("kitchen")} -v", cwd: dir)
356
- end
357
-
358
- sh!("#{usr_bin_path("ohai")} -v")
359
-
360
- sh!("#{usr_bin_path("foodcritic")} -V")
361
- end
362
-
363
- # Test blocks are expected to return a Mixlib::ShellOut compatible
364
- # object:
365
- ComponentTest::NullTestResult.new
366
- end
367
- end
368
-
369
- add_component "openssl" do |c|
370
- # https://github.com/chef/chef-dk/issues/420
371
- c.gem_base_dir = "chef"
372
-
373
- test = <<-EOF.gsub(/^\s+/, "")
374
- require "net/http"
375
-
376
- uris = %w{https://www.google.com https://chef.io/ https://ec2.amazonaws.com}
377
- uris.each do |uri|
378
- uri = URI(uri)
379
- puts "Fetching \#{uri} for SSL check"
380
- Net::HTTP.get uri
381
- end
382
- EOF
383
-
384
- c.unit_test do
385
- tmpdir do |cwd|
386
- with_file(File.join(cwd, "openssl.rb")) do |f|
387
- f.write test
388
- end
389
- sh!("#{Gem.ruby} openssl.rb", cwd: cwd)
390
- end
391
- end
392
- end
393
-
394
- add_component "inspec" do |c|
395
- c.gem_base_dir = "inspec"
396
-
397
- # Commenting out the unit and integration tests for now until we figure
398
- # out the bundler error
399
- #c.unit_test { sh("bundle exec rake test:isolated") }
400
- # This runs Test Kitchen (using kitchen-inspec) with some inspec tests
401
- #c.integration_test { sh("bundle exec rake test:vm") }
402
-
403
- # It would be nice to use a chef generator to create these specs, but
404
- # we dont have that yet. So we do it manually.
405
- c.smoke_test do
406
- tmpdir do |cwd|
407
- File.open(File.join(cwd, "some_spec.rb"), "w+") do |f|
408
- f.print <<-INSPEC_TEST
409
- rule '01' do
410
- impact 0.7
411
- title 'Some Test'
412
- desc 'Make sure inspec is installed and loading correct'
413
- describe 1 do
414
- it { should eq(1) }
415
- end
416
- end
417
- INSPEC_TEST
418
- end
419
- # TODO when we appbundle inspec, no longer `chef exec`
420
- sh("chef exec inspec exec .", cwd: cwd)
421
- end
422
- end
423
- end
424
-
425
- attr_reader :verification_threads
426
- attr_reader :verification_results
427
- attr_reader :verification_status
428
-
429
- def initialize
430
- super
431
- @verification_threads = [ ]
432
- @verification_results = [ ]
433
- @verification_status = 0
434
- end
435
-
436
- def run(params = [ ])
437
- @components_filter = parse_options(params)
438
-
439
- validate_components!
440
- invoke_tests
441
- wait_for_tests
442
- report_results
443
-
444
- verification_status
445
- end
446
-
447
- def omnibus_root
448
- config[:omnibus_dir] || super
449
- end
450
-
451
- def validate_components!
452
- components.each do |component|
453
- component.omnibus_root = omnibus_root
454
- component.assert_present!
455
- end
456
- end
457
-
458
- def components_to_test
459
- if @components_filter.empty?
460
- components
461
- else
462
- components.select do |component|
463
- @components_filter.include?(component.name.to_s)
464
- end
465
- end
466
- end
467
-
468
- def invoke_tests
469
- components_to_test.each do |component|
470
- # Run the component specs in parallel
471
- verification_threads << Thread.new do
472
-
473
- results = []
474
-
475
- results << component.run_smoke_test
476
-
477
- if config[:unit]
478
- results << component.run_unit_test
479
- end
480
-
481
- if config[:integration]
482
- results << component.run_integration_test
483
- end
484
-
485
- if results.any? {|r| r.exitstatus != 0 }
486
- component_status = 1
487
- @verification_status = 1
488
- else
489
- component_status = 0
490
- end
491
-
492
- {
493
- :component => component,
494
- :results => results,
495
- :component_status => component_status
496
- }
497
- end
498
-
499
- msg("Running verification for component '#{component.name}'")
500
- end
501
- end
502
-
503
- def wait_for_tests
504
- while !verification_threads.empty?
505
- verification_threads.each do |t|
506
- if t.join(1)
507
- verification_threads.delete t
508
- verification_results << t.value
509
- t.value[:results].each do |result|
510
- if config[:verbose] || t.value[:component_status] != 0
511
- msg("")
512
- msg(result.stdout)
513
- msg(result.stderr) if result.stderr
514
- end
515
- end
516
- else
517
- $stdout.write "."
518
- end
519
- end
520
- end
521
- end
522
-
523
- def report_results
524
- msg("")
525
- msg("---------------------------------------------")
526
- verification_results.each do |result|
527
- message = result[:component_status] == 0 ? "succeeded" : "failed"
528
- msg("Verification of component '#{result[:component].name}' #{message}.")
529
- end
530
- end
531
-
532
- end
533
- end
534
- end
1
+ #
2
+ # Copyright:: Copyright (c) 2014 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'chef-dk/command/base'
19
+ require 'chef-dk/exceptions'
20
+ require 'chef-dk/component_test'
21
+
22
+ module ChefDK
23
+ module Command
24
+ class Verify < ChefDK::Command::Base
25
+
26
+ include ChefDK::Helpers
27
+
28
+ banner "Usage: chef verify [component, ...] [options]"
29
+
30
+ option :omnibus_dir,
31
+ :long => "--omnibus-dir OMNIBUS_DIR",
32
+ :description => "Alternate path to omnibus install (used for testing)"
33
+
34
+ option :unit,
35
+ :long => "--unit",
36
+ :description => "Run bundled app unit tests (only smoke tests run by default)"
37
+
38
+ option :integration,
39
+ :long => "--integration",
40
+ :description => "Run integration tests. Possibly dangerous, for development systems only"
41
+
42
+ option :verbose,
43
+ :long => "--verbose",
44
+ :description => "Display all test output, not just failing tests"
45
+
46
+ class << self
47
+ def add_component(name, _delete_me=nil)
48
+ component = ComponentTest.new(name)
49
+ yield component if block_given? #delete this conditional
50
+ component_map[name] = component
51
+ end
52
+
53
+ def component(name)
54
+ component_map[name]
55
+ end
56
+
57
+ def components
58
+ component_map.values
59
+ end
60
+
61
+ def component_map
62
+ @component_map ||= {}
63
+ end
64
+ end
65
+
66
+ def components
67
+ self.class.components
68
+ end
69
+
70
+ bundle_install_mutex = Mutex.new
71
+
72
+ #
73
+ # Components included in Chef Development kit:
74
+ # :base_dir => Relative path of the component w.r.t. omnibus_apps_dir
75
+ # :gem_base_dir => Takes a gem name instead and uses first gem found
76
+ # :test_cmd => Test command to be launched for the component
77
+ #
78
+ add_component "berkshelf" do |c|
79
+ c.gem_base_dir = "berkshelf"
80
+ # For berks the real command to run is "#{bin("bundle")} exec thor spec:ci"
81
+ # We can't run it right now since graphviz specs are included in the
82
+ # test suite by default. We will be able to switch to that command when/if
83
+ # Graphviz is added to omnibus.
84
+ c.unit_test do
85
+ bundle_install_mutex.synchronize { sh("#{bin("bundle")} install") }
86
+ sh("#{bin("bundle")} exec #{bin("rspec")} --color --format progress spec/unit --tag ~graphviz")
87
+ end
88
+ c.integration_test do
89
+ bundle_install_mutex.synchronize { sh("#{bin("bundle")} install") }
90
+ sh("#{bin("bundle")} exec #{bin("cucumber")} --color --format progress --tags ~@no_run --tags ~@spawn --tags ~@graphviz --strict")
91
+ end
92
+
93
+ c.smoke_test do
94
+ tmpdir do |cwd|
95
+ FileUtils.touch(File.join(cwd,"Berksfile"))
96
+ sh("#{bin("berks")} install", cwd: cwd)
97
+ end
98
+ end
99
+ end
100
+
101
+ add_component "test-kitchen" do |c|
102
+ c.gem_base_dir = "test-kitchen"
103
+ c.unit_test do
104
+ bundle_install_mutex.synchronize { sh("#{bin("bundle")} install") }
105
+ sh("#{bin("bundle")} exec rake unit")
106
+ end
107
+ c.integration_test do
108
+ bundle_install_mutex.synchronize { sh("#{bin("bundle")} install") }
109
+ sh("#{bin("bundle")} exec rake features")
110
+ end
111
+
112
+ # NOTE: By default, kitchen tries to be helpful and install a driver
113
+ # gem for you. This causes a race condition when running the tests
114
+ # concurrently, because rubygems breaks when there are partially
115
+ # installed gems in the gem repository. Instructing kitchen to create a
116
+ # gemfile instead avoids the gem installation.
117
+ c.smoke_test { run_in_tmpdir("kitchen init --create-gemfile") }
118
+ end
119
+
120
+ add_component "tk-policyfile-provisioner" do |c|
121
+
122
+ c.gem_base_dir = "chef-dk"
123
+
124
+ c.smoke_test do
125
+ tmpdir do |cwd|
126
+ File.open(File.join(cwd, ".kitchen.yml"), "w+") do |f|
127
+ f.print(<<-KITCHEN_YML)
128
+ ---
129
+ driver:
130
+ name: dummy
131
+ network:
132
+ - ["forwarded_port", {guest: 80, host: 8080}]
133
+
134
+ provisioner:
135
+ name: policyfile_zero
136
+ require_chef_omnibus: 12.3.0
137
+
138
+ platforms:
139
+ - name: ubuntu-14.04
140
+
141
+ suites:
142
+ - name: default
143
+ run_list:
144
+ - recipe[aar::default]
145
+ attributes:
146
+
147
+ KITCHEN_YML
148
+ end
149
+
150
+ sh("kitchen list", cwd: cwd)
151
+
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+ add_component "chef-client" do |c|
158
+ c.gem_base_dir = "chef"
159
+ c.unit_test do
160
+ bundle_install_mutex.synchronize { sh("#{bin("bundle")} install") }
161
+ sh("#{bin("bundle")} exec #{bin("rspec")} -fp -t '~volatile_from_verify' spec/unit")
162
+ end
163
+ c.integration_test do
164
+ bundle_install_mutex.synchronize { sh("#{bin("bundle")} install") }
165
+ sh("#{bin("bundle")} exec #{bin("rspec")} -fp spec/integration spec/functional")
166
+ end
167
+
168
+ c.smoke_test do
169
+ tmpdir do |cwd|
170
+ FileUtils.touch(File.join(cwd, "apply.rb"))
171
+ sh("#{bin("chef-apply")} apply.rb", cwd: cwd)
172
+ end
173
+ end
174
+ end
175
+
176
+ add_component "chef-dk" do |c|
177
+ c.gem_base_dir = "chef-dk"
178
+ c.unit_test do
179
+ bundle_install_mutex.synchronize { sh("#{bin("bundle")} install") }
180
+ sh("#{bin("bundle")} exec #{bin("rspec")}")
181
+ end
182
+ c.smoke_test do
183
+ run_in_tmpdir("#{bin("chef")} generate cookbook example")
184
+ end
185
+ end
186
+
187
+ # entirely possible this needs to be driven by a utility method in chef-provisioning.
188
+ add_component "chef-provisioning" do |c|
189
+ c.gem_base_dir = "chef-dk"
190
+
191
+ c.smoke_test do
192
+ # ------------
193
+ # we want to avoid hard-coding driver names, but calling Gem::Specification produces a warning;
194
+ # changing $VERBOSE seems to be the best way to silence it.
195
+ verbose = $VERBOSE
196
+ $VERBOSE = nil
197
+
198
+ # construct a hash of { driver_name => [version1, version2, ...]}
199
+ driver_versions = {}
200
+ Gem::Specification.all.map { |gs| [gs.name, gs.version] }.
201
+ select { |n| n[0] =~ /^chef-provisioning-/ }.
202
+ each { |gem, version| (driver_versions[gem] ||= []) << version }
203
+
204
+ drivers = Gem::Specification.all.map { |gs| gs.name }.
205
+ select { |n| n =~ /^chef-provisioning-/ }.
206
+ uniq
207
+
208
+ versions = Gem::Specification.find_all_by_name("chef-provisioning").map { |s| s.version }
209
+ $VERBOSE = verbose
210
+ # ------------
211
+ failures = []
212
+
213
+ # ------------
214
+ # fail the verify if we have more than one version of chef-provisioning or any of its drivers.
215
+ def format_gem_failure(name, versions)
216
+ <<-EOS
217
+ #{name} has multiple versions installed:
218
+ #{versions.sort.map { |gv| " #{gv.to_s}" }.join("\n")}
219
+ EOS
220
+ end
221
+
222
+ failures << format_gem_failure("chef-provisioning", versions) if versions.size > 1
223
+
224
+ driver_versions.keys.sort.each do |driver_name|
225
+ v = driver_versions[driver_name]
226
+ failures << format_gem_failure(driver_name, v) if v.size > 1
227
+ end
228
+
229
+ if failures.size > 0
230
+ failures << <<-EOS
231
+
232
+ Some applications may need or prefer different versions of the chef-provisioning gem or its drivers, so
233
+ this multiple-version check can fail if a user has installed new versions of those libraries.
234
+ EOS
235
+ end
236
+
237
+ # ------------
238
+ # load the core gem and all of the drivers (ignoring versions).
239
+ require "chef/provisioning"
240
+ drivers.map { |d| "#{d.gsub('-', '/')}_driver" }.each do |driver_gem|
241
+ begin
242
+ begin
243
+ require driver_gem
244
+ rescue LoadError
245
+ # anomalously, chef-provisioning-fog does not have a fog_driver.rb. (9/2015)
246
+ require "#{driver_gem}/driver.rb"
247
+ end
248
+ rescue LoadError => ex
249
+ puts ex
250
+ end
251
+ end
252
+
253
+ # ------------
254
+ # look for version dependency conflicts.
255
+ tmpdir do |cwd|
256
+ versions.each do |provisioning_version|
257
+ gemfile = "chef-provisioning-#{provisioning_version}-chefdk-test.gemfile"
258
+
259
+ # write out the gemfile for this chef-provisioning version, and see if Bundler can make it go.
260
+ with_file(File.join(cwd, gemfile)) do |f|
261
+ f.puts %Q(gem "chef-provisioning", "= #{provisioning_version}")
262
+ drivers.each { |d| f.puts %Q(gem "#{d}") }
263
+ end
264
+
265
+ result = bundle_install_mutex.synchronize do
266
+ sh("#{bin("bundle")} install --local --quiet", cwd: cwd, env: {"BUNDLE_GEMFILE" => gemfile })
267
+ end
268
+
269
+ if result.exitstatus != 0
270
+ failures << result.stdout
271
+ end
272
+ end # end provisioning versions.
273
+
274
+ failures.each { |fail| puts fail }
275
+
276
+ # dubious on Windows.
277
+ # this is weird, but we seem to require a Mixlib::ShellOut as the return value. suggestions
278
+ # welcome.
279
+ sh(failures.size > 0 ? "false" : "true")
280
+ end
281
+ end
282
+ end
283
+
284
+ add_component "chefspec" do |c|
285
+ c.gem_base_dir = "chefspec"
286
+ c.unit_test do
287
+ bundle_install_mutex.synchronize { sh("#{bin("bundle")} install") }
288
+ sh("#{bin("bundle")} exec #{bin("rake")} unit")
289
+ end
290
+ c.smoke_test do
291
+ tmpdir do |cwd|
292
+ FileUtils.mkdir(File.join(cwd, "spec"))
293
+ with_file(File.join(cwd, "spec", "spec_helper.rb")) do |f|
294
+ f.write <<-EOF
295
+ require 'chefspec'
296
+ require 'chefspec/berkshelf'
297
+ require 'chefspec/cacher'
298
+
299
+ RSpec.configure do |config|
300
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
301
+ end
302
+ EOF
303
+ end
304
+ FileUtils.touch(File.join(cwd, "Berksfile"))
305
+ with_file(File.join(cwd, "spec", "foo_spec.rb")) do |f|
306
+ f.write <<-EOF
307
+ require 'spec_helper'
308
+ EOF
309
+ end
310
+ sh(bin("rspec"), cwd: cwd)
311
+ end
312
+ end
313
+ end
314
+
315
+ add_component "generated-cookbooks-pass-chefspec" do |c|
316
+
317
+ c.gem_base_dir = "chef-dk"
318
+ c.smoke_test do
319
+ tmpdir do |cwd|
320
+ sh("#{bin("chef")} generate cookbook example", cwd: cwd)
321
+ cb_cwd = File.join(cwd, "example")
322
+ sh(bin("rspec"), cwd: cb_cwd)
323
+ end
324
+ end
325
+ end
326
+
327
+ add_component "rubocop" do |c|
328
+ c.gem_base_dir = "rubocop"
329
+ c.smoke_test do
330
+ tmpdir do |cwd|
331
+ with_file(File.join(cwd, 'foo.rb')) do |f|
332
+ f.write <<-EOF
333
+ def foo
334
+ puts 'foo'
335
+ end
336
+ EOF
337
+ end
338
+ sh("#{bin("rubocop")} foo.rb -l", cwd: cwd)
339
+ end
340
+ end
341
+ end
342
+
343
+ add_component "fauxhai" do |c|
344
+ c.gem_base_dir = "fauxhai"
345
+ c.smoke_test { sh("#{bin("gem")} list fauxhai") }
346
+ end
347
+
348
+ add_component "knife-spork" do |c|
349
+ c.gem_base_dir = "knife-spork"
350
+ c.smoke_test { sh("#{bin("knife")} spork info")}
351
+ end
352
+
353
+ add_component "kitchen-vagrant" do |c|
354
+ c.gem_base_dir = "kitchen-vagrant"
355
+ # The build is not passing in travis, so no tests
356
+ c.smoke_test { sh("#{bin("gem")} list kitchen-vagrant") }
357
+ end
358
+
359
+ add_component "package installation" do |c|
360
+
361
+ c.gem_base_dir = "chef-dk"
362
+
363
+ c.smoke_test do
364
+
365
+ if File.directory?(usr_bin_prefix)
366
+ sh!("#{usr_bin_path("berks")} -v")
367
+
368
+ sh!("#{usr_bin_path("chef")} -v")
369
+
370
+ sh!("#{usr_bin_path("chef-client")} -v")
371
+ sh!("#{usr_bin_path("chef-solo")} -v")
372
+
373
+ # In `knife`, `knife -v` follows a different code path that skips
374
+ # command/plugin loading; `knife -h` loads commands and plugins, but
375
+ # it exits with code 1, which is the same as a load error. Running
376
+ # `knife exec` forces command loading to happen and this command
377
+ # exits 0, which runs most of the code.
378
+ #
379
+ # See also: https://github.com/chef/chef-dk/issues/227
380
+ sh!("#{usr_bin_path("knife")} exec -E true")
381
+
382
+ tmpdir do |dir|
383
+ # Kitchen tries to create a .kitchen dir even when just running
384
+ # `kitchen -v`:
385
+ sh!("#{usr_bin_path("kitchen")} -v", cwd: dir)
386
+ end
387
+
388
+ sh!("#{usr_bin_path("ohai")} -v")
389
+
390
+ sh!("#{usr_bin_path("foodcritic")} -V")
391
+ end
392
+
393
+ # Test blocks are expected to return a Mixlib::ShellOut compatible
394
+ # object:
395
+ ComponentTest::NullTestResult.new
396
+ end
397
+ end
398
+
399
+ add_component "openssl" do |c|
400
+ # https://github.com/chef/chef-dk/issues/420
401
+ c.gem_base_dir = "chef"
402
+
403
+ test = <<-EOF.gsub(/^\s+/, "")
404
+ require "net/http"
405
+
406
+ uris = %w{https://www.google.com https://chef.io/ https://ec2.amazonaws.com}
407
+ uris.each do |uri|
408
+ uri = URI(uri)
409
+ puts "Fetching \#{uri} for SSL check"
410
+ Net::HTTP.get uri
411
+ end
412
+ EOF
413
+
414
+ c.unit_test do
415
+ tmpdir do |cwd|
416
+ with_file(File.join(cwd, "openssl.rb")) do |f|
417
+ f.write test
418
+ end
419
+ sh!("#{Gem.ruby} openssl.rb", cwd: cwd)
420
+ end
421
+ end
422
+ end
423
+
424
+ add_component "inspec" do |c|
425
+ c.gem_base_dir = "inspec"
426
+
427
+ # Commenting out the unit and integration tests for now until we figure
428
+ # out the bundler error
429
+ #c.unit_test { sh("#{bin("bundle")} exec rake test:isolated") }
430
+ # This runs Test Kitchen (using kitchen-inspec) with some inspec tests
431
+ #c.integration_test { sh("#{bin("bundle")} exec rake test:vm") }
432
+
433
+ # It would be nice to use a chef generator to create these specs, but
434
+ # we dont have that yet. So we do it manually.
435
+ c.smoke_test do
436
+ tmpdir do |cwd|
437
+ File.open(File.join(cwd, "some_spec.rb"), "w+") do |f|
438
+ f.print <<-INSPEC_TEST
439
+ rule '01' do
440
+ impact 0.7
441
+ title 'Some Test'
442
+ desc 'Make sure inspec is installed and loading correct'
443
+ describe 1 do
444
+ it { should eq(1) }
445
+ end
446
+ end
447
+ INSPEC_TEST
448
+ end
449
+ # TODO when we appbundle inspec, no longer `chef exec`
450
+ sh("#{bin("chef")} exec #{bin("inspec")} exec .", cwd: cwd)
451
+ end
452
+ end
453
+ end
454
+
455
+ attr_reader :verification_threads
456
+ attr_reader :verification_results
457
+ attr_reader :verification_status
458
+
459
+ def initialize
460
+ super
461
+ @verification_threads = [ ]
462
+ @verification_results = [ ]
463
+ @verification_status = 0
464
+ end
465
+
466
+ def run(params = [ ])
467
+ @components_filter = parse_options(params)
468
+
469
+ validate_components!
470
+ invoke_tests
471
+ wait_for_tests
472
+ report_results
473
+
474
+ verification_status
475
+ end
476
+
477
+ def omnibus_root
478
+ config[:omnibus_dir] || super
479
+ end
480
+
481
+ def validate_components!
482
+ components.each do |component|
483
+ component.omnibus_root = omnibus_root
484
+ component.assert_present!
485
+ end
486
+ end
487
+
488
+ def components_to_test
489
+ if @components_filter.empty?
490
+ components
491
+ else
492
+ components.select do |component|
493
+ @components_filter.include?(component.name.to_s)
494
+ end
495
+ end
496
+ end
497
+
498
+ def invoke_tests
499
+ components_to_test.each do |component|
500
+ # Run the component specs in parallel
501
+ verification_threads << Thread.new do
502
+
503
+ results = []
504
+
505
+ results << component.run_smoke_test
506
+
507
+ if config[:unit]
508
+ results << component.run_unit_test
509
+ end
510
+
511
+ if config[:integration]
512
+ results << component.run_integration_test
513
+ end
514
+
515
+ if results.any? {|r| r.exitstatus != 0 }
516
+ component_status = 1
517
+ @verification_status = 1
518
+ else
519
+ component_status = 0
520
+ end
521
+
522
+ {
523
+ :component => component,
524
+ :results => results,
525
+ :component_status => component_status
526
+ }
527
+ end
528
+
529
+ msg("Running verification for component '#{component.name}'")
530
+ end
531
+ end
532
+
533
+ def wait_for_tests
534
+ while !verification_threads.empty?
535
+ verification_threads.each do |t|
536
+ if t.join(1)
537
+ verification_threads.delete t
538
+ verification_results << t.value
539
+ t.value[:results].each do |result|
540
+ if config[:verbose] || t.value[:component_status] != 0
541
+ msg("")
542
+ msg(result.stdout)
543
+ msg(result.stderr) if result.stderr
544
+ end
545
+ end
546
+ else
547
+ $stdout.write "."
548
+ end
549
+ end
550
+ end
551
+ end
552
+
553
+ def report_results
554
+ msg("")
555
+ msg("---------------------------------------------")
556
+ verification_results.each do |result|
557
+ message = result[:component_status] == 0 ? "succeeded" : "failed"
558
+ msg("Verification of component '#{result[:component].name}' #{message}.")
559
+ end
560
+ end
561
+
562
+ end
563
+ end
564
+ end