chef-dk 0.5.1 → 0.6.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.
@@ -68,19 +68,7 @@ module ChefDK
68
68
  class InvalidPolicyfileFilename < StandardError
69
69
  end
70
70
 
71
- class ChefRunnerError < StandardError
72
-
73
- attr_reader :cause
74
-
75
- def initialize(message, cause)
76
- super(message)
77
- @cause = cause
78
- end
79
-
71
+ class BUG < RuntimeError
80
72
  end
81
73
 
82
- class CookbookNotFound < ChefRunnerError; end
83
-
84
- class ChefConvergeError < ChefRunnerError; end
85
-
86
74
  end
@@ -24,28 +24,7 @@ require 'chef-dk/service_exception_inspectors'
24
24
 
25
25
  module ChefDK
26
26
 
27
- # Base class for errors raised by ChefDK::PolicyfileServices objects. Don't
28
- # raise this directly, create a descriptively-named subclass. You can rescue
29
- # this to catch all errors from PolicyfileServices objects though.
30
- class PolicyfileServiceError < StandardError
31
- end
32
-
33
- class PolicyfileNotFound < PolicyfileServiceError
34
- end
35
-
36
- class LockfileNotFound < PolicyfileServiceError
37
- end
38
-
39
- class MalformedLockfile < PolicyfileServiceError
40
- end
41
-
42
- class GitError < PolicyfileServiceError
43
- end
44
-
45
- class ExportDirNotEmpty < PolicyfileServiceError
46
- end
47
-
48
- class PolicyfileNestedException < PolicyfileServiceError
27
+ module NestedExceptionWithInspector
49
28
 
50
29
  attr_reader :cause
51
30
  attr_reader :inspector
@@ -76,6 +55,33 @@ module ChefDK
76
55
 
77
56
  end
78
57
 
58
+ # Base class for errors raised by ChefDK::PolicyfileServices objects. Don't
59
+ # raise this directly, create a descriptively-named subclass. You can rescue
60
+ # this to catch all errors from PolicyfileServices objects though.
61
+ class PolicyfileServiceError < StandardError
62
+ end
63
+
64
+ class PolicyfileNotFound < PolicyfileServiceError
65
+ end
66
+
67
+ class LockfileNotFound < PolicyfileServiceError
68
+ end
69
+
70
+ class MalformedLockfile < PolicyfileServiceError
71
+ end
72
+
73
+ class GitError < PolicyfileServiceError
74
+ end
75
+
76
+ class ExportDirNotEmpty < PolicyfileServiceError
77
+ end
78
+
79
+ class PolicyfileNestedException < PolicyfileServiceError
80
+
81
+ include NestedExceptionWithInspector
82
+
83
+ end
84
+
79
85
  class PolicyfileDownloadError < PolicyfileNestedException
80
86
  end
81
87
 
@@ -91,5 +97,15 @@ module ChefDK
91
97
  class PolicyfileExportRepoError < PolicyfileNestedException
92
98
  end
93
99
 
100
+ class ChefRunnerError < StandardError
101
+
102
+ include NestedExceptionWithInspector
103
+
104
+ end
105
+
106
+ class CookbookNotFound < ChefRunnerError; end
107
+
108
+ class ChefConvergeError < ChefRunnerError; end
109
+
94
110
  end
95
111
 
@@ -1,3 +1,3 @@
1
- source "https://supermarket.chef.io"
1
+ source 'https://supermarket.chef.io'
2
2
 
3
3
  metadata
@@ -1,8 +1,7 @@
1
- name '<%= cookbook_name %>'
2
- maintainer '<%= copyright_holder %>'
1
+ name '<%= cookbook_name %>'
2
+ maintainer '<%= copyright_holder %>'
3
3
  maintainer_email '<%= email %>'
4
- license '<%= license %>'
5
- description 'Installs/Configures <%= cookbook_name %>'
4
+ license '<%= license %>'
5
+ description 'Installs/Configures <%= cookbook_name %>'
6
6
  long_description 'Installs/Configures <%= cookbook_name %>'
