cf_deployer 1.2.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +29 -0
- data/ChangeLog.md +16 -0
- data/DETAILS.md +268 -0
- data/FAQ.md +61 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +51 -0
- data/LICENSE +22 -0
- data/QUICKSTART.md +96 -0
- data/README.md +36 -0
- data/Rakefile +32 -0
- data/bin/cf_deploy +10 -0
- data/cf_deployer.gemspec +23 -0
- data/lib/cf_deployer/application.rb +74 -0
- data/lib/cf_deployer/application_error.rb +4 -0
- data/lib/cf_deployer/aws_constants.rb +3 -0
- data/lib/cf_deployer/cli.rb +111 -0
- data/lib/cf_deployer/component.rb +103 -0
- data/lib/cf_deployer/config_loader.rb +189 -0
- data/lib/cf_deployer/config_validation.rb +138 -0
- data/lib/cf_deployer/defaults.rb +10 -0
- data/lib/cf_deployer/deployment_strategy/auto_scaling_group_swap.rb +102 -0
- data/lib/cf_deployer/deployment_strategy/base.rb +88 -0
- data/lib/cf_deployer/deployment_strategy/blue_green.rb +70 -0
- data/lib/cf_deployer/deployment_strategy/cname_swap.rb +108 -0
- data/lib/cf_deployer/deployment_strategy/create_or_update.rb +57 -0
- data/lib/cf_deployer/driver/auto_scaling_group.rb +86 -0
- data/lib/cf_deployer/driver/cloud_formation_driver.rb +85 -0
- data/lib/cf_deployer/driver/dry_run.rb +27 -0
- data/lib/cf_deployer/driver/elb_driver.rb +17 -0
- data/lib/cf_deployer/driver/instance.rb +29 -0
- data/lib/cf_deployer/driver/route53_driver.rb +79 -0
- data/lib/cf_deployer/driver/verisign_driver.rb +21 -0
- data/lib/cf_deployer/hook.rb +32 -0
- data/lib/cf_deployer/logger.rb +34 -0
- data/lib/cf_deployer/stack.rb +154 -0
- data/lib/cf_deployer/status_presenter.rb +195 -0
- data/lib/cf_deployer/version.rb +3 -0
- data/lib/cf_deployer.rb +97 -0
- data/spec/fakes/instance.rb +32 -0
- data/spec/fakes/route53_client.rb +23 -0
- data/spec/fakes/stack.rb +65 -0
- data/spec/functional/deploy_spec.rb +73 -0
- data/spec/functional/kill_inactive_spec.rb +57 -0
- data/spec/functional_spec_helper.rb +3 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/unit/application_spec.rb +191 -0
- data/spec/unit/component_spec.rb +142 -0
- data/spec/unit/config_loader_spec.rb +356 -0
- data/spec/unit/config_validation_spec.rb +480 -0
- data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +435 -0
- data/spec/unit/deployment_strategy/base_spec.rb +44 -0
- data/spec/unit/deployment_strategy/cname_swap_spec.rb +294 -0
- data/spec/unit/deployment_strategy/create_or_update_spec.rb +113 -0
- data/spec/unit/deployment_strategy/deployment_strategy_spec.rb +29 -0
- data/spec/unit/driver/auto_scaling_group_spec.rb +127 -0
- data/spec/unit/driver/cloud_formation_spec.rb +32 -0
- data/spec/unit/driver/elb_spec.rb +11 -0
- data/spec/unit/driver/instance_spec.rb +30 -0
- data/spec/unit/driver/route53_spec.rb +85 -0
- data/spec/unit/driver/verisign_spec.rb +18 -0
- data/spec/unit/hook_spec.rb +64 -0
- data/spec/unit/stack_spec.rb +150 -0
- data/spec/unit/status_presenter_spec.rb +108 -0
- metadata +197 -0
@@ -0,0 +1,294 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CfDeployer::DeploymentStrategy::CnameSwap do
|
4
|
+
|
5
|
+
let(:dns_driver) { double('route53') }
|
6
|
+
let(:elb_driver) { double('elb') }
|
7
|
+
let(:blue_asg_driver) { double('blue_asg_driver') }
|
8
|
+
let(:green_asg_driver) { double('green_asg_driver') }
|
9
|
+
let(:blue_stack) { Fakes::Stack.new(name: 'BLUE', outputs: {'web-elb-name' => 'BLUE-elb', 'AutoScalingGroupID' => 'blueASG'}, parameters: {:name => 'blue'}) }
|
10
|
+
let(:green_stack) { Fakes::Stack.new(name: 'GREEN', outputs: {'web-elb-name' => 'GREEN-elb', 'AutoScalingGroupID' => 'greenASG'}, parameters: {:name => 'green'}) }
|
11
|
+
|
12
|
+
before do
|
13
|
+
allow(Kernel).to receive(:sleep)
|
14
|
+
@context =
|
15
|
+
{
|
16
|
+
:'deployment-strategy' => 'cname-swap',
|
17
|
+
:dns_driver => dns_driver,
|
18
|
+
:elb_driver => elb_driver,
|
19
|
+
:settings => {
|
20
|
+
:'dns-fqdn' => 'test.foobar.com',
|
21
|
+
:'dns-zone' => 'foobar.com',
|
22
|
+
:'elb-name-output' => 'web-elb-name',
|
23
|
+
:'dns-driver' => CfDeployer::Defaults::DNSDriver
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
allow(CfDeployer::Stack).to receive(:new).with('myapp-dev-web-B', 'web', anything()) { blue_stack }
|
28
|
+
allow(CfDeployer::Stack).to receive(:new).with('myapp-dev-web-G', 'web', anything()) { green_stack }
|
29
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
30
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
31
|
+
allow(elb_driver).to receive(:find_dns_and_zone_id).with('BLUE-elb') { {:dns_name => 'blue-elb.aws.amazon.com', :canonical_hosted_zone_name_id => 'BLUE111'}}
|
32
|
+
allow(elb_driver).to receive(:find_dns_and_zone_id).with('GREEN-elb') { {:dns_name => 'green-elb.aws.amazon.com', :canonical_hosted_zone_name_id => 'GREEN111'}}
|
33
|
+
end
|
34
|
+
|
35
|
+
context "hooks" do
|
36
|
+
let(:before_destroy_hook) { double('before_destroy_hook') }
|
37
|
+
let(:after_create_hook) { double('after_create_hook') }
|
38
|
+
let(:after_swap_hook) { double('after_swap_hook') }
|
39
|
+
|
40
|
+
before :each do
|
41
|
+
allow(CfDeployer::Hook).to receive(:new).with(:'before-destroy', 'before-destroy'){ before_destroy_hook }
|
42
|
+
allow(CfDeployer::Hook).to receive(:new).with(:'after-create', 'after-create'){ after_create_hook }
|
43
|
+
allow(CfDeployer::Hook).to receive(:new).with(:'after-swap', 'after-swap'){ after_swap_hook }
|
44
|
+
allow(dns_driver).to receive(:set_alias_target)
|
45
|
+
@context[:'before-destroy'] = 'before-destroy'
|
46
|
+
@context[:'after-create'] = 'after-create'
|
47
|
+
@context[:'after-swap'] = 'after-swap'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should call hooks" do
|
51
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
52
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
53
|
+
expect(before_destroy_hook).to receive(:run).with(@context).twice
|
54
|
+
expect(after_create_hook).to receive(:run).with(@context)
|
55
|
+
expect(after_swap_hook).to receive(:run).with(@context)
|
56
|
+
cname_swap.deploy
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should call hooks when destroying green and blue stacks' do
|
60
|
+
@log = ''
|
61
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
62
|
+
allow(blue_stack).to receive(:delete)
|
63
|
+
allow(green_stack).to receive(:delete)
|
64
|
+
allow(before_destroy_hook).to receive(:run) do |arg|
|
65
|
+
@log += "#{arg[:parameters][:name]} deleted."
|
66
|
+
end
|
67
|
+
cname_swap.destroy
|
68
|
+
@log.should eq('green deleted.blue deleted.')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "deploy" do
|
73
|
+
|
74
|
+
it "deploys green when blue is active" do
|
75
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
76
|
+
expect(dns_driver).to receive(:set_alias_target).with('foobar.com', 'test.foobar.com', 'GREEN111', 'green-elb.aws.amazon.com')
|
77
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
78
|
+
cname_swap.deploy
|
79
|
+
|
80
|
+
expect(green_stack).to be_deployed
|
81
|
+
expect(blue_stack).to_not be_deployed
|
82
|
+
end
|
83
|
+
|
84
|
+
it "deletes blue after deploying green" do
|
85
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
86
|
+
allow(dns_driver).to receive(:set_alias_target).with('foobar.com', 'test.foobar.com', 'GREEN111', 'green-elb.aws.amazon.com')
|
87
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
88
|
+
cname_swap.deploy
|
89
|
+
|
90
|
+
expect(blue_stack).to be_deleted
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should not delete blue after deploying green if keep-previous-stack is specified" do
|
94
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
95
|
+
allow(dns_driver).to receive(:set_alias_target).with('foobar.com', 'test.foobar.com', 'GREEN111', 'green-elb.aws.amazon.com')
|
96
|
+
@context[:settings][:'keep-previous-stack'] = true
|
97
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
98
|
+
cname_swap.deploy
|
99
|
+
|
100
|
+
expect(blue_stack).to_not be_deleted
|
101
|
+
end
|
102
|
+
|
103
|
+
it "deploys blue when green is active" do
|
104
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'GREEN-elb.aws.amazon.com' }
|
105
|
+
expect(dns_driver).to receive(:set_alias_target).with('foobar.com', 'test.foobar.com', 'BLUE111', 'blue-elb.aws.amazon.com')
|
106
|
+
|
107
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
108
|
+
|
109
|
+
cname_swap.deploy
|
110
|
+
|
111
|
+
expect(blue_stack).to be_deployed
|
112
|
+
expect(green_stack).to_not be_deployed
|
113
|
+
end
|
114
|
+
|
115
|
+
it "deletes the inactive stack before deployment" do
|
116
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
117
|
+
allow(dns_driver).to receive(:set_alias_target).with('foobar.com', 'test.foobar.com', 'GREEN111', 'green-elb.aws.amazon.com')
|
118
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
119
|
+
cname_swap.deploy
|
120
|
+
|
121
|
+
expect(green_stack).to be_deleted
|
122
|
+
expect(green_stack).to be_deployed
|
123
|
+
end
|
124
|
+
|
125
|
+
it "does not delete the green-inactive stack before deployment if that stack does not exist" do
|
126
|
+
green_stack.die!
|
127
|
+
|
128
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
129
|
+
allow(dns_driver).to receive(:set_alias_target)
|
130
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
131
|
+
cname_swap.deploy
|
132
|
+
|
133
|
+
expect(green_stack).to_not be_deleted
|
134
|
+
end
|
135
|
+
|
136
|
+
it "does not delete the blue-inactive stack before deployment if that stack does not exist" do
|
137
|
+
blue_stack.die!
|
138
|
+
|
139
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'GREEN-elb.aws.amazon.com' }
|
140
|
+
allow(dns_driver).to receive(:set_alias_target)
|
141
|
+
|
142
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
143
|
+
cname_swap.deploy
|
144
|
+
|
145
|
+
expect(blue_stack).to_not be_deleted
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should warm up any auto scaling groups to previous colors levels' do
|
149
|
+
blue_stack.die!
|
150
|
+
green_stack.live!
|
151
|
+
|
152
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'GREEN-elb.aws.amazon.com' }
|
153
|
+
allow(dns_driver).to receive(:set_alias_target)
|
154
|
+
allow(green_asg_driver).to receive(:describe) { { desired: 3, min: 1, max: 5 } }
|
155
|
+
@context[:settings][:'auto-scaling-group-name-output'] = ['AutoScalingGroupID']
|
156
|
+
expect(blue_asg_driver).to receive(:warm_up).with 3
|
157
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
158
|
+
cname_swap.deploy
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should warm up any auto scaling groups to desired number when no previous color exists' do
|
162
|
+
blue_stack.die!
|
163
|
+
green_stack.die!
|
164
|
+
|
165
|
+
allow(dns_driver).to receive(:set_alias_target)
|
166
|
+
allow(blue_asg_driver).to receive(:describe) { { desired: 2, min: 1, max: 5 } }
|
167
|
+
@context[:settings][:'auto-scaling-group-name-output'] = ['AutoScalingGroupID']
|
168
|
+
expect(blue_asg_driver).to receive(:warm_up).with 2
|
169
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
170
|
+
cname_swap.deploy
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should not warm up if there are no auto scaling groups given' do
|
174
|
+
blue_stack.die!
|
175
|
+
green_stack.die!
|
176
|
+
|
177
|
+
allow(dns_driver).to receive(:set_alias_target)
|
178
|
+
allow(blue_asg_driver).to receive(:describe) { { desired: 2, min: 1, max: 5 } }
|
179
|
+
expect(blue_asg_driver).not_to receive(:warm_up)
|
180
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
181
|
+
cname_swap.deploy
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'exists?' do
|
186
|
+
it 'no, if green stack and blue stack do not exist' do
|
187
|
+
blue_stack.die!
|
188
|
+
green_stack.die!
|
189
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
190
|
+
cname_swap.exists?.should be_false
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'yes, if green stack exists and blue stack does not' do
|
194
|
+
blue_stack.die!
|
195
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
196
|
+
cname_swap.exists?.should be_true
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'yes, if blue stack exists and green stack does not' do
|
200
|
+
green_stack.die!
|
201
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
202
|
+
cname_swap.exists?.should be_true
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context '#destroy' do
|
207
|
+
it 'should destroy green and blue stacks' do
|
208
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
209
|
+
expect(blue_stack).to receive(:delete)
|
210
|
+
expect(green_stack).to receive(:delete)
|
211
|
+
cname_swap.destroy
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context 'dns_driver' do
|
216
|
+
it 'should use a different driver class if the dns-driver setting is used' do
|
217
|
+
my_context = @context.clone
|
218
|
+
my_context.delete :dns_driver
|
219
|
+
my_context[:settings][:'dns-driver'] = 'CfDeployer::Driver::Verisign'
|
220
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', my_context)
|
221
|
+
cname_swap.send(:dns_driver).class.to_s.should eq(my_context[:settings][:'dns-driver'])
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe '#kill_inactive' do
|
226
|
+
let(:cname_swap) { CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) }
|
227
|
+
|
228
|
+
context 'when blue stack is active' do
|
229
|
+
it 'should destroy the green stack' do
|
230
|
+
green_stack.live!
|
231
|
+
blue_stack.live!
|
232
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
233
|
+
expect(green_stack).to receive(:delete)
|
234
|
+
cname_swap.kill_inactive
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
context 'when green stack is active' do
|
239
|
+
it 'should destroy the blue stack' do
|
240
|
+
green_stack.live!
|
241
|
+
blue_stack.live!
|
242
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'GREEN-elb.aws.amazon.com' }
|
243
|
+
expect(blue_stack).to receive(:delete)
|
244
|
+
cname_swap.kill_inactive
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
context 'when green stack is active and blue stack does not exist' do
|
249
|
+
it 'should raise an error' do
|
250
|
+
green_stack.live!
|
251
|
+
blue_stack.die!
|
252
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'GREEN-elb.aws.amazon.com' }
|
253
|
+
expect(blue_stack).not_to receive(:delete)
|
254
|
+
expect { cname_swap.kill_inactive }.to raise_error CfDeployer::ApplicationError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#switch' do
|
260
|
+
let(:cname_swap) { CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context) }
|
261
|
+
context 'if no inactive version exists' do
|
262
|
+
it 'should raise an error' do
|
263
|
+
green_stack.die!
|
264
|
+
blue_stack.live!
|
265
|
+
expect { cname_swap.switch }.to raise_error 'There is only one color stack active, you cannot switch back to a non-existent version'
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
context 'if an inactive version exists' do
|
270
|
+
it 'should swap the cname to the inactive version' do
|
271
|
+
green_stack.live!
|
272
|
+
blue_stack.live!
|
273
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
274
|
+
expect(dns_driver).to receive(:set_alias_target).with('foobar.com', 'test.foobar.com', 'GREEN111', 'green-elb.aws.amazon.com')
|
275
|
+
cname_swap.switch
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context '#output_value' do
|
281
|
+
|
282
|
+
it 'should get stack output if active stack exists' do
|
283
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ 'BLUE-elb.aws.amazon.com' }
|
284
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
285
|
+
cname_swap.output_value("AutoScalingGroupID").should eq("blueASG")
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'should get the information where the value comes from if the active stack does not exist' do
|
289
|
+
allow(dns_driver).to receive(:find_alias_target).with('foobar.com', 'test.foobar.com'){ '' }
|
290
|
+
cname_swap = CfDeployer::DeploymentStrategy.create('myapp', 'dev', 'web', @context)
|
291
|
+
cname_swap.output_value(:a_key).should eq("The value will be referenced from the output a_key of undeployed component web")
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'CreateOrUpdate Strategy' do
|
4
|
+
before :each do
|
5
|
+
@after_create_hook = double('after_create_hook')
|
6
|
+
@before_destroy_hook = double('before_destroy_hook')
|
7
|
+
allow(CfDeployer::Hook).to receive(:new).with(:'after-create', 'after-create'){ @after_create_hook }
|
8
|
+
allow(CfDeployer::Hook).to receive(:new).with(:'before-destroy', 'before-destroy') { @before_destroy_hook }
|
9
|
+
@context = {
|
10
|
+
:application => 'myApp',
|
11
|
+
:environment => 'uat',
|
12
|
+
:components =>
|
13
|
+
{ :base => {
|
14
|
+
:settings => {},
|
15
|
+
:'deployment-strategy' => 'create-or-update',
|
16
|
+
:'after-create' => 'after-create',
|
17
|
+
:'before-destroy' => 'before-destroy'
|
18
|
+
}
|
19
|
+
}
|
20
|
+
}
|
21
|
+
@stack = double('stack')
|
22
|
+
@create_or_update = CfDeployer::DeploymentStrategy.create(@context[:application], @context[:environment], 'base', @context[:components][:base])
|
23
|
+
allow(CfDeployer::Stack).to receive(:new).with('myApp-uat-base','base', @context[:components][:base]){ @stack}
|
24
|
+
allow(@stack).to receive(:parameters){ {'vpc' => 'myvpc'}}
|
25
|
+
allow(@stack).to receive(:outputs){ {'ELBName' => 'myelb'}}
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should deploy stack' do
|
29
|
+
hook_context = nil
|
30
|
+
expect(@after_create_hook).to receive(:run) do |given_context|
|
31
|
+
hook_context = given_context
|
32
|
+
end
|
33
|
+
expect(@stack).to receive(:deploy)
|
34
|
+
@create_or_update.deploy
|
35
|
+
expect(hook_context[:parameters]).to eq( {'vpc' => 'myvpc'} )
|
36
|
+
expect(hook_context[:outputs]).to eq( {'ELBName' => 'myelb'} )
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'warm up auto scaling group' do
|
41
|
+
|
42
|
+
let(:asg_driver) { double('asg_driver') }
|
43
|
+
|
44
|
+
it 'should warm up the stack if any auto-scaling groups are given' do
|
45
|
+
context = @context[:components][:base]
|
46
|
+
context[:settings] = {}
|
47
|
+
context[:settings][:'auto-scaling-group-name-output'] = ['AutoScalingGroupID']
|
48
|
+
allow(@stack).to receive(:output).with('AutoScalingGroupID') { 'asg_name' }
|
49
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('asg_name') { asg_driver }
|
50
|
+
allow(asg_driver).to receive(:describe) { {desired:2, min:1, max:3} }
|
51
|
+
allow(@after_create_hook).to receive(:run).with(anything)
|
52
|
+
allow(@stack).to receive(:deploy)
|
53
|
+
create_or_update = CfDeployer::DeploymentStrategy.create(@context[:application], @context[:environment], 'base', context)
|
54
|
+
expect(asg_driver).to receive(:warm_up).with 2
|
55
|
+
create_or_update.deploy
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should tell if stack exists' do
|
60
|
+
expect(@stack).to receive(:exists?){true}
|
61
|
+
@create_or_update.exists?.should eq(true)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should get stack output' do
|
65
|
+
allow(@stack).to receive(:exists?){true}
|
66
|
+
expect(@stack).to receive(:output).with(:a_key){ "output_value" }
|
67
|
+
@create_or_update.output_value(:a_key).should eq("output_value")
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should get the information where the value comes from if the stack does not exist' do
|
71
|
+
allow(@stack).to receive(:exists?){false}
|
72
|
+
expect(@stack).not_to receive(:output).with(anything)
|
73
|
+
@create_or_update.output_value(:a_key).should eq("The value will be referenced from the output a_key of undeployed component base")
|
74
|
+
end
|
75
|
+
|
76
|
+
context '#destroy' do
|
77
|
+
it 'should destroy stack' do
|
78
|
+
allow(@stack).to receive(:exists?){ true}
|
79
|
+
allow(@stack).to receive(:parameters) { {}}
|
80
|
+
allow(@stack).to receive(:outputs) {{}}
|
81
|
+
expect(@before_destroy_hook).to receive(:run).with(anything)
|
82
|
+
expect(@stack).to receive(:delete)
|
83
|
+
@create_or_update.destroy
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '#kill_inactive' do
|
88
|
+
it 'should raise an error' do
|
89
|
+
expect { @create_or_update.kill_inactive }.to raise_error CfDeployer::ApplicationError
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context '#switch' do
|
94
|
+
it 'should raise an error' do
|
95
|
+
expect{ @create_or_update.switch }.to raise_error 'There is no inactive version to switch to for Create or Update Deployments. Redeploy the version you want'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context '#status' do
|
100
|
+
before :each do
|
101
|
+
allow(@stack).to receive(:status) { 'deployed' }
|
102
|
+
allow(@stack).to receive(:name) { 'base-uat' }
|
103
|
+
allow(@stack).to receive(:exists?) { true }
|
104
|
+
end
|
105
|
+
it 'should get status from stack' do
|
106
|
+
@create_or_update.status.should eq({ 'base-uat' => {status: 'deployed'}})
|
107
|
+
end
|
108
|
+
it 'should get status from stack including resource info' do
|
109
|
+
allow(@stack).to receive(:resource_statuses) { 'resource1' }
|
110
|
+
@create_or_update.status(true).should eq({ 'base-uat' => {status: 'deployed', resources: 'resource1'}})
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Deployment Strategy' do
|
4
|
+
before :each do
|
5
|
+
@context = {
|
6
|
+
application: 'myApp',
|
7
|
+
environment: 'uat',
|
8
|
+
components:
|
9
|
+
{ base: {:'deployment-strategy' => 'create-or-update'},
|
10
|
+
db: {:'deployment-strategy' => 'auto-scaling-group-swap'},
|
11
|
+
web: { :'deployment-strategy' => 'cname-swap' }
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should create Create-Or-Update strategy" do
|
17
|
+
expect(CfDeployer::DeploymentStrategy::CreateOrUpdate).to receive(:new)
|
18
|
+
CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'base', @context[:components][:base])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should create Auto-Scaling-Group-Swap strategy" do
|
22
|
+
expect(CfDeployer::DeploymentStrategy::AutoScalingGroupSwap).to receive(:new)
|
23
|
+
CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'db', @context[:components][:db])
|
24
|
+
end
|
25
|
+
it "should create Cname-Swap strategy" do
|
26
|
+
expect(CfDeployer::DeploymentStrategy::CnameSwap).to receive(:new)
|
27
|
+
CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', @context[:components][:web])
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Autoscaling group driver' do
|
4
|
+
let(:group) { double('group', :desired_capacity => 2, :min_size => 1, :max_size => 4)}
|
5
|
+
let(:scaling) { double('scaling', :groups => { 'myAsg' => group}) }
|
6
|
+
let(:ec2_instance1) { double('ec2_instance1') }
|
7
|
+
let(:ec2_instance2) { double('ec2_instance2') }
|
8
|
+
let(:ec2_instance3) { double('ec2_instance3') }
|
9
|
+
let(:ec2_instance4) { double('ec2_instance4') }
|
10
|
+
let(:instance1) { double('instance1', :health_status => 'HEALTHY', :ec2_instance => ec2_instance1)}
|
11
|
+
let(:instance2) { double('instance2', :health_status => 'HEALTHY', :ec2_instance => ec2_instance2)}
|
12
|
+
let(:instance3) { double('instance3', :health_status => 'HEALTHY', :ec2_instance => ec2_instance3)}
|
13
|
+
let(:instance4) { double('instance4', :health_status => 'HEALTHY', :ec2_instance => ec2_instance4)}
|
14
|
+
let(:load_balancer) { double('load_balancer', :instances => [instance1, instance2, instance3, instance4]) }
|
15
|
+
|
16
|
+
before :each do
|
17
|
+
allow(AWS::AutoScaling).to receive(:new) { scaling }
|
18
|
+
allow(group).to receive(:load_balancers) { [] }
|
19
|
+
allow(group).to receive(:auto_scaling_instances) { [] }
|
20
|
+
allow(group).to receive(:ec2_instances) { [] }
|
21
|
+
@driver = CfDeployer::Driver::AutoScalingGroup.new('myAsg', 1)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should describe group' do
|
25
|
+
@driver.describe.should eq({ min: 1, max: 4, desired: 2})
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#warm_up' do
|
29
|
+
it 'should warm up the group to the desired size' do
|
30
|
+
expect(group).to receive(:auto_scaling_instances){[instance1, instance2]}
|
31
|
+
expect(group).to receive(:set_desired_capacity).with(2)
|
32
|
+
@driver.warm_up 2
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should wait for the warm up of the group even if desired is the same as the minimum' do
|
36
|
+
expect(group).to receive(:auto_scaling_instances){[instance2]}
|
37
|
+
expect(group).to receive(:set_desired_capacity).with(1)
|
38
|
+
@driver.warm_up 1
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should ignore warming up if desired number is less than min size of the group' do
|
42
|
+
expect(group).not_to receive(:set_desired_capacity)
|
43
|
+
@driver.warm_up 0
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should warm up to maximum if desired number is greater than maximum size of group' do
|
47
|
+
expect(group).to receive(:auto_scaling_instances){[instance1, instance2, instance3, instance4]}
|
48
|
+
expect(group).to receive(:set_desired_capacity).with(4)
|
49
|
+
@driver.warm_up 5
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#healthy_instance_count' do
|
54
|
+
it 'should respond with the number of instances that are HEALTHY' do
|
55
|
+
instance5 = double('instance1', :health_status => 'UNHEALTHY')
|
56
|
+
allow(group).to receive(:auto_scaling_instances){[instance1, instance2, instance3, instance4, instance5]}
|
57
|
+
expect(@driver.send(:healthy_instance_count)).to eql 4
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when an elb is associated with the auto scaling group' do
|
61
|
+
it 'should not include instances that are HEALTHY but not associated with the elb' do
|
62
|
+
instance_collection = double('instance_collection', :health => [{:instance => ec2_instance1, :state => 'InService'}])
|
63
|
+
load_balancer = double('load_balancer', :instances => instance_collection)
|
64
|
+
allow(group).to receive(:load_balancers) { [load_balancer] }
|
65
|
+
allow(group).to receive(:auto_scaling_instances) { [instance1, instance2] }
|
66
|
+
|
67
|
+
expect(@driver.send(:healthy_instance_count)).to eql 1
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should only include instances registered with an elb that are InService' do
|
71
|
+
allow(group).to receive(:auto_scaling_instances) { [instance1, instance2, instance3] }
|
72
|
+
instance_collection = double('instance_collection', :health => [{:instance => ec2_instance1, :state => 'InService'},
|
73
|
+
{:instance => ec2_instance2, :state => 'OutOfService'},
|
74
|
+
{:instance => ec2_instance3, :state => 'OutOfService'}])
|
75
|
+
load_balancer = double('load_balancer', :instances => instance_collection)
|
76
|
+
allow(group).to receive(:load_balancers) { [load_balancer] }
|
77
|
+
|
78
|
+
expect(@driver.send(:healthy_instance_count)).to eql 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when there are multiple elbs for an auto scaling group' do
|
83
|
+
it 'should not include instances that are not registered with all load balancers' do
|
84
|
+
instance_collection1 = double('instance_collection1', :health => [{:instance => ec2_instance1, :state => 'InService'}])
|
85
|
+
instance_collection2 = double('instance_collection2', :health => [])
|
86
|
+
load_balancer1 = double('load_balancer1', :instances => instance_collection1)
|
87
|
+
load_balancer2 = double('load_balancer2', :instances => instance_collection2)
|
88
|
+
allow(group).to receive(:load_balancers) { [load_balancer1, load_balancer2] }
|
89
|
+
allow(group).to receive(:auto_scaling_instances) { [instance1] }
|
90
|
+
|
91
|
+
expect(@driver.send(:healthy_instance_count)).to eql 0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '#cool_down' do
|
97
|
+
it 'should cool down group' do
|
98
|
+
expect(group).to receive(:update).with({min_size: 0, max_size: 0})
|
99
|
+
expect(group).to receive(:set_desired_capacity).with(0)
|
100
|
+
@driver.cool_down
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '#warm_up_cooled_group' do
|
105
|
+
it 'should set min, max, and desired from a hash' do
|
106
|
+
hash = {:max => 5, :min => 2, :desired => 3}
|
107
|
+
allow(group).to receive(:auto_scaling_instances){[instance1, instance2, instance3]}
|
108
|
+
expect(group).to receive(:update).with({:min_size => 2, :max_size => 5})
|
109
|
+
expect(group).to receive(:set_desired_capacity).with(3)
|
110
|
+
@driver.warm_up_cooled_group hash
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#instance_statuses' do
|
115
|
+
it 'should get the status for any EC2 instances' do
|
116
|
+
aws_instance = double AWS::EC2::Instance
|
117
|
+
expect(aws_instance).to receive(:id) { 'i-abcd1234' }
|
118
|
+
allow(@driver).to receive(:ec2_instances) { [ aws_instance ] }
|
119
|
+
|
120
|
+
returned_status = { :status => :some_status }
|
121
|
+
cfd_instance = double CfDeployer::Driver::Instance
|
122
|
+
expect(CfDeployer::Driver::Instance).to receive(:new).with(aws_instance) { cfd_instance }
|
123
|
+
expect(cfd_instance).to receive(:status) { returned_status }
|
124
|
+
expect(@driver.instance_statuses).to eq( { 'i-abcd1234' => returned_status } )
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe 'CloudFormation' do
|
3
|
+
let(:outputs) { [output1, output2] }
|
4
|
+
let(:output1) { double('output1', :key => 'key1', :value => 'value1')}
|
5
|
+
let(:output2) { double('output2', :key => 'key2', :value => 'value2')}
|
6
|
+
let(:parameters) { double('parameters')}
|
7
|
+
let(:stack) { double('stack', :outputs => outputs, :parameters => parameters) }
|
8
|
+
let(:cloudFormation) {
|
9
|
+
double('cloudFormation',
|
10
|
+
:stacks =>
|
11
|
+
{'testStack' => stack
|
12
|
+
})
|
13
|
+
}
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
allow(AWS::CloudFormation).to receive(:new) { cloudFormation }
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should get outputs of stack' do
|
20
|
+
CfDeployer::Driver::CloudFormation.new('testStack').outputs.should eq({'key1' => 'value1', 'key2' => 'value2'})
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should get parameters of stack' do
|
24
|
+
CfDeployer::Driver::CloudFormation.new('testStack').parameters.should eq(parameters)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'resource_statuses' do
|
28
|
+
it 'should be tested' do
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CfDeployer::Driver::Elb do
|
4
|
+
it 'should get dns name and hosted zone id' do
|
5
|
+
elb = double('elb', :dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id')
|
6
|
+
aws = double('aws', :load_balancers => {'myelb' => elb})
|
7
|
+
elb_name = 'myelb'
|
8
|
+
expect(AWS::ELB).to receive(:new){aws}
|
9
|
+
CfDeployer::Driver::Elb.new.find_dns_and_zone_id(elb_name).should eq({:dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id'})
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CfDeployer::Driver::Instance do
|
4
|
+
context '#status' do
|
5
|
+
it 'should build the right hash of instance info' do
|
6
|
+
expected = { :status => :pending,
|
7
|
+
:public_ip_address => '4.3.2.1',
|
8
|
+
:private_ip_address => '192.168.1.10',
|
9
|
+
:image_id => 'ami-testami',
|
10
|
+
:key_pair => 'test_pair'
|
11
|
+
}
|
12
|
+
|
13
|
+
|
14
|
+
aws = double AWS::EC2
|
15
|
+
expect(AWS::EC2).to receive(:new) { aws }
|
16
|
+
|
17
|
+
instance_collection = double AWS::EC2::InstanceCollection
|
18
|
+
expect(aws).to receive(:instances) { instance_collection }
|
19
|
+
|
20
|
+
instance = Fakes::Instance.new expected.merge( { :id => 'i-wxyz1234' } )
|
21
|
+
expect(instance_collection).to receive(:[]).with(instance.id) { instance }
|
22
|
+
|
23
|
+
instance_status = CfDeployer::Driver::Instance.new('i-wxyz1234').status
|
24
|
+
|
25
|
+
expected.each do |key, val|
|
26
|
+
expect(instance_status[key]).to eq(val)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|