knife-container 0.2.0

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