cf_deployer 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +29 -0
  3. data/ChangeLog.md +16 -0
  4. data/DETAILS.md +268 -0
  5. data/FAQ.md +61 -0
  6. data/Gemfile +10 -0
  7. data/Gemfile.lock +51 -0
  8. data/LICENSE +22 -0
  9. data/QUICKSTART.md +96 -0
  10. data/README.md +36 -0
  11. data/Rakefile +32 -0
  12. data/bin/cf_deploy +10 -0
  13. data/cf_deployer.gemspec +23 -0
  14. data/lib/cf_deployer/application.rb +74 -0
  15. data/lib/cf_deployer/application_error.rb +4 -0
  16. data/lib/cf_deployer/aws_constants.rb +3 -0
  17. data/lib/cf_deployer/cli.rb +111 -0
  18. data/lib/cf_deployer/component.rb +103 -0
  19. data/lib/cf_deployer/config_loader.rb +189 -0
  20. data/lib/cf_deployer/config_validation.rb +138 -0
  21. data/lib/cf_deployer/defaults.rb +10 -0
  22. data/lib/cf_deployer/deployment_strategy/auto_scaling_group_swap.rb +102 -0
  23. data/lib/cf_deployer/deployment_strategy/base.rb +88 -0
  24. data/lib/cf_deployer/deployment_strategy/blue_green.rb +70 -0
  25. data/lib/cf_deployer/deployment_strategy/cname_swap.rb +108 -0
  26. data/lib/cf_deployer/deployment_strategy/create_or_update.rb +57 -0
  27. data/lib/cf_deployer/driver/auto_scaling_group.rb +86 -0
  28. data/lib/cf_deployer/driver/cloud_formation_driver.rb +85 -0
  29. data/lib/cf_deployer/driver/dry_run.rb +27 -0
  30. data/lib/cf_deployer/driver/elb_driver.rb +17 -0
  31. data/lib/cf_deployer/driver/instance.rb +29 -0
  32. data/lib/cf_deployer/driver/route53_driver.rb +79 -0
  33. data/lib/cf_deployer/driver/verisign_driver.rb +21 -0
  34. data/lib/cf_deployer/hook.rb +32 -0
  35. data/lib/cf_deployer/logger.rb +34 -0
  36. data/lib/cf_deployer/stack.rb +154 -0
  37. data/lib/cf_deployer/status_presenter.rb +195 -0
  38. data/lib/cf_deployer/version.rb +3 -0
  39. data/lib/cf_deployer.rb +97 -0
  40. data/spec/fakes/instance.rb +32 -0
  41. data/spec/fakes/route53_client.rb +23 -0
  42. data/spec/fakes/stack.rb +65 -0
  43. data/spec/functional/deploy_spec.rb +73 -0
  44. data/spec/functional/kill_inactive_spec.rb +57 -0
  45. data/spec/functional_spec_helper.rb +3 -0
  46. data/spec/spec_helper.rb +8 -0
  47. data/spec/unit/application_spec.rb +191 -0
  48. data/spec/unit/component_spec.rb +142 -0
  49. data/spec/unit/config_loader_spec.rb +356 -0
  50. data/spec/unit/config_validation_spec.rb +480 -0
  51. data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +435 -0
  52. data/spec/unit/deployment_strategy/base_spec.rb +44 -0
  53. data/spec/unit/deployment_strategy/cname_swap_spec.rb +294 -0
  54. data/spec/unit/deployment_strategy/create_or_update_spec.rb +113 -0
  55. data/spec/unit/deployment_strategy/deployment_strategy_spec.rb +29 -0
  56. data/spec/unit/driver/auto_scaling_group_spec.rb +127 -0
  57. data/spec/unit/driver/cloud_formation_spec.rb +32 -0
  58. data/spec/unit/driver/elb_spec.rb +11 -0
  59. data/spec/unit/driver/instance_spec.rb +30 -0
  60. data/spec/unit/driver/route53_spec.rb +85 -0
  61. data/spec/unit/driver/verisign_spec.rb +18 -0
  62. data/spec/unit/hook_spec.rb +64 -0
  63. data/spec/unit/stack_spec.rb +150 -0
  64. data/spec/unit/status_presenter_spec.rb +108 -0
  65. metadata +197 -0
