chef-dk 0.5.1 → 0.6.0

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