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,356 @@
1
+ require 'spec_helper'
2
+ describe "load config settings" do
3
+
4
+ before :each do
5
+ @config_file = File.expand_path("../../../tmp/test_config.yml", __FILE__)
6
+ @base_json = File.expand_path("../../../tmp/base.json", __FILE__)
7
+ @api_json = File.expand_path("../../../tmp/api.json", __FILE__)
8
+ @front_end_json = File.expand_path("../../../tmp/front-end.json", __FILE__)
9
+ @very_simple_json = File.expand_path("../../../tmp/very-simple.json", __FILE__)
10
+ @json_with_erb = File.expand_path("../../../tmp/json-with-erb.json", __FILE__)
11
+
12
+ base_json = <<-eos
13
+ {
14
+ "Parameters" : {},
15
+ "Outputs" : {
16
+ "vpc-id" : {},
17
+ "AutoScalingGroupName" : {},
18
+ "public-subnet-id" : {}
19
+ }
20
+ }
21
+ eos
22
+
23
+ json_with_erb = <<-eos
24
+ {
25
+ "Description": "<%= config[:inputs][:environment] %>"
26
+ }
27
+ eos
28
+
29
+ very_simple_json = <<-eos
30
+ {
31
+ "Parameters" : {},
32
+ "Outputs" : {
33
+ "vpc-id" : {},
34
+ "public-subnet-id" : {}
35
+ }
36
+ }
37
+ eos
38
+
39
+ api_json = <<-eos
40
+ {
41
+ "Parameters" : {
42
+ "require-basic-auth" : { "Default" : "true" }
43
+ }
44
+ }
45
+ eos
46
+
47
+ front_end_json = <<-eos
48
+ {
49
+ "Parameters" : {
50
+ "require-basic-auth" : {},
51
+ "mail-server" : {}
52
+ },
53
+ "Outputs" : {
54
+ "AutoScalingGroupName" : {},
55
+ "elb-cname" : {}
56
+ }
57
+ }
58
+ eos
59
+
60
+ File.open(@api_json, 'w') {|f| f.write(api_json) }
61
+ File.open(@base_json, 'w') {|f| f.write(base_json) }
62
+ File.open(@front_end_json, 'w') {|f| f.write(front_end_json) }
63
+ File.open(@very_simple_json, 'w') {|f| f.write(very_simple_json) }
64
+ File.open(@json_with_erb, 'w') {|f| f.write(json_with_erb) }
65
+
66
+
67
+ yaml_string = <<-eos
68
+ application: myApp
69
+ components:
70
+ base:
71
+ deployment-strategy: create-or-update
72
+ inputs:
73
+ foobar: <%= environment %>.IsGreat
74
+ api:
75
+ deployment-strategy: auto-scaling-group-swap
76
+ depends-on:
77
+ - base
78
+ inputs:
79
+ require-basic-auth: true
80
+ timeout: 90
81
+ mail-server: http://api.abc.com
82
+ front-end:
83
+ deployment-strategy: cname-swap
84
+ depends-on:
85
+ - base
86
+ - api
87
+ inputs:
88
+ cname: front-end.myserver.com
89
+ settings:
90
+ keep-previous-stack: false
91
+ very-simple:
92
+ json-with-erb:
93
+
94
+ inputs:
95
+ require-basic-auth: false
96
+ timeout: 300
97
+ mail-server: http://abc.com
98
+ cname: myserver.com
99
+
100
+
101
+ environments:
102
+ dev:
103
+ inputs:
104
+ mail-server: http://dev.abc.com
105
+ timeout: 60
106
+ production:
107
+ inputs:
108
+ requires-basic-auth: true
109
+ mail-server: http://prod.abc.com
110
+ components:
111
+ front-end:
112
+ inputs:
113
+ cname: prod-front-end.myserver.com
114
+ eos
115
+ File.open(@config_file, 'w') {|f| f.write(yaml_string) }
116
+
117
+ end
118
+
119
+ before :each do
120
+ ENV['timeout'] = nil
121
+ ENV['cfdeploy_settings_timeout'] = nil
122
+ end
123
+
124
+ it "all the keys should be symbols in config" do
125
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
126
+ config[:components][:base][:'deployment-strategy'].should eq('create-or-update')
127
+ config['components'].should be_nil
128
+ end
129
+
130
+ it "should copy application, environment, component to component settings" do
131
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'})
132
+ config[:components][:api][:settings][:application].should eq("myApp")
133
+ config[:components][:api][:settings][:component].should eq("api")
134
+ config[:components][:api][:settings][:environment].should eq("uat")
135
+ config[:components][:base][:settings][:application].should eq("myApp")
136
+ config[:components][:base][:settings][:component].should eq("base")
137
+ config[:components][:base][:settings][:environment].should eq("uat")
138
+ end
139
+
140
+ it "should copy region to coponent settings" do
141
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat', :region => 'us-west-1'})
142
+ config[:components][:api][:settings][:region].should eq("us-west-1")
143
+ config[:components][:base][:settings][:region].should eq("us-west-1")
144
+ end
145
+
146
+ it "should copy application, environment, component to component inputs" do
147
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'})
148
+ config[:components][:api][:inputs][:application].should eq("myApp")
149
+ config[:components][:api][:inputs][:component].should eq("api")
150
+ config[:components][:api][:inputs][:environment].should eq("uat")
151
+ config[:components][:base][:inputs][:application].should eq("myApp")
152
+ config[:components][:base][:inputs][:component].should eq("base")
153
+ config[:components][:base][:inputs][:environment].should eq("uat")
154
+ end
155
+
156
+ it "should copy region to coponent inputs" do
157
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat', :region => 'us-west-1'})
158
+ config[:components][:api][:inputs][:region].should eq("us-west-1")
159
+ config[:components][:base][:inputs][:region].should eq("us-west-1")
160
+ end
161
+
162
+ it "config_dir option should be copied to component context" do
163
+ config_dir = File.dirname(@config_file)
164
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'})
165
+ config[:components][:api][:config_dir].should eq(config_dir)
166
+ config[:components][:base][:config_dir].should eq(config_dir)
167
+ config[:components][:'front-end'][:config_dir].should eq(config_dir)
168
+ end
169
+
170
+ it "component's settings should be merged to common settings" do
171
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'uat'})
172
+ config[:components][:api][:inputs][:'timeout'].should eq(90)
173
+ config[:components][:api][:inputs][:'require-basic-auth'].should eq(true)
174
+ config[:components][:api][:inputs][:'mail-server'].should eq('http://api.abc.com')
175
+ end
176
+
177
+
178
+ it "environment's settings should be merged to component settings" do
179
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev'})
180
+ config[:components][:api][:inputs][:'timeout'].should eq(60)
181
+ config[:components][:api][:inputs][:'require-basic-auth'].should eq(true)
182
+ config[:components][:api][:inputs][:'mail-server'].should eq('http://dev.abc.com')
183
+ end
184
+
185
+ it "should merge environment's components to component settings" do
186
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'production'})
187
+ config[:components][:'front-end'][:inputs][:'cname'].should eq('prod-front-end.myserver.com')
188
+ config[:components][:api][:inputs][:'cname'].should eq('myserver.com')
189
+ end
190
+
191
+ it "environment variables without prefix 'cfdeploy_settings_' should not be merged to components settings" do
192
+ ENV['timeout'] = "180"
193
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev'})
194
+ config[:components][:api][:inputs][:'timeout'].should eq(60)
195
+ end
196
+
197
+ it "should merge environment variables should be merged to components settings" do
198
+ ENV['cfdeploy_inputs_timeout'] = "180"
199
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev'})
200
+ config[:components][:api][:inputs][:'timeout'].should eq("180")
201
+ end
202
+
203
+ it "cli settings should be merged to components settings" do
204
+ ENV['cfdeploy_settings_timeout'] = "180"
205
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file, :environment => 'dev',:cli_overrides => {:settings => {:timeout => 45}}})
206
+ config[:components][:api][:settings][:'timeout'].should eq(45)
207
+ end
208
+
209
+ it "should set cloudFormation parameter names into each component" do
210
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
211
+ config[:components][:base][:defined_parameters].should eq({})
212
+ config[:components][:api][:defined_parameters].should eq({:'require-basic-auth' => {:Default => "true"}})
213
+ config[:components][:'front-end'][:defined_parameters].should eq({:'require-basic-auth' => {}, :'mail-server' => {}})
214
+ end
215
+
216
+ it "should set cloudFormation output names into each component" do
217
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
218
+ config[:components][:base][:defined_outputs].should eq({:'vpc-id' => {}, :'AutoScalingGroupName'=>{}, :'public-subnet-id'=>{}})
219
+ config[:components][:api][:defined_outputs].should eq({})
220
+ config[:components][:'front-end'][:defined_outputs].should eq({:'AutoScalingGroupName'=>{}, :'elb-cname' => {}})
221
+ end
222
+
223
+ it "should remove common settings in order not to confuse us" do
224
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
225
+ config[:settings].should be_nil
226
+ end
227
+
228
+ it "should set default elb-name-output for cname-swap strategy" do
229
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
230
+ config[:components][:'front-end'][:settings][:'elb-name-output'].should eq('ELBName')
231
+ end
232
+
233
+ it "should set default auto-scaling-group-name-output for cname-swap strategy" do
234
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
235
+ config[:components][:api][:settings][:'auto-scaling-group-name-output'].should eq([ CfDeployer::Defaults::AutoScalingGroupName ])
236
+ end
237
+
238
+ it "should set auto-scaling-group-name-output to default if auto-scaling-group-name exists in output for create-or-update strategy" do
239
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
240
+ config[:components][:base][:settings][:'auto-scaling-group-name-output'].should eq([ CfDeployer::Defaults::AutoScalingGroupName ])
241
+ end
242
+
243
+ it "should not set auto-scaling-group-name-output to default if auto-scaling-group-name does not exists in output for create-or-update strategy" do
244
+ base_json = <<-eos
245
+ {
246
+ "Outputs" : {
247
+ }
248
+ }
249
+ eos
250
+
251
+ File.open(@base_json, 'w') {|f| f.write(base_json) }
252
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
253
+ config[:components][:'base'][:settings][:'auto-scaling-group-name-output'].should be_nil
254
+ end
255
+
256
+ it "should set auto-scaling-group-name-output to default if auto-scaling-group-name exists in output for cname-swap strategy" do
257
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
258
+ config[:components][:'front-end'][:settings][:'auto-scaling-group-name-output'].should eq([ CfDeployer::Defaults::AutoScalingGroupName ])
259
+ end
260
+
261
+ it "should set raise-error-for-unused-inputs to default" do
262
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
263
+ config[:components][:'front-end'][:settings][:'raise-error-for-unused-inputs'].should eq(CfDeployer::Defaults::RaiseErrorForUnusedInputs)
264
+ end
265
+
266
+ it "should not set auto-scaling-group-name-output to default if auto-scaling-group-name does not exists in output for cname-swap strategy" do
267
+ front_end_json = <<-eos
268
+ {
269
+ "Outputs" : {
270
+ "elb-cname" : {}
271
+ }
272
+ }
273
+ eos
274
+
275
+ File.open(@front_end_json, 'w') {|f| f.write(front_end_json) }
276
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
277
+ config[:components][:'front-end'][:settings][:'auto-scaling-group-name-output'].should be_nil
278
+ end
279
+
280
+ it "should ERB the config file and provide the environment in the binding" do
281
+ config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file, :environment => 'DrWho')
282
+ config[:components][:base][:inputs][:'foobar'].should eq('DrWho.IsGreat')
283
+ end
284
+
285
+ it "should ERB the component JSON and make the parsed template available" do
286
+ config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file, :environment => 'DrWho')
287
+ CfDeployer::ConfigLoader.component_json('json-with-erb', config[:components][:'json-with-erb']).should include('DrWho')
288
+ end
289
+
290
+ it 'should set default keep-previous-stack to true' do
291
+ config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file)
292
+ config[:components][:api][:settings][:'keep-previous-stack'].should eq(CfDeployer::Defaults::KeepPreviousStack)
293
+ end
294
+
295
+ it 'should keep keep-previous-stack setting' do
296
+ config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file)
297
+ config[:components][:'front-end'][:settings][:'keep-previous-stack'].should be_false
298
+ end
299
+
300
+ context 'targets' do
301
+
302
+ it 'should use all components as targets if no targets are specified' do
303
+ config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file)
304
+ config[:targets].should eq(['base', 'api', 'front-end', 'very-simple', 'json-with-erb'])
305
+ end
306
+
307
+ it 'should keep targets if targets are specified' do
308
+ config = CfDeployer::ConfigLoader.new.load(:'config-file' => @config_file, :component => ['api', 'web'])
309
+ config[:targets].should eq(['api', 'web'])
310
+ end
311
+
312
+ end
313
+
314
+ context 'load CF template' do
315
+ it "should be able to load CF template even though inputs have referencing value not resolved" do
316
+ api_template = <<-eos
317
+ {
318
+ "Parameters" : {
319
+ "db" : "<%= config[:inputs][:db]%>",
320
+ "url" : "<%= config[:inputs][:url]%>"
321
+ },
322
+ "Outputs" : {}
323
+ }
324
+ eos
325
+ File.open(@api_json, 'w') {|f| f.write(api_template) }
326
+
327
+ base_template = <<-eos
328
+ {
329
+ "Outputs" : {
330
+ "elb-cname" : {}
331
+ }
332
+ }
333
+ eos
334
+ File.open(@base_json, 'w') {|f| f.write(base_template) }
335
+
336
+ config = <<-eos
337
+ application: myApp
338
+ components:
339
+ base:
340
+ api:
341
+ depends-on:
342
+ - base
343
+ inputs:
344
+ url: http://abc.com
345
+ db:
346
+ component: base
347
+ output-key: elb-cname
348
+ eos
349
+ File.open(@config_file, 'w') {|f| f.write(config) }
350
+
351
+ config = CfDeployer::ConfigLoader.new.load({:'config-file' => @config_file})
352
+ config[:components][:'api'][:defined_parameters].should eq({ db:'base::elb-cname', url:'http://abc.com'})
353
+ end
354
+
355
+ end
356
+ end