@@ -0,0 +1,480 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Config Validation" do
4
+
5
+ it "should pass validation if there is no errors" do
6
+ config = {
7
+ :targets => ['web', 'api', 'scaler'],
8
+ :application => 'ABC.com-myAppfoo',
9
+ :verbosity => 'all',
10
+ :'dry-run' => false,
11
+ :'output-format' => 'human',
12
+ :components =>{
13
+ :base => {
14
+ :inputs => {:time_out => 30, :mail_server =>'abc'},
15
+ :defined_outputs => {:VPCID => {}},
16
+ :defined_parameters => {:mail_server => {}, :time_out => {}}
17
+ },
18
+ :web => {
19
+ :'deployment-strategy' => 'cname-swap',
20
+ :config_dir => 'samples/simple',
21
+ :'before-destroy'=> "puts 'destroying'",
22
+ :capabilities => ['CAPABILITIES_IAM'],
23
+ :'after-create' => {
24
+ :file => "after_create_hook.rb",
25
+ :timeout => 30
26
+ },
27
+ :'after-swap' => {
28
+ :code => "puts 'done'",
29
+ :timeout => 300
30
+ },
31
+ :settings => {
32
+ :'dns-fqdn' => 'myweb.man.com',
33
+ :'dns-zone' => 'man.com',
34
+ :'elb-name-output' => 'ELBID',
35
+ },
36
+ :inputs => {
37
+ :vpc_id => {
38
+ :component => 'base',
39
+ :'output-key' => 'VPCID'
40
+ }
41
+ },
42
+ :defined_parameters => {:vpc_id => {}},
43
+ :defined_outputs => {:ELBID => {}}
44
+ },
45
+ :api => {
46
+ :'deployment-strategy' => 'cname-swap',
47
+ :settings => {
48
+ :'dns-fqdn' => 'myapi.man.com',
49
+ :'dns-zone' => 'man.com',
50
+ :'elb-name-output' => 'ELBName',
51
+ },
52
+ :defined_outputs => {:ELBName => {}}
53
+ },
54
+ :scaler => {
55
+ :'deployment-strategy' => 'auto-scaling-group-swap',
56
+ :settings => {
57
+ :'auto-scaling-group-name-output' => ['ASGName']
58
+ },
59
+ :defined_outputs => {:ASGName => {}}
60
+ }
61
+ },
62
+ :environment =>{
63
+ :dev => {}
64
+ }
65
+ }
66
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.not_to raise_error
67
+ end
68
+
69
+ it "should get error if hook is not string, code or file" do
70
+ config = {
71
+ :targets => ['base'],
72
+ :application => 'myApp',
73
+ :components =>{
74
+ :base => {
75
+ :'deployment-strategy' => 'cname-swap',
76
+ :'before-destroy' => {:ruby => "puts 'hi'"},
77
+ :'after-create' => {:something => ""},
78
+ :'after-swap' => {:foo => "boo"},
79
+ :settings => {
80
+ :'dns-fqdn' => 'myweb.man.com',
81
+ :'dns-zone' => 'man.com',
82
+ :'elb-name-output' => 'ELBID'
83
+ },
84
+ :defined_outputs => {:ELBID => {}}
85
+ }
86
+ }}
87
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/Invalid hook 'before-destroy'/)
88
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/Invalid hook 'after-create'/)
89
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/Invalid hook 'after-swap'/)
90
+ end
91
+
92
+ it "should get error if hook points to a file which does not exist" do
93
+ config = {
94
+ :targets => ['base'],
95
+ :application => 'myApp',
96
+ :components =>{
97
+ :base => {
98
+ :config_dir => '../samples',
99
+ :'before-destroy' => {
100
+ :file => "something.rb"}
101
+ }
102
+ }}
103
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("File '../samples/something.rb' does not exist, which is required by hook 'before-destroy'")
104
+ end
105
+
106
+ it "should get error if any CF parameters do not have co-responding settings" do
107
+ config = {
108
+ :targets => ['base'],
109
+ :application => 'myApp',
110
+ :components =>{
111
+ :base => {
112
+ :defined_parameters => {:mail_server =>{}}
113
+ }
114
+ }}
115
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("No input setting 'mail_server' found for CF template parameter in component base")
116
+ end
117
+
118
+ it "should not get error if any CF parameters do not have co-responding settings and we tell CV not to validate inputs" do
119
+ config = {
120
+ :targets => ['base'],
121
+ :application => 'myApp',
122
+ :components =>{
123
+ :base => {
124
+ :defined_parameters => {:mail_server =>{}}
125
+ }
126
+ }}
127
+ expect{CfDeployer::ConfigValidation.new.validate(config, false)}.not_to raise_error
128
+ end
129
+
130
+ it "should not get error if any CF parameters do not have co-responding settings but the component is not a target to deploy" do
131
+ config = {
132
+ :targets => ['vpn'],
133
+ :application => 'myApp',
134
+ :components =>{
135
+ :vpn => {},
136
+ :web => {
137
+ :defined_parameters => {:mail_server =>{}}
138
+ }
139
+ }}
140
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.not_to raise_error
141
+ end
142
+
143
+ it "should not get error if any deployment options are not set for a component that is not a target to deploy" do
144
+ config = {
145
+ :targets => ['vpn'],
146
+ :application => 'myApp',
147
+ :components =>{
148
+ :vpn => {},
149
+ :web => {
150
+ :'deployment-strategy' => 'cname-swap',
151
+ :inputs => { :foo => 'dd' }
152
+ }
153
+ }}
154
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.not_to raise_error
155
+ end
156
+
157
+ it "should not get error if CF parameters have a default and are not set in the config" do
158
+ config = {
159
+ :targets => ['base'],
160
+ :application => 'myApp',
161
+ :components =>{
162
+ :base => {
163
+ :defined_parameters => {:mail_server =>{:Default => 'abc'}}
164
+ }
165
+ }}
166
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.not_to raise_error
167
+ end
168
+
169
+ it "should get error if there is un-used inputs" do
170
+ config = {
171
+ :targets => ['base'],
172
+ :application => 'myApp',
173
+ :components =>{
174
+ :base => {
175
+ :inputs => {
176
+ :vpc_id => "ab1234"
177
+ },
178
+ :settings => {
179
+ :'raise-error-for-unused-inputs' => true,
180
+ },
181
+ :defined_parameters => {:vpcId => {:Default => 'ef2345'}}
182
+ }
183
+ }}
184
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("The input 'vpc_id' defined in the component 'base' is not used in the json template as a parameter")
185
+ end
186
+
187
+ it "should not get error if raise-error-for-unused-inputs is not set to true" do
188
+ config = {
189
+ :targets => ['base'],
190
+ :application => 'myApp',
191
+ :components =>{
192
+ :base => {
193
+ :inputs => {
194
+ :vpc_id => "ab1234"
195
+ },
196
+ :settings => {
197
+ :'raise-error-for-unused-inputs' => false,
198
+ },
199
+ :defined_parameters => {:vpcId => {:Default => 'ef2345'}}
200
+ }
201
+ }}
202
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.not_to raise_error
203
+ end
204
+
205
+ it "should get error if there is un-recognized option under the component level" do
206
+ config = {
207
+ :targets => ['base'],
208
+ :application => 'myApp',
209
+ :components =>{
210
+ :base => {
211
+ :boo => {}
212
+ }
213
+ },
214
+ :environments => {},
215
+ :tags => {}
216
+ }
217
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("The option 'boo' of the component 'base' is not valid")
218
+ end
219
+
220
+ it "should get error if there is un-recognized option under the environment level" do
221
+ config = {
222
+ :targets => ['base'],
223
+ :application => 'myApp',
224
+ :components =>{
225
+ :base => {}
226
+ },
227
+ :environments => {
228
+ :dev => {
229
+ :foo => {}
230
+ }
231
+ },
232
+ :tags => {}
233
+ }
234
+ expect{ CfDeployer::ConfigValidation.new.validate(config) }.to raise_error("The option 'foo' of the environment 'dev' is not valid")
235
+ end
236
+
237
+
238
+ it "should get error if any output-reference settings do not have co-responding output" do
239
+ config = {
240
+ :targets => ['web', 'base'],
241
+ :application => 'myApp',
242
+ :components =>{
243
+ :base => {
244
+ },
245
+ :web => {
246
+ :inputs => {:vpc_id => {
247
+ :component => 'base',
248
+ :'output-key' => 'VPCID'
249
+ }},
250
+ :defined_parameters => {:vpc_id =>{}}
251
+ }
252
+ }}
253
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("No output 'VPCID' found in CF template of component base, which is referenced by input setting 'vpc_id' in component web")
254
+ end
255
+
256
+ it "should get error if application name is missing" do
257
+ config = {
258
+ :components => {
259
+ :base =>{
260
+ }
261
+ }
262
+ }
263
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("Application name is missing in config")
264
+ config[:application] = ""
265
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("Application name is missing in config")
266
+ end
267
+
268
+ it "should get error if application name is too long (100 characters)" do
269
+ config = {
270
+ :targets => ['base'],
271
+ :application => "a" * 101,
272
+ :components => {
273
+ :base =>{
274
+ }
275
+ }
276
+ }
277
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("Application name cannot be longer than 100 and can only contain letters, numbers, '-' and '.'")
278
+ end
279
+
280
+ it "should get error if application name contains invalid characters" do
281
+ config = {
282
+ :targets => ['base'],
283
+ :application => "a!@#%^&*()",
284
+ :components => {
285
+ :base =>{
286
+ }
287
+ }
288
+ }
289
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("Application name cannot be longer than 100 and can only contain letters, numbers, '-' and '.'")
290
+ end
291
+
292
+ it "should get error if application name contains invalid character '_'" do
293
+ config = {
294
+ :targets => ['base'],
295
+ :application => "a_b",
296
+ :components => {
297
+ :base =>{
298
+ }
299
+ }
300
+ }
301
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("Application name cannot be longer than 100 and can only contain letters, numbers, '-' and '.'")
302
+ end
303
+
304
+
305
+ it "should get error if no component is defined in config" do
306
+ config = {
307
+ :application => "app",
308
+ :components => {}
309
+ }
310
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("At least one component must be defined in config")
311
+ end
312
+
313
+ it "should get error if component name is longer than 100" do
314
+ component_name = ("a"*101)
315
+ config = {
316
+ :targets => [component_name],
317
+ :application => "app",
318
+ :components => { component_name.to_sym => {} }
319
+ }
320
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("Component name cannot be longer than 100 and can only contain letters, numbers, '-' and '.': #{'a'*101}")
321
+ end
322
+
323
+ it "should get error if component name contains invalid characters" do
324
+ config = {
325
+ :targets => ['my@component', 'com_b'],
326
+ :application => "app",
327
+ :components => {
328
+ :'my@component' => {},
329
+ :com_b => {}
330
+ }
331
+ }
332
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/Component name cannot be longer than 100 and can only contain letters, numbers, '-' and '.': my@component/)
333
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/Component name cannot be longer than 100 and can only contain letters, numbers, '-' and '.': com_b/)
334
+ end
335
+
336
+
337
+ it "should get error if environment name is longer than 12" do
338
+ config = {
339
+ :targets => ['base'],
340
+ :application => "app",
341
+ :components => {
342
+ :base => {}
343
+ },
344
+ :environments => {
345
+ ("a"*13).to_sym => {}
346
+ }
347
+ }
348
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error("Environment name cannot be longer than 12 and can only contain letters, numbers, '-' and '.': #{"a"*13}")
349
+ end
350
+
351
+ it "should get error if environment name contains invalid characters" do
352
+ config = {
353
+ :targets => ['base'],
354
+ :application => "app",
355
+ :components => {
356
+ :base => {}
357
+ },
358
+ :environments => {
359
+ :'a@ss' => {},
360
+ :b_env => {}
361
+ }
362
+ }
363
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/Environment name cannot be longer than 12 and can only contain letters, numbers, '-' and '.': a@ss/)
364
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/Environment name cannot be longer than 12 and can only contain letters, numbers, '-' and '.': b_env/)
365
+ end
366
+
367
+
368
+ context 'cname-swap deployment strategy' do
369
+ it 'should require dns-fqdn, dns-zone, and elb-name-output' do
370
+ config = {
371
+ :targets => ['base'],
372
+ :application => 'app',
373
+ :components => {
374
+ :base => {
375
+ :'deployment-strategy' => 'cname-swap',
376
+ :settings => {
377
+ :'elb-name-output' => 'somthing'
378
+ },
379
+ :defined_outputs => { :somthing => {} }
380
+ }
381
+ }
382
+ }
383
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/dns-fqdn is required when using cname-swap deployment-strategy/)
384
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/dns-zone is required when using cname-swap deployment-strategy/)
385
+ end
386
+
387
+ it 'should require elb-name-output set to an existing output' do
388
+ config = {
389
+ :targets => ['base'],
390
+ :application => 'app',
391
+ :components => {
392
+ :base => {
393
+ :'deployment-strategy' => 'cname-swap',
394
+ :settings => {
395
+ :'elb-name-output' => 'somethingNotExist'
396
+ }
397
+ }
398
+ }
399
+ }
400
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/'somethingNotExist' is not a CF stack output, which is required by cname-swap deployment/)
401
+ end
402
+
403
+ it 'should require auto-scaling-group-name-output set to an existing output' do
404
+ config = {
405
+ :targets => ['worker'],
406
+ :application => 'app',
407
+ :components => {
408
+ :worker => {
409
+ :'deployment-strategy' => 'cname-swap',
410
+ :settings => {
411
+ :'elb-name-output' => 'ELBID',
412
+ :'auto-scaling-group-name-output' => ['somethingNotExist', 'IExist']
413
+ },
414
+ :defined_outputs => {
415
+ :IExist => {},
416
+ :ELBID => {}
417
+ }
418
+ }
419
+ }
420
+ }
421
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/'\["somethingNotExist"\]' is not a CF stack output/)
422
+ end
423
+ end
424
+
425
+ context 'auto-scaling-group swap deployment strategy' do
426
+ it 'should require auto-scaling-group-name-output set to an existing output' do
427
+ config = {
428
+ :targets => ['worker'],
429
+ :application => 'app',
430
+ :components => {
431
+ :worker => {
432
+ :'deployment-strategy' => 'auto-scaling-group-swap',
433
+ :settings => {
434
+ :'auto-scaling-group-name-output' => ['somethingNotExist', 'IExist']
435
+ },
436
+ :defined_outputs => {
437
+ :IExist => {}
438
+ }
439
+ }
440
+ }
441
+ }
442
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/'\["somethingNotExist"\]' is not a CF stack output/)
443
+ end
444
+ end
445
+
446
+ context 'create-or-update deployment strategy' do
447
+ it 'should require auto-scaling-group-name-output set to an existing output' do
448
+ config = {
449
+ :targets => ['worker'],
450
+ :application => 'app',
451
+ :components => {
452
+ :worker => {
453
+ :'deployment-strategy' => 'create-or-update',
454
+ :settings => {
455
+ :'auto-scaling-group-name-output' => ['somethingNotExist', 'IExist']
456
+ },
457
+ :defined_outputs => {
458
+ :IExist => {}
459
+ }
460
+ }
461
+ }
462
+ }
463
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/'\["somethingNotExist"\]' is not a CF stack output/)
464
+ end
465
+ end
466
+
467
+ context 'targets' do
468
+ it 'should find invalid targets which are not defined in config as components' do
469
+ config = {
470
+ :targets => ['web', 'vpc'],
471
+ :application => 'app',
472
+ :components => {
473
+ :base => {},
474
+ :web => {}
475
+ }
476
+ }
477
+ expect{CfDeployer::ConfigValidation.new.validate(config)}.to raise_error(/Found invalid deployment components \["vpc"\]/)
478
+ end
479
+ end
480
+ end