chef_cap 0.0.5
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.
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +19 -0
- data/README.rdoc +49 -0
- data/Rakefile +10 -0
- data/chef_cap.gemspec +21 -0
- data/fixtures/parser.json +3 -0
- data/fixtures/ssh_deploy_key +1 -0
- data/fixtures/ssh_public_key +1 -0
- data/init.rb +1 -0
- data/lib/chef_cap/tasks.rb +18 -0
- data/lib/chef_cap/version.rb +3 -0
- data/lib/chef_cap.rb +14 -0
- data/lib/generators/chef_cap/install_generator.rb +14 -0
- data/lib/generators/chef_cap/templates/Capfile +18 -0
- data/lib/generators/chef_cap/templates/chef/cookbooks/gems/recipes/default.rb +11 -0
- data/lib/generators/chef_cap/templates/chef/node.json +50 -0
- data/recipes/chef_cap.rb +196 -0
- data/recipes/cook.rb +7 -0
- data/recipes/lib/chef_cap_configuration.rb +19 -0
- data/recipes/lib/chef_cap_helper.rb +77 -0
- data/recipes/lib/chef_cap_initializer.rb +6 -0
- data/recipes/lib/chef_dna_parser.rb +36 -0
- data/recipes/rvm_bootstrap.rb +34 -0
- data/recipes/unset_default_deploy.rb +44 -0
- data/spec/chef_cap_configuration_spec.rb +27 -0
- data/spec/chef_cap_helper_spec.rb +126 -0
- data/spec/chef_cap_mock_cap.rb +267 -0
- data/spec/chef_cap_spec.rb +808 -0
- data/spec/chef_dna_parser_spec.rb +29 -0
- data/spec/spec_helper.rb +45 -0
- metadata +117 -0
@@ -0,0 +1,808 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))
|
2
|
+
|
3
|
+
describe "chef_cap" do
|
4
|
+
|
5
|
+
let(:chef_cap) {
|
6
|
+
FakeChefCapConfiguration.create(@test_dna)
|
7
|
+
}
|
8
|
+
|
9
|
+
before do
|
10
|
+
@test_dna = <<-JS
|
11
|
+
{
|
12
|
+
"chef": {
|
13
|
+
"root": "path_to_cookbooks"
|
14
|
+
}
|
15
|
+
}
|
16
|
+
JS
|
17
|
+
end
|
18
|
+
|
19
|
+
it "hijacks the load method to silently ignore the config/deploy load line if deploy.rb does not exist" do
|
20
|
+
File.should_receive(:exist?).and_return(false)
|
21
|
+
chef_cap.load("foo").should == true
|
22
|
+
end
|
23
|
+
|
24
|
+
it "loads a JSON file and assigns values to capistrano variables" do
|
25
|
+
@test_dna = <<-JS
|
26
|
+
{
|
27
|
+
"chef": {
|
28
|
+
"root": "path_to_cookbooks"
|
29
|
+
},
|
30
|
+
"application": {
|
31
|
+
"name": "frobble"
|
32
|
+
}
|
33
|
+
}
|
34
|
+
JS
|
35
|
+
chef_cap.cap_variable[:application].should == "frobble"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "loads all the required capistrano variables" do
|
39
|
+
@test_dna = <<-JS
|
40
|
+
{
|
41
|
+
"chef": {
|
42
|
+
"root": "path_to_cookbooks"
|
43
|
+
},
|
44
|
+
"application": {
|
45
|
+
"name": "frobble",
|
46
|
+
"repository": "git@somehost:user/repo.git"
|
47
|
+
},
|
48
|
+
"environments": {
|
49
|
+
"defaults": {
|
50
|
+
"user": "myuser"
|
51
|
+
},
|
52
|
+
"some_env": {
|
53
|
+
"user": "newenvuser",
|
54
|
+
"servers": [
|
55
|
+
{
|
56
|
+
"hostname": "localhost",
|
57
|
+
"roles": ["app", "someotherrole", "db"],
|
58
|
+
"primary": ["db"]
|
59
|
+
}
|
60
|
+
]
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
JS
|
65
|
+
chef_cap.cap_variable[:application].should == "frobble"
|
66
|
+
chef_cap.cap_variable[:repository].should == "git@somehost:user/repo.git"
|
67
|
+
chef_cap.cap_variable[:scm].should == :git
|
68
|
+
chef_cap.cap_variable[:user].should == "myuser"
|
69
|
+
|
70
|
+
chef_cap.cap_variable[:rails_env].should be_nil
|
71
|
+
chef_cap.cap_task[:some_env].should_not be_nil
|
72
|
+
chef_cap.cap_task[:some_env].call
|
73
|
+
chef_cap.cap_variable[:rails_env].should == "some_env"
|
74
|
+
chef_cap.cap_variable[:user].should == "newenvuser"
|
75
|
+
chef_cap.cap_role[:someotherrole].should_not be_nil
|
76
|
+
chef_cap.cap_role[:someotherrole][:servers].should include("localhost")
|
77
|
+
chef_cap.cap_role[:app][:servers].should include("localhost")
|
78
|
+
chef_cap.cap_role[:db][:servers].should include("localhost")
|
79
|
+
chef_cap.cap_role[:db][:primary].should == "localhost"
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "default_environment" do
|
83
|
+
it "sets the RAILS_ENV to rails_env" do
|
84
|
+
@test_dna = <<-ERB
|
85
|
+
{
|
86
|
+
"chef": {
|
87
|
+
"root": "path_to_cookbooks"
|
88
|
+
},
|
89
|
+
"environments": {
|
90
|
+
"some_env": {
|
91
|
+
"rails_env": "my_env"
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
ERB
|
96
|
+
|
97
|
+
chef_cap.cap_task[:some_env].call
|
98
|
+
chef_cap.default_environment["RAILS_ENV"].should == "my_env"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
describe ":repositories" do
|
104
|
+
context "svn" do
|
105
|
+
before do
|
106
|
+
@test_dna = <<-JS
|
107
|
+
{
|
108
|
+
"chef": {
|
109
|
+
"root": "path_to_cookbooks"
|
110
|
+
},
|
111
|
+
"application": {
|
112
|
+
"repository": "svn://somehost:user/repo/trunk"
|
113
|
+
}
|
114
|
+
}
|
115
|
+
JS
|
116
|
+
end
|
117
|
+
|
118
|
+
it "sets the scm to svn if the repository looks to be subversion" do
|
119
|
+
chef_cap.cap_variable[:repository].should == "svn://somehost:user/repo/trunk"
|
120
|
+
chef_cap.cap_variable[:scm].should == :svn
|
121
|
+
end
|
122
|
+
|
123
|
+
it "adds a dependency on git existing on the remote machine" do
|
124
|
+
chef_cap.cap_depends["svn"].should_not be_nil
|
125
|
+
chef_cap.cap_depends["svn"].should == { :remote => :command }
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
context "git" do
|
131
|
+
|
132
|
+
before do
|
133
|
+
@test_dna = <<-JS
|
134
|
+
{
|
135
|
+
"chef": {
|
136
|
+
"root": "path_to_cookbooks"
|
137
|
+
},
|
138
|
+
"application": {
|
139
|
+
"repository": "git@somehost:user/repo.git"
|
140
|
+
}
|
141
|
+
}
|
142
|
+
JS
|
143
|
+
end
|
144
|
+
|
145
|
+
it "sets additional git settings if the repository looks to be git" do
|
146
|
+
chef_cap.cap_variable[:repository].should == "git@somehost:user/repo.git"
|
147
|
+
chef_cap.cap_variable[:scm].should == :git
|
148
|
+
chef_cap.cap_variable[:git_enable_submodules].should == 1
|
149
|
+
end
|
150
|
+
|
151
|
+
it "adds a dependency on git existing on the remote machine" do
|
152
|
+
chef_cap.cap_depends["git"].should_not be_nil
|
153
|
+
chef_cap.cap_depends["git"].should == { :remote => :command }
|
154
|
+
end
|
155
|
+
|
156
|
+
it "sets the default_run_options pty to true" do
|
157
|
+
chef_cap.default_run_options[:pty].should be_true
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
describe "ssh keys" do
|
164
|
+
|
165
|
+
before do
|
166
|
+
@test_dna = <<-ERB
|
167
|
+
{
|
168
|
+
"chef": {
|
169
|
+
"root": "path_to_cookbooks"
|
170
|
+
},
|
171
|
+
"environments": {
|
172
|
+
"defaults": {
|
173
|
+
"ssh": {
|
174
|
+
"deploy_key_file": "#{File.join(File.dirname(__FILE__), '..', 'fixtures', 'ssh_deploy_key')}",
|
175
|
+
"authorized_pub_file": "#{File.join(File.dirname(__FILE__), '..', 'fixtures', 'ssh_public_key')}",
|
176
|
+
"known_hosts": "knownhostscontent",
|
177
|
+
"options": {
|
178
|
+
"keys": "some_ssh_key_path"
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
182
|
+
}
|
183
|
+
}
|
184
|
+
ERB
|
185
|
+
end
|
186
|
+
|
187
|
+
it "parses out the deploy private key" do
|
188
|
+
chef_cap.cap_variable[:ssh_deploy_key_file].should_not be_nil
|
189
|
+
chef_cap.cap_variable[:ssh_deploy_key_file].should == File.join(File.dirname(__FILE__), '..', 'fixtures', 'ssh_deploy_key')
|
190
|
+
end
|
191
|
+
|
192
|
+
it "parses out the authorized public file" do
|
193
|
+
chef_cap.cap_variable[:ssh_authorized_pub_file].should_not be_nil
|
194
|
+
chef_cap.cap_variable[:ssh_authorized_pub_file].should == File.join(File.dirname(__FILE__), '..', 'fixtures', 'ssh_public_key')
|
195
|
+
end
|
196
|
+
|
197
|
+
it "parses out the known hosts file" do
|
198
|
+
chef_cap.cap_variable[:ssh_known_hosts].should_not be_nil
|
199
|
+
chef_cap.cap_variable[:ssh_known_hosts].should == "knownhostscontent"
|
200
|
+
end
|
201
|
+
|
202
|
+
it "creates a task that uploads the keys to the server users .ssh directory" do
|
203
|
+
chef_cap.cap_namespace[:ssh].should be_true
|
204
|
+
chef_cap.cap_task["ssh:transfer_keys"].should_not be_nil
|
205
|
+
chef_cap.should_receive(:put).with("dsa imaprivatekey", ".ssh/id_dsa", :mode => "0600").and_return(true)
|
206
|
+
chef_cap.should_receive(:put).with("imapublickey", ".ssh/authorized_keys", :mode => "0600").and_return(true)
|
207
|
+
chef_cap.should_receive(:put).with("knownhostscontent", ".ssh/known_hosts", :mode => "0600").and_return(true)
|
208
|
+
|
209
|
+
chef_cap.cap_task["ssh:transfer_keys"].call
|
210
|
+
end
|
211
|
+
|
212
|
+
it "adds a before chef:setup hook that that uploads the keys on every deploy" do
|
213
|
+
chef_cap.cap_before["chef:setup"].should_not be_nil
|
214
|
+
chef_cap.cap_before["chef:setup"].should include("ssh:transfer_keys")
|
215
|
+
end
|
216
|
+
|
217
|
+
it "adds dependencies on remote files for the ssh files to be uploaded" do
|
218
|
+
chef_cap.cap_depends[".ssh/id_dsa"].should_not be_nil
|
219
|
+
chef_cap.cap_depends[".ssh/id_dsa"].should == { :remote => :file }
|
220
|
+
|
221
|
+
chef_cap.cap_depends[".ssh/authorized_keys"].should_not be_nil
|
222
|
+
chef_cap.cap_depends[".ssh/authorized_keys"].should == { :remote => :file }
|
223
|
+
|
224
|
+
chef_cap.cap_depends[".ssh/known_hosts"].should_not be_nil
|
225
|
+
chef_cap.cap_depends[".ssh/known_hosts"].should == { :remote => :file }
|
226
|
+
end
|
227
|
+
|
228
|
+
describe "ssh_options" do
|
229
|
+
it "parses out keys" do
|
230
|
+
chef_cap.cap_ssh_options[:keys].should_not be_nil
|
231
|
+
chef_cap.cap_ssh_options[:keys].should == "some_ssh_key_path"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "rvm bootstrap" do
|
238
|
+
it "uploads a shell script to the server and runs it as root" do
|
239
|
+
@test_dna = <<-ERB
|
240
|
+
{
|
241
|
+
"chef": {
|
242
|
+
"root": "path_to_cookbooks"
|
243
|
+
},
|
244
|
+
"environments": {
|
245
|
+
"some_env": {
|
246
|
+
"rails_env": "my_env"
|
247
|
+
}
|
248
|
+
}
|
249
|
+
}
|
250
|
+
ERB
|
251
|
+
|
252
|
+
chef_cap.cap_task[:some_env].call
|
253
|
+
chef_cap.cap_namespace[:rvm].should_not be_nil
|
254
|
+
chef_cap.cap_task["rvm:bootstrap"].should_not be_nil
|
255
|
+
|
256
|
+
chef_cap.should_receive(:put)
|
257
|
+
chef_cap.should_receive(:sudo).with("/tmp/chef-cap-my_env-rvm-standup.sh")
|
258
|
+
chef_cap.cap_task["rvm:bootstrap"].call
|
259
|
+
end
|
260
|
+
|
261
|
+
it "adds a dependency check on the rvm command" do
|
262
|
+
chef_cap.cap_depends["rvm"].should_not be_nil
|
263
|
+
chef_cap.cap_depends["rvm"].should == { :remote => :command }
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe "task :chef:cleanup" do
|
268
|
+
it "creates a task that wipes all /tmp/chef-cap* files" do
|
269
|
+
chef_cap.cap_namespace[:chef].should_not be_nil
|
270
|
+
chef_cap.cap_task["chef:cleanup"].should_not be_nil
|
271
|
+
|
272
|
+
chef_cap.should_receive(:sudo).with("rm -rf /tmp/chef-cap*")
|
273
|
+
chef_cap.cap_task["chef:cleanup"].call
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
describe "namespace :chef" do
|
278
|
+
|
279
|
+
it "adds a remote dependency on chef-solo" do
|
280
|
+
chef_cap.cap_depends["chef-solo"].should_not be_nil
|
281
|
+
chef_cap.cap_depends["chef-solo"].should == { :remote => :command }
|
282
|
+
end
|
283
|
+
|
284
|
+
it "runs chef:setup before chef:deploy" do
|
285
|
+
chef_cap.cap_before["chef:deploy"].should_not be_nil
|
286
|
+
chef_cap.cap_before["chef:deploy"].should include("chef:setup")
|
287
|
+
end
|
288
|
+
|
289
|
+
it "runs rvm:bootstrap before chef:setup" do
|
290
|
+
chef_cap.cap_before["chef:setup"].should_not be_nil
|
291
|
+
chef_cap.cap_before["chef:setup"].should include("rvm:bootstrap")
|
292
|
+
end
|
293
|
+
|
294
|
+
describe "task :deploy" do
|
295
|
+
|
296
|
+
before do
|
297
|
+
@test_dna = <<-JS
|
298
|
+
{
|
299
|
+
"chef": {
|
300
|
+
"root": "path_to_cookbooks"
|
301
|
+
},
|
302
|
+
"environments": {
|
303
|
+
"some_env": {
|
304
|
+
"rails_env": "myenv",
|
305
|
+
"servers": [
|
306
|
+
{
|
307
|
+
"hostname": "localhost",
|
308
|
+
"roles": ["role1", "role2"]
|
309
|
+
},
|
310
|
+
{
|
311
|
+
"hostname": "otherhost.com",
|
312
|
+
"roles": ["role1"]
|
313
|
+
}
|
314
|
+
]
|
315
|
+
}
|
316
|
+
},
|
317
|
+
"roles": {
|
318
|
+
"role1": { "run_list": ["foo"] },
|
319
|
+
"role2": { "run_list": ["foo", "bar"] }
|
320
|
+
}
|
321
|
+
}
|
322
|
+
JS
|
323
|
+
|
324
|
+
chef_cap.cap_task[:some_env].should_not be_nil
|
325
|
+
chef_cap.cap_task[:some_env].call
|
326
|
+
end
|
327
|
+
|
328
|
+
it "exists" do
|
329
|
+
chef_cap.cap_namespace[:chef].should be_true
|
330
|
+
chef_cap.cap_task["chef:deploy"].should_not be_nil
|
331
|
+
end
|
332
|
+
|
333
|
+
it "modifies the default run list for each host and stores all modified structure" do
|
334
|
+
chef_cap.roles.keys.should == ["role1", "role2"]
|
335
|
+
|
336
|
+
chef_cap.stub!(:put => "stubbed")
|
337
|
+
chef_cap.stub!(:upload => "stubbed")
|
338
|
+
chef_cap.stub!(:sudo => "stubbed")
|
339
|
+
|
340
|
+
chef_cap.cap_variable[:environment_settings].should_not be_nil
|
341
|
+
|
342
|
+
chef_cap.parallel_mocks << proc { |server_session|
|
343
|
+
server_session.stub!(:put => "stubbed")
|
344
|
+
server_session.stub!(:sudo => "stubbed")
|
345
|
+
}
|
346
|
+
|
347
|
+
chef_cap.cap_task["chef:deploy"].call
|
348
|
+
|
349
|
+
chef_cap.parallel_sessions.each do |server_session|
|
350
|
+
if server_session.things_that_were_set.keys.include? "node_hash_for_localhost"
|
351
|
+
server_session.things_that_were_set["node_hash_for_localhost"].should == {
|
352
|
+
"environments" => {"some_env"=>{ "rails_env" => "myenv",
|
353
|
+
"servers"=>[ {"hostname"=>"localhost", "roles"=>["role1", "role2"] }, {"hostname"=>"otherhost.com", "roles"=>["role1"]}]}},
|
354
|
+
"chef" => {"root"=>"path_to_cookbooks"},
|
355
|
+
"run_list" => ["foo", "bar"],
|
356
|
+
"environment" => {"rails_env" => "myenv", "servers"=>[ {"primary" => [], "hostname"=>"localhost", "roles"=>["role1", "role2"] },
|
357
|
+
{"primary" => [], "hostname"=>"otherhost.com", "roles"=>["role1"]}]},
|
358
|
+
"roles" => {"role1" => {"run_list"=>["foo"]}, "role2"=>{"run_list"=>["foo", "bar"]}}}
|
359
|
+
elsif server_session.things_that_were_set.keys.include? "node_hash_for_otherhost"
|
360
|
+
server_session.things_that_were_set["node_hash_for_otherhost"].should == {
|
361
|
+
"environments" => {"some_env"=>{ "rails_env" => "myenv",
|
362
|
+
"servers"=>[{"hostname"=>"localhost", "roles"=>["role1", "role2"]}, {"hostname"=>"otherhost.com", "roles"=>["role1"]}]}},
|
363
|
+
"chef"=>{"root"=>"path_to_cookbooks"},
|
364
|
+
"run_list"=>["foo"],
|
365
|
+
"environment"=>{"rails_env" => "myenv", "servers"=>[{"primary" => [], "hostname"=>"localhost", "roles"=>["role1", "role2"]},
|
366
|
+
{"primary" => [], "hostname"=>"otherhost.com", "roles"=>["role1"]}]},
|
367
|
+
"roles"=>{"role1"=>{"run_list"=>["foo"]}, "role2"=>{"run_list"=>["foo", "bar"]}}}
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
it "that uploads the DNA.json and a solo.rb file" do
|
373
|
+
pending "FIXME"
|
374
|
+
localhost_dna = JSON.parse(@test_dna).dup
|
375
|
+
otherhost_dna = JSON.parse(@test_dna).dup
|
376
|
+
localhost_dna["run_list"] = ["foo", "bar"]
|
377
|
+
localhost_dna["environment"] = localhost_dna["environments"]["some_env"]
|
378
|
+
otherhost_dna["run_list"] = ["foo"]
|
379
|
+
otherhost_dna["environment"] = otherhost_dna["environments"]["some_env"]
|
380
|
+
|
381
|
+
chef_cap.parallel_mocks << proc { |server_session|
|
382
|
+
server_session.should_receive(:put).ordered.with(localhost_dna.to_json, "/tmp/chef-cap-myenv.json", :mode => "0600", :hosts => "localhost").and_return("mocked")
|
383
|
+
server_session.should_receive(:put).ordered.with(otherhost_dna.to_json, "/tmp/chef-cap-myenv.json", :mode => "0600", :hosts => "otherhost.com").and_return("mocked")
|
384
|
+
server_session.stub!(:set => "stubbed")
|
385
|
+
server_session.stub!(:sudo => "stubbed")
|
386
|
+
}
|
387
|
+
chef_cap.should_receive(:put).ordered.with("cookbook_path '/tmp/chef-cap-myenv/cookbooks'", "/tmp/chef-cap-solo-myenv.rb", :mode => "0600").and_return("mocked")
|
388
|
+
chef_cap.stub!(:upload => "stubbed")
|
389
|
+
chef_cap.stub!(:sudo => "stubbed")
|
390
|
+
chef_cap.cap_task["chef:deploy"].call
|
391
|
+
end
|
392
|
+
|
393
|
+
it "uploads the cookbooks" do
|
394
|
+
chef_cap.stub!(:put => "stubbed")
|
395
|
+
chef_cap.should_receive(:upload).with("path_to_cookbooks", "/tmp/chef-cap-myenv", :mode => "0700").and_return("mocked")
|
396
|
+
chef_cap.stub!(:sudo => "stubbed")
|
397
|
+
chef_cap.parallel_mocks << proc { |server_session|
|
398
|
+
server_session.stub!(:put => "stubbed")
|
399
|
+
server_session.stub!(:set => "stubbed")
|
400
|
+
server_session.stub!(:sudo => "stubbed")
|
401
|
+
}
|
402
|
+
chef_cap.cap_task["chef:deploy"].call
|
403
|
+
end
|
404
|
+
|
405
|
+
it "sets up chef gem" do
|
406
|
+
chef_cap.cap_servers.should_not be_empty
|
407
|
+
chef_cap.should_receive(:sudo).ordered.with("rvm default exec gem specification --version '>=0.9.12' chef 2>&1 | awk 'BEGIN { s = 0 } /^name:/ { s = 1; exit }; END { if(s == 0) exit 1 }' || sudo rvm default exec gem install chef --no-ri --no-rdoc && echo 'Chef Solo already on this server.'").and_return("mocked")
|
408
|
+
chef_cap.should_receive(:sudo).ordered.with("rvm default exec which chef-solo").and_return("mocked")
|
409
|
+
chef_cap.cap_task["chef:setup"].call
|
410
|
+
end
|
411
|
+
|
412
|
+
it "installs rvm + ruby and run it if it does not exist" do
|
413
|
+
chef_cap.cap_servers.should_not be_empty
|
414
|
+
chef_cap.stub!(:put => "stubbed")
|
415
|
+
chef_cap.should_receive(:sudo).ordered.with("/tmp/chef-cap-myenv-rvm-standup.sh").and_return("mocked")
|
416
|
+
chef_cap.cap_task["rvm:bootstrap"].call
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|
420
|
+
|
421
|
+
describe "task :run_chef_solo" do
|
422
|
+
|
423
|
+
before do
|
424
|
+
@test_dna = <<-JS
|
425
|
+
{
|
426
|
+
"chef": {
|
427
|
+
"root": "path_to_cookbooks"
|
428
|
+
},
|
429
|
+
"environments": {
|
430
|
+
"some_env": {
|
431
|
+
"rails_env": "myenv",
|
432
|
+
"servers": [
|
433
|
+
{
|
434
|
+
"hostname": "dbhost",
|
435
|
+
"roles": ["db"]
|
436
|
+
},
|
437
|
+
{
|
438
|
+
"hostname": "apphost",
|
439
|
+
"roles": ["app"]
|
440
|
+
}
|
441
|
+
]
|
442
|
+
}
|
443
|
+
},
|
444
|
+
"roles": {
|
445
|
+
"db": { "run_list": [] },
|
446
|
+
"app": { "run_list": [] }
|
447
|
+
}
|
448
|
+
}
|
449
|
+
JS
|
450
|
+
|
451
|
+
chef_cap.cap_task[:some_env].should_not be_nil
|
452
|
+
chef_cap.cap_task[:some_env].call
|
453
|
+
end
|
454
|
+
|
455
|
+
it "invokes chef-solo on db hosts then app and web only hosts" do
|
456
|
+
chef_cap.cap_servers.should_not be_empty
|
457
|
+
|
458
|
+
chef_cap.should_receive(:sudo).ordered.with(/.*chef-solo.*/, :hosts => ["dbhost"]).and_return("mocked")
|
459
|
+
chef_cap.should_receive(:sudo).ordered.with(/.*chef-solo.*/, :hosts => ["apphost"]).and_return("mocked")
|
460
|
+
chef_cap.cap_task["chef:run_chef_solo"].call
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
describe "merging roles with shared" do
|
465
|
+
|
466
|
+
before do
|
467
|
+
@test_dna = <<-JS
|
468
|
+
{
|
469
|
+
"chef": {
|
470
|
+
"root": "path_to_cookbooks"
|
471
|
+
},
|
472
|
+
"environments": {
|
473
|
+
"some_env": {
|
474
|
+
"servers": [
|
475
|
+
{
|
476
|
+
"hostname": "localhost",
|
477
|
+
"roles": ["role1", "role2"]
|
478
|
+
},
|
479
|
+
{
|
480
|
+
"hostname": "otherhost",
|
481
|
+
"roles": ["role2"]
|
482
|
+
}
|
483
|
+
]
|
484
|
+
}
|
485
|
+
},
|
486
|
+
"shared": {
|
487
|
+
"string": "shouldbeoverwritten",
|
488
|
+
"simple": ["one", "two"],
|
489
|
+
"complicated": {
|
490
|
+
"three": { "shared": "shouldbeoverwritten", "alsoshared": ["shared"] },
|
491
|
+
"four": "shouldbeoverwritten",
|
492
|
+
"five": "stringtype"
|
493
|
+
},
|
494
|
+
"run_list": ["shared"]
|
495
|
+
},
|
496
|
+
"roles": {
|
497
|
+
"role1": {
|
498
|
+
"string": "overwritten",
|
499
|
+
"simple": ["merged"],
|
500
|
+
"run_list": ["role1", "roleshared"]
|
501
|
+
},
|
502
|
+
"role2": {
|
503
|
+
"complicated": {
|
504
|
+
"three": { "shared": "overwritten", "alsoshared": ["merged"] },
|
505
|
+
"four": "overwritten",
|
506
|
+
"five": ["newtype"]
|
507
|
+
},
|
508
|
+
"run_list": ["role2", "roleshared"]
|
509
|
+
}
|
510
|
+
},
|
511
|
+
"run_list": ["everything"]
|
512
|
+
}
|
513
|
+
JS
|
514
|
+
end
|
515
|
+
|
516
|
+
it "merges recursively all shared and all roles data down into top level keys" do
|
517
|
+
chef_cap.stub!(:put => "stubbed")
|
518
|
+
chef_cap.stub!(:upload => "stubbed")
|
519
|
+
chef_cap.stub!(:sudo => "stubbed")
|
520
|
+
|
521
|
+
chef_cap.cap_task[:some_env].call
|
522
|
+
|
523
|
+
chef_cap.parallel_mocks << proc { |server_session|
|
524
|
+
server_session.stub!(:put => "stubbed")
|
525
|
+
server_session.stub!(:sudo => "stubbed")
|
526
|
+
}
|
527
|
+
|
528
|
+
chef_cap.cap_task["chef:deploy"].call
|
529
|
+
|
530
|
+
chef_cap.parallel_sessions.each do |server_session|
|
531
|
+
if server_session.things_that_were_set.keys.include? "node_hash_for_localhost"
|
532
|
+
server_session.things_that_were_set["node_hash_for_localhost"]["simple"].should == ["one", "two", "merged"]
|
533
|
+
server_session.things_that_were_set["node_hash_for_localhost"]["complicated"].should == {"three"=>{"alsoshared"=>["merged"], "shared"=>"overwritten"}, "four"=>"overwritten", "five"=>["newtype"]}
|
534
|
+
server_session.things_that_were_set["node_hash_for_localhost"]["string"].should == "overwritten"
|
535
|
+
server_session.things_that_were_set["node_hash_for_localhost"]["run_list"].should == ["everything", "shared", "role1", "roleshared", "role2"]
|
536
|
+
elsif server_session.things_that_were_set.keys.include? "node_hash_for_otherhost"
|
537
|
+
server_session.things_that_were_set["node_hash_for_otherhost"]["simple"].should == ["one", "two"]
|
538
|
+
server_session.things_that_were_set["node_hash_for_otherhost"]["complicated"].should == {"three"=>{"alsoshared"=>["merged"], "shared"=>"overwritten"}, "four"=>"overwritten", "five"=>["newtype"]}
|
539
|
+
server_session.things_that_were_set["node_hash_for_otherhost"]["string"].should == "shouldbeoverwritten"
|
540
|
+
server_session.things_that_were_set["node_hash_for_otherhost"]["run_list"].should == ["everything", "shared", "role2", "roleshared"]
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
describe "node[:deploy_recipe]" do
|
547
|
+
before do
|
548
|
+
@test_dna = <<-JS
|
549
|
+
{
|
550
|
+
"chef": {
|
551
|
+
"root": "path_to_cookbooks"
|
552
|
+
},
|
553
|
+
"deploy_recipe": "my_deploy_recipe",
|
554
|
+
"environments": {
|
555
|
+
"some_env": {
|
556
|
+
"something_else": "okay",
|
557
|
+
"servers": [
|
558
|
+
{
|
559
|
+
"hostname": "localhost",
|
560
|
+
"roles": ["role1", "role2"]
|
561
|
+
}
|
562
|
+
]
|
563
|
+
}
|
564
|
+
},
|
565
|
+
"roles": {
|
566
|
+
"role1": {
|
567
|
+
"string": "overwritten",
|
568
|
+
"simple": ["merged"],
|
569
|
+
"run_list": ["role1", "roleshared"]
|
570
|
+
},
|
571
|
+
"role2": {
|
572
|
+
"complicated": {
|
573
|
+
"three": { "shared": "overwritten", "alsoshared": ["merged"] },
|
574
|
+
"four": "overwritten",
|
575
|
+
"five": ["newtype"]
|
576
|
+
},
|
577
|
+
"run_list": ["my_deploy_recipe", "something", "something", "darkside"]
|
578
|
+
}
|
579
|
+
}
|
580
|
+
}
|
581
|
+
JS
|
582
|
+
end
|
583
|
+
|
584
|
+
it "puts the specified deploy_recipe at the very end of the run list" do
|
585
|
+
chef_cap.stub!(:put => "stubbed")
|
586
|
+
chef_cap.stub!(:upload => "stubbed")
|
587
|
+
chef_cap.stub!(:sudo => "stubbed")
|
588
|
+
|
589
|
+
chef_cap.cap_task[:some_env].call
|
590
|
+
|
591
|
+
chef_cap.parallel_mocks << proc { |server_session|
|
592
|
+
server_session.stub!(:put => "stubbed")
|
593
|
+
server_session.stub!(:sudo => "stubbed")
|
594
|
+
}
|
595
|
+
|
596
|
+
chef_cap.cap_task["chef:deploy"].call
|
597
|
+
|
598
|
+
chef_cap.parallel_sessions.each do |server_session|
|
599
|
+
server_session.things_that_were_set["node_hash_for_localhost"]["run_list"].should == ["role1", "roleshared", "something", "darkside", "my_deploy_recipe"]
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
describe "node[:environment]" do
|
605
|
+
before do
|
606
|
+
@test_dna = <<-JS
|
607
|
+
{
|
608
|
+
"chef": {
|
609
|
+
"root": "path_to_cookbooks"
|
610
|
+
},
|
611
|
+
"environments": {
|
612
|
+
"defaults": {
|
613
|
+
"some_default": "yes"
|
614
|
+
},
|
615
|
+
"some_env": {
|
616
|
+
"something_else": "okay",
|
617
|
+
"servers": [
|
618
|
+
{
|
619
|
+
"hostname": "localhost",
|
620
|
+
"roles": ["role1", "role2"]
|
621
|
+
}
|
622
|
+
]
|
623
|
+
},
|
624
|
+
"ignored_env": {
|
625
|
+
"should_not_be_there": "yup"
|
626
|
+
}
|
627
|
+
}
|
628
|
+
}
|
629
|
+
JS
|
630
|
+
end
|
631
|
+
|
632
|
+
it "contains a copy of the structure of the environment we are in that merged with the defaults" do
|
633
|
+
chef_cap.stub!(:put => "stubbed")
|
634
|
+
chef_cap.stub!(:upload => "stubbed")
|
635
|
+
chef_cap.stub!(:sudo => "stubbed")
|
636
|
+
|
637
|
+
chef_cap.cap_task[:some_env].call
|
638
|
+
|
639
|
+
chef_cap.parallel_mocks << proc { |server_session|
|
640
|
+
server_session.stub!(:put => "stubbed")
|
641
|
+
server_session.stub!(:sudo => "stubbed")
|
642
|
+
}
|
643
|
+
|
644
|
+
chef_cap.cap_task["chef:deploy"].call
|
645
|
+
|
646
|
+
chef_cap.parallel_sessions.each do |server_session|
|
647
|
+
if server_session.things_that_were_set.keys.include? "node_hash_for_localhost"
|
648
|
+
server_session.things_that_were_set["node_hash_for_localhost"]["environment"].should == {"some_default"=>"yes",
|
649
|
+
"something_else"=>"okay",
|
650
|
+
"servers"=>[{"primary" => [], "hostname"=>"localhost", "roles"=>["role1", "role2"]}]}
|
651
|
+
end
|
652
|
+
end
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
describe "upload" do
|
657
|
+
before do
|
658
|
+
@test_dna = <<-JS
|
659
|
+
{
|
660
|
+
"chef": {
|
661
|
+
"root": "path_to_cookbooks"
|
662
|
+
},
|
663
|
+
"upload": [
|
664
|
+
{
|
665
|
+
"source": "some_source",
|
666
|
+
"destination": "some_destination",
|
667
|
+
"roles": ["role1"]
|
668
|
+
}
|
669
|
+
],
|
670
|
+
"environments": {
|
671
|
+
"defaults": {
|
672
|
+
},
|
673
|
+
"some_env": {
|
674
|
+
"servers": [
|
675
|
+
{
|
676
|
+
"hostname": "localhost",
|
677
|
+
"roles": ["role1", "role2"]
|
678
|
+
},
|
679
|
+
{
|
680
|
+
"hostname": "otherhost",
|
681
|
+
"roles": ["role2"]
|
682
|
+
}
|
683
|
+
]
|
684
|
+
}
|
685
|
+
}
|
686
|
+
}
|
687
|
+
JS
|
688
|
+
end
|
689
|
+
|
690
|
+
it "defines a chef:upload task and per role tasks" do
|
691
|
+
chef_cap.cap_task["chef:upload_all"].should_not be_nil
|
692
|
+
chef_cap.cap_task[:chef_upload_for_role1].should_not be_nil
|
693
|
+
end
|
694
|
+
|
695
|
+
it "runs chef:upload before chef:deploy" do
|
696
|
+
chef_cap.cap_before["chef:deploy"].should_not be_nil
|
697
|
+
chef_cap.cap_before["chef:deploy"].should include("chef:upload_all")
|
698
|
+
end
|
699
|
+
|
700
|
+
it "takes all hashes listed and uploads the source to the destinations for the given roles" do
|
701
|
+
pending "Need to mock out channel inside of run"
|
702
|
+
chef_cap.cap_task[:some_env].call
|
703
|
+
|
704
|
+
chef_cap.cap_task["chef:upload_all"].call
|
705
|
+
chef_cap.cap_run["md5sum some_destination | cut -f1 -d ' '"].should_not be_nil
|
706
|
+
result = chef_cap.cap_run["md5sum some_destination | cut -f1 -d ' '"]
|
707
|
+
chef_cap.stub!(:'`' => "whatever result")
|
708
|
+
chef_cap.should_receive(:upload).with("some_source", "some_destination", {:mode => "0644"})
|
709
|
+
result.call("channel", "stream", "data")
|
710
|
+
end
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
describe "task :cook" do
|
715
|
+
before do
|
716
|
+
@test_dna = <<-JS
|
717
|
+
{
|
718
|
+
"chef": {
|
719
|
+
"root": "path_to_cookbooks"
|
720
|
+
},
|
721
|
+
"deploy_recipe": "my_deploy_recipe",
|
722
|
+
"environments": {
|
723
|
+
"some_env": {
|
724
|
+
"servers": [
|
725
|
+
{
|
726
|
+
"hostname": "localhost",
|
727
|
+
"roles": ["role1", "role2"]
|
728
|
+
}
|
729
|
+
]
|
730
|
+
}
|
731
|
+
},
|
732
|
+
"roles": {
|
733
|
+
"role1": {
|
734
|
+
"string": "overwritten",
|
735
|
+
"simple": ["merged"],
|
736
|
+
"run_list": ["role1", "roleshared"]
|
737
|
+
}
|
738
|
+
}
|
739
|
+
}
|
740
|
+
JS
|
741
|
+
end
|
742
|
+
|
743
|
+
it "defines a cook task that calls chef deploy but without the deploy recipe" do
|
744
|
+
chef_cap.cap_task[:cook].should_not be_nil
|
745
|
+
|
746
|
+
chef_cap.cap_task[:some_env].call
|
747
|
+
|
748
|
+
chef_cap.cap_task[:cook].call
|
749
|
+
end
|
750
|
+
|
751
|
+
it "calls chef:setup before cook" do
|
752
|
+
chef_cap.cap_before["cook"].should_not be_nil
|
753
|
+
chef_cap.cap_before["cook"].should include("chef:setup")
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
describe "rev environment variable" do
|
758
|
+
before do
|
759
|
+
@test_dna = <<-JS
|
760
|
+
{
|
761
|
+
"chef": {
|
762
|
+
"root": "path_to_cookbooks"
|
763
|
+
},
|
764
|
+
"environments": {
|
765
|
+
"some_env": {
|
766
|
+
"servers": [
|
767
|
+
{
|
768
|
+
"hostname": "localhost",
|
769
|
+
"roles": ["role1", "role2"]
|
770
|
+
}
|
771
|
+
]
|
772
|
+
}
|
773
|
+
},
|
774
|
+
"shared": {
|
775
|
+
"foo": "bar"
|
776
|
+
},
|
777
|
+
"roles": {
|
778
|
+
"role1": {
|
779
|
+
"something": "other"
|
780
|
+
}
|
781
|
+
}
|
782
|
+
}
|
783
|
+
JS
|
784
|
+
end
|
785
|
+
|
786
|
+
it "shoves the value into the node json alongside branch" do
|
787
|
+
ENV['rev'] = "123"
|
788
|
+
chef_cap.stub!(:put => "stubbed")
|
789
|
+
chef_cap.stub!(:upload => "stubbed")
|
790
|
+
chef_cap.stub!(:sudo => "stubbed")
|
791
|
+
|
792
|
+
chef_cap.cap_task[:some_env].call
|
793
|
+
chef_cap.parallel_mocks << proc { |server_session|
|
794
|
+
server_session.stub!(:put => "stubbed")
|
795
|
+
server_session.stub!(:sudo => "stubbed")
|
796
|
+
server_session.should_receive(:set).with("node_hash_for_localhost",
|
797
|
+
{"environments" => { "some_env"=>{"servers"=>[{"hostname"=>"localhost", "roles"=>["role1", "role2"]}]}},
|
798
|
+
"something"=>"other", "foo"=>"bar",
|
799
|
+
"chef"=>{"root"=>"path_to_cookbooks"},
|
800
|
+
"run_list"=>nil, "shared"=>{"foo"=>"bar"},
|
801
|
+
"environment"=> {"revision"=>"123", "servers"=>[{"primary" => [], "hostname"=>"localhost", "roles"=>["role1", "role2"]}]}, "roles"=>{"role1"=>{"something"=>"other"}}}
|
802
|
+
)
|
803
|
+
}
|
804
|
+
chef_cap.cap_task["chef:deploy"].call
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
end
|