knife-container 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +4 -0
  5. data/CONTRIBUTING.md +152 -0
  6. data/Gemfile +10 -0
  7. data/LICENSE +201 -0
  8. data/README.md +59 -0
  9. data/Rakefile +16 -0
  10. data/knife-container.gemspec +31 -0
  11. data/lib/chef/knife/container_docker_build.rb +243 -0
  12. data/lib/chef/knife/container_docker_init.rb +262 -0
  13. data/lib/knife-container/chef_runner.rb +83 -0
  14. data/lib/knife-container/command.rb +45 -0
  15. data/lib/knife-container/generator.rb +88 -0
  16. data/lib/knife-container/helpers.rb +16 -0
  17. data/lib/knife-container/skeletons/knife_container/files/default/plugins/docker_container.rb +37 -0
  18. data/lib/knife-container/skeletons/knife_container/metadata.rb +7 -0
  19. data/lib/knife-container/skeletons/knife_container/recipes/docker_init.rb +181 -0
  20. data/lib/knife-container/skeletons/knife_container/templates/default/berksfile.erb +5 -0
  21. data/lib/knife-container/skeletons/knife_container/templates/default/config.rb.erb +16 -0
  22. data/lib/knife-container/skeletons/knife_container/templates/default/dockerfile.erb +9 -0
  23. data/lib/knife-container/skeletons/knife_container/templates/default/dockerignore.erb +0 -0
  24. data/lib/knife-container/skeletons/knife_container/templates/default/node_name.erb +1 -0
  25. data/lib/knife-container/version.rb +5 -0
  26. data/spec/functional/docker_container_ohai_spec.rb +20 -0
  27. data/spec/functional/fixtures/ohai/Dockerfile +3 -0
  28. data/spec/spec_helper.rb +35 -0
  29. data/spec/test_helpers.rb +59 -0
  30. data/spec/unit/container_docker_build_spec.rb +325 -0
  31. data/spec/unit/container_docker_init_spec.rb +464 -0
  32. data/spec/unit/fixtures/.chef/encrypted_data_bag_secret +0 -0
  33. data/spec/unit/fixtures/.chef/trusted_certs/chef_example_com.crt +0 -0
  34. data/spec/unit/fixtures/.chef/validator.pem +1 -0
  35. data/spec/unit/fixtures/Berksfile +3 -0
  36. data/spec/unit/fixtures/cookbooks/dummy/metadata.rb +0 -0
  37. data/spec/unit/fixtures/cookbooks/nginx/metadata.rb +0 -0
  38. data/spec/unit/fixtures/environments/dev.json +0 -0
  39. data/spec/unit/fixtures/nodes/demo.json +0 -0
  40. data/spec/unit/fixtures/roles/base.json +0 -0
  41. data/spec/unit/fixtures/site-cookbooks/apt/metadata.rb +0 -0
  42. metadata +232 -0
