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