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