cf_deployer 1.2.8

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 (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