@@ -0,0 +1,464 @@
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 'spec_helper'
19
+ require 'chef/knife/container_docker_init'
20
+
21
+ describe Chef::Knife::ContainerDockerInit do
22
+
23
+ let(:stdout_io) { StringIO.new }
24
+ let(:stderr_io) { StringIO.new }
25
+
26
+ def stdout
27
+ stdout_io.string
28
+ end
29
+
30
+ let(:default_cookbook_path) do
31
+ File.expand_path("cookbooks", fixtures_path)
32
+ end
33
+
34
+ let(:reset_chef_config) do
35
+ Chef::Config.reset
36
+ Chef::Config[:chef_repo_path] = tempdir
37
+ Chef::Config[:knife][:dockerfiles_path] = File.join(tempdir, 'dockerfiles')
38
+ Chef::Config[:cookbook_path] = File.join(fixtures_path, 'cookbooks')
39
+ Chef::Config[:chef_server_url] = "http://localhost:4000"
40
+ Chef::Config[:validation_key] = File.join(fixtures_path, '.chef', 'validator.pem')
41
+ Chef::Config[:trusted_certs_dir] = File.join(fixtures_path, '.chef', 'trusted_certs')
42
+ Chef::Config[:validation_client_name] = 'masterchef'
43
+ Chef::Config[:encrypted_data_bag_secret] = File.join(fixtures_path, '.chef', 'encrypted_data_bag_secret')
44
+ end
45
+
46
+ def generator_context
47
+ KnifeContainer::Generator.context
48
+ end
49
+
50
+ before(:each) do
51
+ @knife = Chef::Knife::ContainerDockerInit.new(argv)
52
+ @knife.stub(:output).and_return(true)
53
+ @knife.stub(:download_and_tag_base_image)
54
+ @knife.ui.stub(:stdout).and_return(stdout_io)
55
+ @knife.chef_runner.stub(:stdout).and_return(stdout_io)
56
+ KnifeContainer::Generator.reset
57
+ end
58
+
59
+ describe '#run' do
60
+ let(:argv) { %w[ docker/demo ] }
61
+ it "should run things" do
62
+ @knife.should_receive(:read_and_validate_params)
63
+ @knife.should_receive(:set_config_defaults)
64
+ @knife.should_receive(:setup_context)
65
+ @knife.chef_runner.should_receive(:converge)
66
+ @knife.should_receive(:eval_current_system)
67
+ @knife.should_receive(:download_and_tag_base_image)
68
+ @knife.run
69
+ end
70
+ end
71
+
72
+ #
73
+ # Validating parameters
74
+ #
75
+ describe 'when reading and validating parameters' do
76
+ let(:argv) { %W[] }
77
+
78
+ it 'should should print usage and exit when given no arguments' do
79
+ @knife.should_receive(:show_usage)
80
+ @knife.ui.should_receive(:fatal)
81
+ lambda { @knife.run }.should raise_error(SystemExit)
82
+ end
83
+
84
+ context 'and using berkshelf functionality' do
85
+ let(:argv) {%W[ docker/demo -b ]}
86
+
87
+ it 'loads berkshelf if available' do
88
+ @knife.read_and_validate_params
89
+ defined?(Berkshelf).should == "constant"
90
+ end
91
+ end
92
+ end
93
+
94
+ #
95
+ # Setting up the generator context
96
+ #
97
+ describe 'when setting up the generator context' do
98
+ before(:each) { reset_chef_config }
99
+
100
+ context 'when no cli overrides have been specified' do
101
+ let(:argv) { %w[ docker/demo ] }
102
+
103
+ it 'should set values to Chef::Config default values' do
104
+ @knife.run
105
+ expect(generator_context.chef_server_url).to eq("http://localhost:4000")
106
+ expect(generator_context.cookbook_path).to eq(File.join(fixtures_path, 'cookbooks'))
107
+ expect(generator_context.chef_client_mode).to eq("client")
108
+ expect(generator_context.node_path).to eq(File.join(tempdir, 'nodes'))
109
+ expect(generator_context.role_path).to eq(File.join(tempdir, 'roles'))
110
+ expect(generator_context.environment_path).to eq(File.join(tempdir, 'environments'))
111
+ expect(generator_context.dockerfiles_path).to eq(File.join(tempdir, 'dockerfiles'))
112
+ expect(generator_context.run_list).to eq([])
113
+ end
114
+
115
+ context 'when cookbook_path is an array' do
116
+ before do
117
+ Chef::Config[:cookbook_path] = ['/path/to/cookbooks', '/path/to/site-cookbooks']
118
+ end
119
+
120
+ it 'honors the array' do
121
+ @knife.run
122
+ expect(generator_context.cookbook_path).to eq(['/path/to/cookbooks', '/path/to/site-cookbooks'])
123
+ end
124
+ end
125
+ end
126
+
127
+ describe "when base image is specified" do
128
+ context "with a tag" do
129
+ let(:argv) { %w[ docker/demo -f docker/demo:11.12.8 ] }
130
+
131
+ it "should respect that tag" do
132
+ @knife.run
133
+ expect(generator_context.base_image).to eql("docker/demo:11.12.8")
134
+ end
135
+ end
136
+
137
+ context "without a tag" do
138
+ let(:argv) { %w[ docker/demo -f docker/demo ] }
139
+
140
+ it "should append the 'latest' tag on the name" do
141
+ @knife.run
142
+ expect(generator_context.base_image).to eql("docker/demo:latest")
143
+ end
144
+ end
145
+ end
146
+
147
+ describe 'when passing a run list' do
148
+ let(:argv) { %W[
149
+ docker/demo
150
+ -r recipe[apt],recipe[nginx]
151
+ ]}
152
+
153
+ it 'should add the run_list value to the first_boot.json if passed' do
154
+ @knife.run
155
+ first_boot = { run_list: ["recipe[apt]", "recipe[nginx]"]}
156
+ expect(generator_context.first_boot).to include(JSON.pretty_generate(first_boot))
157
+ end
158
+ end
159
+
160
+ describe 'when local-mode is specified' do
161
+ let(:argv) { %w[ docker/demo -z ] }
162
+
163
+ it "sets generator_context.chef_client_mode to zero" do
164
+ @knife.run
165
+ expect(generator_context.chef_client_mode).to eq("zero")
166
+ end
167
+ end
168
+ end
169
+
170
+
171
+ #
172
+ # The chef runner converge
173
+ #
174
+ describe 'the converge phase' do
175
+ describe 'when the -b flag is specified' do
176
+ before(:each) { reset_chef_config }
177
+ let(:argv) { %w[ docker/demo -r recipe[nginx] -z -b ] }
178
+
179
+ subject(:berksfile) do
180
+ File.read("#{tempdir}/dockerfiles/docker/demo/Berksfile")
181
+ end
182
+
183
+ it 'generates a Berksfile based on the run_list' do
184
+ @knife.run
185
+ berksfile.should include 'cookbook "nginx"'
186
+ end
187
+
188
+ context 'and the run_list includes fully-qualified recipe names' do
189
+ let(:argv) { %W[
190
+ docker/demo
191
+ -r role[demo],recipe[demo::recipe],recipe[nginx]
192
+ -z -b
193
+ ]}
194
+
195
+ it 'correctly configures Berksfile with just the cookbook name' do
196
+ @knife.run
197
+ berksfile.should include 'cookbook "demo"'
198
+ berksfile.should include 'cookbook "nginx"'
199
+ end
200
+ end
201
+ end
202
+
203
+ describe 'when creating the client config file' do
204
+ context 'for server-mode' do
205
+ before { reset_chef_config }
206
+ let(:argv) {%W[ docker/demo ]}
207
+
208
+ subject(:config_file) do
209
+ File.read("#{tempdir}/dockerfiles/docker/demo/chef/client.rb")
210
+ end
211
+
212
+ it 'should have some global settings' do
213
+ @knife.run
214
+ expect(config_file).to include "require 'chef-init'"
215
+ expect(config_file).to include "node_name", "ChefInit.node_name"
216
+ expect(config_file).to include "ssl_verify_mode", ":verify_peer"
217
+ expect(config_file).to include "chef_server_url", "http://localhost:4000"
218
+ expect(config_file).to include "validation_key", "/etc/chef/secure/validation.pem"
219
+ expect(config_file).to include "client_key", "/etc/chef/secure/client.pem"
220
+ expect(config_file).to include "trusted_certs_dir", "/etc/chef/secure/trusted_certs"
221
+ expect(config_file).to include "validation_client_name", "masterchef"
222
+ expect(config_file).to include "encrypted_data_bag_secret", "/etc/chef/secure/encrypted_data_bag_secret"
223
+ end
224
+ end
225
+
226
+ context 'for local-mode' do
227
+ before { reset_chef_config }
228
+ let(:argv) {%W[ docker/demo -z ]}
229
+
230
+ subject(:config_file) do
231
+ File.read("#{tempdir}/dockerfiles/docker/demo/chef/zero.rb")
232
+ end
233
+
234
+ it 'should include local-mode specific settings' do
235
+ @knife.run
236
+ expect(config_file).to include "require 'chef-init'"
237
+ expect(config_file).to include "node_name", "ChefInit.node_name"
238
+ expect(config_file).to include "ssl_verify_mode", ":verify_peer"
239
+ expect(config_file).to include "cookbook_path", "[\"/etc/chef/cookbooks\"]"
240
+ expect(config_file).to include "encrypted_data_bag_secret", "/etc/chef/secure/encrypted_data_bag_secret"
241
+ end
242
+ end
243
+
244
+ context 'when encrypted_data_bag_secret is not specified' do
245
+ before do
246
+ reset_chef_config
247
+ Chef::Config[:encrypted_data_bag_secret] = nil
248
+ end
249
+
250
+ let(:argv) {%W[ docker/demo ]}
251
+
252
+ subject(:config_file) do
253
+ File.read("#{tempdir}/dockerfiles/docker/demo/chef/client.rb")
254
+ end
255
+
256
+ it 'should not be present in config' do
257
+ @knife.run
258
+ expect(config_file).to_not include "encrypted_data_bag_secret"
259
+ end
260
+
261
+ end
262
+
263
+ end
264
+
265
+ describe 'when creating the Dockerfile' do
266
+ subject(:dockerfile) do
267
+ File.read("#{Chef::Config[:chef_repo_path]}/dockerfiles/docker/demo/Dockerfile")
268
+ end
269
+
270
+ context 'by default' do
271
+ let(:argv) { %w[ docker/demo ] }
272
+
273
+ before do
274
+ reset_chef_config
275
+ @knife.run
276
+ end
277
+
278
+ it 'should set the base_image name in a comment in the Dockerfile' do
279
+ expect(dockerfile).to include '# BASE chef/ubuntu-12.04:latest'
280
+ end
281
+
282
+ it 'should remove the secure directory' do
283
+ expect(dockerfile).to include 'RUN rm -rf /etc/chef/secure/*'
284
+ end
285
+ end
286
+
287
+ context 'when include_credentials is specified' do
288
+ let(:argv) { %w[ docker/demo --include-credentials ] }
289
+
290
+ before do
291
+ reset_chef_config
292
+ @knife.run
293
+ end
294
+
295
+ it 'should not remove the secure directory' do
296
+ expect(dockerfile).not_to include 'RUN rm -rf /etc/chef/secure/*'
297
+ end
298
+ end
299
+ end
300
+
301
+ describe "when no valid cookbook path is specified" do
302
+ before(:each) do
303
+ reset_chef_config
304
+ Chef::Config[:cookbook_path] = '/tmp/nil/cookbooks'
305
+ end
306
+
307
+ let(:argv) { %W[
308
+ docker/demo
309
+ -r recipe[nginx]
310
+ -z
311
+ -b
312
+ ]}
313
+
314
+ it "should log an error and not copy cookbooks" do
315
+ @knife.run
316
+ expect(stdout).to include('log[Could not find a \'/tmp/nil/cookbooks\' directory in your chef-repo.] action write')
317
+ end
318
+ end
319
+
320
+ describe "when copying cookbooks to temporary chef-repo" do
321
+ context "and the chef config specifies multiple directories" do
322
+ before do
323
+ reset_chef_config
324
+ Chef::Config[:cookbook_path] = ["#{fixtures_path}/cookbooks", "#{fixtures_path}/site-cookbooks"]
325
+ @knife.run
326
+ end
327
+
328
+ let(:argv) { %W[
329
+ docker/demo
330
+ -r recipe[nginx],recipe[apt]
331
+ -z
332
+ ]}
333
+
334
+ it "should copy cookbooks from both directories" do
335
+ expect(stdout).to include("execute[cp -rf #{fixtures_path}/cookbooks/nginx #{tempdir}/dockerfiles/docker/demo/chef/cookbooks/] action run")
336
+ expect(stdout).to include("execute[cp -rf #{fixtures_path}/site-cookbooks/apt #{tempdir}/dockerfiles/docker/demo/chef/cookbooks/] action run")
337
+ end
338
+
339
+ it "only copies cookbooks that exist in the run_list" do
340
+ expect(stdout).not_to include("execute[cp -rf #{default_cookbook_path}/dummy #{Chef::Config[:chef_repo_path]}/dockerfiles/docker/demo/chef/cookbooks/] action run")
341
+ end
342
+ end
343
+ end
344
+
345
+ describe 'when running in local-mode' do
346
+ before(:each) { reset_chef_config }
347
+
348
+ let(:argv) { %W[
349
+ docker/demo
350
+ -r recipe[nginx]
351
+ -z
352
+ ]}
353
+
354
+ let(:expected_container_file_relpaths) do
355
+ %w[
356
+ Dockerfile
357
+ .dockerignore
358
+ chef/first-boot.json
359
+ chef/zero.rb
360
+ chef/.node_name
361
+ ]
362
+ end
363
+
364
+ let(:expected_files) do
365
+ expected_container_file_relpaths.map do |relpath|
366
+ File.join(Chef::Config[:chef_repo_path], "dockerfiles", "docker/demo", relpath)
367
+ end
368
+ end
369
+
370
+ it "creates a folder to manage the Dockerfile and Chef files" do
371
+ @knife.run
372
+ generated_files = Dir.glob("#{Chef::Config[:chef_repo_path]}/dockerfiles/docker/demo/**{,/*/**}/*", File::FNM_DOTMATCH)
373
+ expected_files.each do |expected_file|
374
+ expect(generated_files).to include(expected_file)
375
+ end
376
+ end
377
+ end
378
+
379
+ describe 'when running in server-mode' do
380
+ before(:each) { reset_chef_config }
381
+
382
+ let(:argv) { %W[
383
+ docker/demo
384
+ -r recipe[nginx]
385
+ ]}
386
+
387
+ let(:expected_container_file_relpaths) do
388
+ %w[
389
+ Dockerfile
390
+ .dockerignore
391
+ chef/first-boot.json
392
+ chef/client.rb
393
+ chef/secure/validation.pem
394
+ chef/secure/trusted_certs/chef_example_com.crt
395
+ chef/.node_name
396
+ ]
397
+ end
398
+
399
+ let(:expected_files) do
400
+ expected_container_file_relpaths.map do |relpath|
401
+ File.join(Chef::Config[:chef_repo_path], "dockerfiles", "docker/demo", relpath)
402
+ end
403
+ end
404
+
405
+ it "creates a folder to manage the Dockerfile and Chef files" do
406
+ @knife.run
407
+ generated_files = Dir.glob("#{Chef::Config[:chef_repo_path]}/dockerfiles/docker/demo/**{,/*/**}/*", File::FNM_DOTMATCH)
408
+ expected_files.each do |expected_file|
409
+ expect(generated_files).to include(expected_file)
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ describe "#download_and_tag_base_image" do
416
+ before(:each) do
417
+ reset_chef_config
418
+ @knife.unstub(:download_and_tag_base_image)
419
+ end
420
+
421
+ let(:argv) { %w[ docker/demo ] }
422
+
423
+ it "should run docker pull on the specified base image and tag it with the dockerfile name" do
424
+ @knife.ui.should_receive(:info).exactly(3).times
425
+ @knife.should_receive(:shell_out).with("docker pull chef/ubuntu-12.04:latest")
426
+ @knife.should_receive(:shell_out).with("docker tag chef/ubuntu-12.04 docker/demo")
427
+ @knife.run
428
+ end
429
+ end
430
+
431
+ describe '#eval_current_system' do
432
+ let(:argv) { %w[ docker/demo ] }
433
+
434
+ context 'the context already exists' do
435
+ before do
436
+ reset_chef_config
437
+ File.stub(:exist?).with(File.join(Chef::Config[:chef_repo_path], 'dockerfiles', 'docker', 'demo')).and_return(true)
438
+ end
439
+
440
+ it 'should warn the user if the context they are trying to create already exists' do
441
+ @knife.should_receive(:show_usage)
442
+ @knife.ui.should_receive(:fatal)
443
+ lambda { @knife.run }.should raise_error(SystemExit)
444
+ end
445
+ end
446
+
447
+ context 'the context already exists but the force flag was specified' do
448
+ let(:argv) { %w[ docker/demo --force ] }
449
+
450
+ before do
451
+ reset_chef_config
452
+ File.stub(:exist?).with(File.join(Chef::Config[:chef_repo_path], 'dockerfiles', 'docker', 'demo')).and_return(true)
453
+ @knife.config[:dockerfiles_path] = File.join(Chef::Config[:chef_repo_path], 'dockerfiles')
454
+ end
455
+
456
+ it 'should delete that folder and proceed as normal' do
457
+ FileUtils.should_receive(:rm_rf).with(File.join(Chef::Config[:chef_repo_path], 'dockerfiles', 'docker', 'demo'))
458
+ @knife.read_and_validate_params
459
+ @knife.set_config_defaults
460
+ @knife.eval_current_system
461
+ end
462
+ end
463
+ end
464
+ end