7
- version '0.1.0'
8
-
7
+ version '0.1.0'
@@ -7,9 +7,7 @@
7
7
  require 'spec_helper'
8
8
 
9
9
  describe '<%= cookbook_name %>::<%= recipe_name %>' do
10
-
11
10
  context 'When all attributes are default, on an unspecified platform' do
12
-
13
11
  let(:chef_run) do
14
12
  runner = ChefSpec::ServerRunner.new
15
13
  runner.converge(described_recipe)
@@ -18,6 +16,5 @@ describe '<%= cookbook_name %>::<%= recipe_name %>' do
18
16
  it 'converges successfully' do
19
17
  chef_run # This should not raise an error
20
18
  end
21
-
22
19
  end
23
20
  end
@@ -1,12 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe '<%= cookbook_name %>::default' do
4
-
5
4
  # Serverspec examples can be found at
6
5
  # http://serverspec.org/resource_types.html
7
-
8
6
  it 'does something' do
9
7
  skip 'Replace this with meaningful tests'
10
8
  end
11
-
12
- end
9
+ end
@@ -16,5 +16,5 @@
16
16
  #
17
17
 
18
18
  module ChefDK
19
- VERSION = "0.5.1"
19
+ VERSION = "0.6.0"
20
20
  end
@@ -54,7 +54,14 @@ describe ChefDK::ChefRunner do
54
54
  end
55
55
 
56
56
  it "configures a formatter for the chef run" do
57
- expect(chef_runner.formatter).to be_a(Chef::Formatters::Doc)
57
+ expect(chef_runner.formatter).to be_a(Chef::EventDispatch::Dispatcher)
58
+
59
+ # TODO: Once https://github.com/chef/chef/pull/3340 is merged/released,
60
+ # just use `formatter.subscribers`
61
+ subscribers = chef_runner.formatter.instance_variable_get(:@subscribers)
62
+
63
+ expect(subscribers.size).to eq(1)
64
+ expect(subscribers.first).to be_a(Chef::Formatters::Doc)
58
65
  end
59
66
 
60
67
  it "detects the platform with ohai" do
@@ -0,0 +1,536 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2015 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 'spec_helper'
19
+ require 'shared/command_with_ui_object'
20
+ require 'chef-dk/command/provision'
21
+
22
+ describe ChefDK::Command::Provision do
23
+
24
+ it_behaves_like "a command with a UI object"
25
+
26
+ let(:command) do
27
+ described_class.new
28
+ end
29
+
30
+ let(:push_service) { instance_double(ChefDK::PolicyfileServices::Push) }
31
+
32
+ let(:chef_config_loader) { instance_double("Chef::WorkstationConfigLoader") }
33
+
34
+ let(:chef_config) { double("Chef::Config") }
35
+
36
+ let(:config_arg) { nil }
37
+
38
+ before do
39
+ ChefDK::ProvisioningData.reset
40
+
41
+ stub_const("Chef::Config", chef_config)
42
+ allow(Chef::WorkstationConfigLoader).to receive(:new).with(config_arg).and_return(chef_config_loader)
43
+ end
44
+
45
+ describe "evaluating CLI options and arguments" do
46
+
47
+ let(:ui) { TestHelpers::TestUI.new }
48
+
49
+ before do
50
+ command.ui = ui
51
+ end
52
+
53
+ describe "when input is invalid" do
54
+
55
+ context "when not enough arguments are given" do
56
+
57
+ let(:params) { [] }
58
+
59
+ it "prints usage and exits non-zero" do
60
+ expect(command.run(params)).to eq(1)
61
+ expect(ui.output).to include("You must specify a POLICY_GROUP or disable policyfiles with --no-policy")
62
+ end
63
+
64
+ end
65
+
66
+ context "when --no-policy is combined with policy arguments" do
67
+
68
+ let(:params) { %w[ --no-policy some-policy-group ] }
69
+
70
+ it "prints usage and exits non-zero" do
71
+ expect(command.run(params)).to eq(1)
72
+ expect(ui.output).to include("The --no-policy flag cannot be combined with policyfile arguments")
73
+ end
74
+
75
+ end
76
+
77
+ context "when a POLICY_GROUP is given but neither of --sync or --policy-name are given" do
78
+
79
+ let(:params) { %w[ some-policy-group ] }
80
+
81
+ it "prints usage and exits non-zero" do
82
+ expect(command.run(params)).to eq(1)
83
+ expect(ui.output).to include("You must pass either --sync or --policy-name to provision machines in policyfile mode")
84
+ end
85
+
86
+ end
87
+
88
+ context "when both --sync and --policy-name are given" do
89
+
90
+ let(:params) { %w[ some-policy-group --policy-name foo --sync] }
91
+
92
+ it "prints usage and exits non-zero" do
93
+ expect(command.run(params)).to eq(1)
94
+ expect(ui.output).to include("The --policy-name and --sync arguments cannot be combined")
95
+ end
96
+
97
+ end
98
+
99
+ context "when too many arguments are given" do
100
+
101
+ let(:params) { %w[ policygroup extraneous-argument --sync ] }
102
+
103
+ it "prints usage and exits non-zero" do
104
+ expect(command.run(params)).to eq(1)
105
+ expect(ui.output).to include("Too many arguments")
106
+ end
107
+
108
+ end
109
+ end
110
+
111
+ describe "when input is valid" do
112
+
113
+ let(:context) { ChefDK::ProvisioningData.context }
114
+
115
+ shared_examples "common_optional_options" do
116
+
117
+ context "with default option values" do
118
+
119
+ it "node name is not specified" do
120
+ expect(command.node_name).to eq(nil)
121
+ expect(context.node_name).to eq(nil)
122
+ end
123
+
124
+ it "sets the cookbook path to CWD" do
125
+ # this is cookbook_path in the chef sense, a directory with cookbooks in it.
126
+ expect(command.provisioning_cookbook_path).to eq(Dir.pwd)
127
+ end
128
+
129
+ it "sets the cookbook name to 'provision'" do
130
+ expect(command.provisioning_cookbook_name).to eq('provision')
131
+ end
132
+
133
+ it "sets the recipe to 'default'" do
134
+ expect(command.recipe).to eq("default")
135
+ expect(command.chef_runner.run_list).to eq(["recipe[provision::default]"])
136
+ end
137
+
138
+ it "sets the default action to converge" do
139
+ expect(command.default_action).to eq(:converge)
140
+ expect(context.action).to eq(:converge)
141
+ end
142
+
143
+ end
144
+
145
+ context "with -n NODE_NAME" do
146
+
147
+ let(:extra_params) { %w[ -n example-node ] }
148
+
149
+ it "sets the default requested node name" do
150
+ expect(command.node_name).to eq("example-node")
151
+ expect(context.node_name).to eq("example-node")
152
+ end
153
+
154
+ end
155
+
156
+ context "with --cookbook COOKBOOK_PATH" do
157
+
158
+ let(:extra_params) { %w[ --cookbook ~/mystuff/my-provision-cookbook ] }
159
+
160
+ let(:expected_cookbook_path) { File.expand_path("~/mystuff") }
161
+ let(:expected_cookbook_name) { "my-provision-cookbook" }
162
+
163
+ it "sets the cookbook path" do
164
+ # this is cookbook_path in the chef sense, a directory with cookbooks in it.
165
+ expect(command.provisioning_cookbook_path).to eq(expected_cookbook_path)
166
+ end
167
+
168
+ it "sets the cookbook name" do
169
+ expect(command.provisioning_cookbook_name).to eq(expected_cookbook_name)
170
+ end
171
+
172
+ end
173
+
174
+ context "with -c CONFIG_FILE" do
175
+
176
+ let(:config_arg) { "~/somewhere_else/knife.rb" }
177
+
178
+ let(:extra_params) { [ "-c", config_arg ] }
179
+
180
+ it "loads config from the specified location" do
181
+ # The configurable module uses config[:config_file]
182
+ expect(command.config[:config_file]).to eq("~/somewhere_else/knife.rb")
183
+ end
184
+
185
+ end
186
+
187
+ context "with -r MACHINE_RECIPE" do
188
+
189
+ let(:extra_params) { %w[ -r ec2cluster ] }
190
+
191
+ it "sets the recipe to run as specified" do
192
+ expect(command.recipe).to eq("ec2cluster")
193
+ expect(command.chef_runner.run_list).to eq(["recipe[provision::ec2cluster]"])
194
+ end
195
+
196
+ end
197
+
198
+ context "with -d" do
199
+
200
+ let(:extra_params) { %w[ -d ] }
201
+
202
+ it "sets the default action to destroy" do
203
+ expect(command.default_action).to eq(:destroy)
204
+ expect(context.action).to eq(:destroy)
205
+ end
206
+
207
+ end
208
+
209
+ end # shared examples
210
+
211
+ context "when --no-policy is given" do
212
+
213
+ before do
214
+ allow(chef_config_loader).to receive(:load)
215
+ allow(command).to receive(:push).and_return(push_service)
216
+
217
+ allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer)
218
+
219
+ command.apply_params!(params)
220
+ command.setup_context
221
+ end
222
+
223
+ let(:extra_params) { [] }
224
+ let(:params) { %w[ --no-policy ] + extra_params }
225
+
226
+ it "disables policyfile integration" do
227
+ expect(command.enable_policyfile?).to be(false)
228
+ end
229
+
230
+ it "generates chef config with no policyfile options" do
231
+ expected_config = <<-CONFIG
232
+ # SSL Settings:
233
+ ssl_verify_mode :verify_peer
234
+
235
+ CONFIG
236
+ expect(context.chef_config).to eq(expected_config)
237
+ end
238
+
239
+ include_examples "common_optional_options"
240
+
241
+ end # when --no-policy is given
242
+
243
+ context "when --sync POLICYFILE argument is given" do
244
+
245
+ let(:policy_data) { { "name" => "myapp" } }
246
+
247
+ before do
248
+ allow(chef_config_loader).to receive(:load)
249
+
250
+ allow(ChefDK::PolicyfileServices::Push).to receive(:new).
251
+ with(policyfile: given_policyfile_path, ui: ui, policy_group: given_policy_group, config: chef_config, root_dir: Dir.pwd).
252
+ and_return(push_service)
253
+
254
+ allow(push_service).to receive(:policy_data).and_return(policy_data)
255
+
256
+ command.apply_params!(params)
257
+ command.setup_context
258
+ end
259
+
260
+ context "with explicit policyfile relative path" do
261
+
262
+ let(:given_policyfile_path) { "policies/OtherPolicy.rb" }
263
+
264
+ let(:given_policy_group) { "some-policy-group" }
265
+
266
+ let(:params) { [ given_policy_group, '--sync', given_policyfile_path ] }
267
+
268
+ it "sets policy group" do
269
+ expect(command.policy_group).to eq(given_policy_group)
270
+ expect(context.policy_group).to eq(given_policy_group)
271
+ end
272
+
273
+ it "sets policy name" do
274
+ expect(command.policy_name).to eq("myapp")
275
+ expect(context.policy_name).to eq("myapp")
276
+ end
277
+
278
+ end
279
+
280
+ context "with implicit policyfile relative path" do
281
+
282
+ let(:given_policyfile_path) { nil }
283
+
284
+ let(:given_policy_group) { "some-policy-group" }
285
+
286
+ let(:extra_params) { [] }
287
+
288
+ let(:params) { [ given_policy_group, '--sync' ] + extra_params }
289
+
290
+ before do
291
+ allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer)
292
+ end
293
+
294
+ it "sets policy group" do
295
+ expect(command.policy_group).to eq(given_policy_group)
296
+ expect(context.policy_group).to eq(given_policy_group)
297
+ end
298
+
299
+ it "sets policy name" do
300
+ expect(command.policy_name).to eq("myapp")
301
+ expect(context.policy_name).to eq("myapp")
302
+ end
303
+
304
+ it "generates chef config with policyfile options" do
305
+ expected_config = <<-CONFIG
306
+ # SSL Settings:
307
+ ssl_verify_mode :verify_peer
308
+
309
+ # Policyfile Settings:
310
+ use_policyfile true
311
+ policy_document_native_api true
312
+
313
+ policy_group "some-policy-group"
314
+ policy_name "myapp"
315
+
316
+ CONFIG
317
+ expect(context.chef_config).to eq(expected_config)
318
+ end
319
+
320
+
321
+ include_examples "common_optional_options"
322
+
323
+ end
324
+
325
+ end # when --sync POLICYFILE argument is given
326
+
327
+ context "when a --policy-name is given" do
328
+
329
+ let(:given_policy_group) { "some-policy-group" }
330
+
331
+ let(:extra_params) { [] }
332
+
333
+ let(:params) { [ given_policy_group, '--policy-name', "myapp" ] + extra_params }
334
+
335
+
336
+ before do
337
+ command.apply_params!(params)
338
+ command.setup_context
339
+
340
+ allow(chef_config).to receive(:ssl_verify_mode).and_return(:verify_peer)
341
+ end
342
+
343
+ it "sets policy group" do
344
+ expect(command.policy_group).to eq(given_policy_group)
345
+ expect(context.policy_group).to eq(given_policy_group)
346
+ end
347
+
348
+ it "sets policy name" do
349
+ expect(command.policy_name).to eq("myapp")
350
+ expect(context.policy_name).to eq("myapp")
351
+ end
352
+
353
+ it "generates chef config with policyfile options" do
354
+ expected_config = <<-CONFIG
355
+ # SSL Settings:
356
+ ssl_verify_mode :verify_peer
357
+
358
+ # Policyfile Settings:
359
+ use_policyfile true
360
+ policy_document_native_api true
361
+
362
+ policy_group "some-policy-group"
363
+ policy_name "myapp"
364
+
365
+ CONFIG
366
+ expect(context.chef_config).to eq(expected_config)
367
+ end
368
+
369
+ include_examples "common_optional_options"
370
+
371
+ end
372
+ end
373
+
374
+ end
375
+
376
+ describe "running the provision cookbook" do
377
+
378
+ let(:ui) { TestHelpers::TestUI.new }
379
+
380
+ before do
381
+ allow(chef_config_loader).to receive(:load)
382
+ allow(command).to receive(:push).and_return(push_service)
383
+ command.ui = ui
384
+ end
385
+
386
+ let(:provision_cookbook_path) { File.expand_path("provision", Dir.pwd) }
387
+ let(:provision_recipe_path) { File.join(provision_cookbook_path, "recipes", "default.rb") }
388
+
389
+ let(:chef_runner) { instance_double("ChefDK::ChefRunner") }
390
+
391
+ let(:params) { %w[ policygroup --sync ] }
392
+
393
+ context "when the provision cookbook doesn't exist" do
394
+
395
+ before do
396
+ allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(false)
397
+ end
398
+
399
+ it "prints an error and exits non-zero" do
400
+ expect(command.run(params)).to eq(1)
401
+ expect(ui.output).to include("Provisioning cookbook not found at path #{provision_cookbook_path}")
402
+ end
403
+
404
+ end
405
+
406
+ context "when the provision cookbook doesn't have the requested recipe" do
407
+
408
+ before do
409
+ allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true)
410
+ allow(File).to receive(:exist?).with(provision_recipe_path).and_return(false)
411
+ end
412
+
413
+ it "prints an error and exits non-zero" do
414
+ expect(command.run(params)).to eq(1)
415
+ expect(ui.output).to include("Provisioning recipe not found at path #{provision_recipe_path}")
416
+ end
417
+
418
+ end
419
+
420
+ context "when the policyfile upload fails" do
421
+
422
+ let(:backtrace) { caller[0...3] }
423
+
424
+ let(:cause) do
425
+ e = StandardError.new("some operation failed")
426
+ e.set_backtrace(backtrace)
427
+ e
428
+ end
429
+
430
+ let(:exception) do
431
+ ChefDK::PolicyfilePushError.new("push failed", cause)
432
+ end
433
+
434
+ before do
435
+ allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true)
436
+ allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true)
437
+
438
+ expect(push_service).to receive(:run).and_raise(exception)
439
+ end
440
+
441
+ it "prints an error and exits non-zero" do
442
+ expected_output=<<-E
443
+ Error: push failed
444
+ Reason: (StandardError) some operation failed
445
+
446
+ E
447
+ expect(command.run(params)).to eq(1)
448
+ expect(ui.output).to include(expected_output)
449
+ end
450
+
451
+ end
452
+
453
+ context "when the chef run fails" do
454
+
455
+ let(:base_exception) { StandardError.new("Something went wrong") }
456
+ let(:exception) { ChefDK::ChefConvergeError.new("Chef failed to converge: #{base_exception}", base_exception) }
457
+
458
+ let(:policy_data) { { "name" => "myapp" } }
459
+
460
+ before do
461
+ allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true)
462
+ allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true)
463
+
464
+ allow(push_service).to receive(:policy_data).and_return(policy_data)
465
+
466
+ expect(push_service).to receive(:run)
467
+
468
+ allow(command).to receive(:chef_runner).and_return(chef_runner)
469
+ allow(chef_runner).to receive(:cookbook_path).and_return(Dir.pwd)
470
+ expect(chef_runner).to receive(:converge).and_raise(exception)
471
+ end
472
+
473
+ it "prints an error and exits non-zero" do
474
+ expect(command.run(params)).to eq(1)
475
+ expect(ui.output).to include("Error: Chef failed to converge")
476
+ expect(ui.output).to include("Reason: (StandardError) Something went wrong")
477
+ end
478
+
479
+ end
480
+
481
+ context "when the chef run is successful" do
482
+
483
+ before do
484
+ allow(File).to receive(:exist?).with(provision_cookbook_path).and_return(true)
485
+ allow(File).to receive(:exist?).with(provision_recipe_path).and_return(true)
486
+ allow(command).to receive(:chef_runner).and_return(chef_runner)
487
+ allow(chef_runner).to receive(:cookbook_path).and_return(Dir.pwd)
488
+
489
+ expect(chef_runner).to receive(:converge)
490
+ end
491
+
492
+ context "when using --no-policy" do
493
+
494
+ let(:params) { %w[ --no-policy ] }
495
+
496
+ it "exits 0" do
497
+ return_value = command.run(params)
498
+ expect(ui.output).to eq("")
499
+ expect(return_value).to eq(0)
500
+ end
501
+
502
+ end
503
+
504
+ context "with --policy-name" do
505
+
506
+ let(:params) { %w[ policygroup --policy-name otherapp ] }
507
+
508
+ it "exits 0" do
509
+ return_value = command.run(params)
510
+ expect(ui.output).to eq("")
511
+ expect(return_value).to eq(0)
512
+ end
513
+ end
514
+
515
+ context "with --sync" do
516
+
517
+ let(:policy_data) { { "name" => "myapp" } }
518
+
519
+ before do
520
+ allow(push_service).to receive(:policy_data).and_return(policy_data)
521
+ expect(push_service).to receive(:run)
522
+ end
523
+
524
+ it "exits 0" do
525
+ return_value = command.run(params)
526
+ expect(ui.output).to eq("")
527
+ expect(return_value).to eq(0)
528
+ end
529
+
530
+ end
531
+
532
+ end
533
+
534
+ end
535
+ end
536
+