cf_deployer 1.3.8 → 1.3.9
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 +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +18 -0
- data/ChangeLog.md +4 -0
- data/README.md +5 -0
- data/lib/cf_deployer/deployment_strategy/auto_scaling_group_swap.rb +10 -21
- data/lib/cf_deployer/deployment_strategy/base.rb +20 -12
- data/lib/cf_deployer/deployment_strategy/blue_green.rb +0 -2
- data/lib/cf_deployer/driver/auto_scaling_group.rb +1 -3
- data/lib/cf_deployer/stack.rb +5 -1
- data/lib/cf_deployer/version.rb +1 -1
- data/spec/fakes/stack.rb +4 -0
- data/spec/functional/deploy_spec.rb +2 -0
- data/spec/functional/kill_inactive_spec.rb +21 -5
- data/spec/functional_spec_helper.rb +7 -0
- data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +95 -39
- data/spec/unit/deployment_strategy/base_spec.rb +74 -0
- data/spec/unit/deployment_strategy/create_or_update_spec.rb +1 -1
- data/spec/unit/driver/cloud_formation_spec.rb +31 -3
- data/spec/unit/stack_spec.rb +24 -11
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d5da90ce16ae1b8de132cba6d1359e3a643d4c9
|
4
|
+
data.tar.gz: 8d34ac5950b1ce737da92f384c86dc72a0b8128c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05f26ca292c10038313d724d7a24e5b875179aa41f986fa0b757bd7ea16038cd65fa585950bf18282bde4db042dede93dbc7e2d5fe1b6c1f3749bdceab967f12
|
7
|
+
data.tar.gz: 843e008b514878929b1a1d2802ae2b42b5fe638acc9898f7ca6bdb0d11af0c52d8db541fad2c55f331988dac1ac2b57cd25ca866ccd322607ea3a284ed885635
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.9.3
|
4
|
+
- 2.0.0
|
5
|
+
- 2.1.7
|
6
|
+
- 2.2.3
|
7
|
+
- ruby-head
|
8
|
+
- jruby-19mode
|
9
|
+
- jruby-9.0.1.0
|
10
|
+
- rbx-2
|
11
|
+
matrix:
|
12
|
+
allow_failures:
|
13
|
+
- rvm: ruby-head
|
14
|
+
- rvm: rbx-2
|
15
|
+
- rvm: jruby-9.0.1.0
|
16
|
+
fast_finish: true
|
17
|
+
script:
|
18
|
+
- bundle exec rake
|
data/ChangeLog.md
CHANGED
data/README.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
[](https://badge.fury.io/rb/cf_deployer)
|
2
|
+
[](https://travis-ci.org/manheim/cf_deployer)
|
3
|
+
[](https://codeclimate.com/github/manheim/cf_deployer)
|
4
|
+
[](LICENSE)
|
5
|
+
|
1
6
|
##### [README](README.md) - [QUICKSTART](QUICKSTART.md) - [DETAILS](DETAILS.md) - [FAQ](FAQ.md)
|
2
7
|
|
3
8
|
CFDeployer
|
@@ -2,7 +2,6 @@ module CfDeployer
|
|
2
2
|
module DeploymentStrategy
|
3
3
|
class AutoScalingGroupSwap < BlueGreen
|
4
4
|
|
5
|
-
|
6
5
|
def deploy
|
7
6
|
check_blue_green_not_both_active 'Deployment'
|
8
7
|
Log.info "Found active stack #{active_stack.name}" if active_stack
|
@@ -15,7 +14,6 @@ module CfDeployer
|
|
15
14
|
Log.info "#{component_name} deployed successfully"
|
16
15
|
end
|
17
16
|
|
18
|
-
|
19
17
|
def kill_inactive
|
20
18
|
check_blue_green_not_both_active 'Kill Inactive'
|
21
19
|
raise ApplicationError.new('Only one color stack exists, cannot kill a non-existant version!') unless both_stacks_exist?
|
@@ -25,15 +23,13 @@ module CfDeployer
|
|
25
23
|
def switch
|
26
24
|
check_blue_green_not_both_active 'Switch'
|
27
25
|
raise ApplicationError.new('Only one color stack exists, cannot switch to a non-existent version!') unless both_stacks_exist?
|
28
|
-
|
29
|
-
swap_group warm_up_cooled
|
26
|
+
swap_group true
|
30
27
|
end
|
31
28
|
|
32
29
|
private
|
33
30
|
|
34
|
-
|
35
31
|
def check_blue_green_not_both_active action
|
36
|
-
active_stacks =
|
32
|
+
active_stacks = get_active_asgs(active_stack) + get_active_asgs(inactive_stack)
|
37
33
|
raise BothStacksActiveError.new("Found both auto-scaling-groups, #{active_stacks}, in green and blue stacks are active. #{action} aborted!") if both_stacks_active?
|
38
34
|
end
|
39
35
|
|
@@ -56,32 +52,25 @@ module CfDeployer
|
|
56
52
|
active_stack && stack_active?(inactive_stack)
|
57
53
|
end
|
58
54
|
|
59
|
-
|
60
55
|
def warm_up_cooled_stack
|
61
|
-
|
62
|
-
min_max_desired = asg_driver(id).describe
|
63
|
-
asg_driver(group_ids(inactive_stack)[index]).warm_up_cooled_group min_max_desired
|
64
|
-
end
|
56
|
+
warm_up_stack(inactive_stack, active_stack, true)
|
65
57
|
end
|
66
58
|
|
67
59
|
def cool_down_active_stack
|
68
|
-
|
60
|
+
get_active_asgs(active_stack).each do |id|
|
69
61
|
asg_driver(id).cool_down
|
70
62
|
end
|
71
|
-
|
72
63
|
end
|
73
64
|
|
74
65
|
def stack_active?(stack)
|
75
|
-
|
76
|
-
get_active_asg(stack).any?
|
66
|
+
stack.exists? && get_active_asgs(stack).any?
|
77
67
|
end
|
78
68
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
group_ids(stack).select do |id|
|
69
|
+
def get_active_asgs stack
|
70
|
+
return [] unless stack && stack.exists? && stack.resource_statuses[:asg_instances]
|
71
|
+
stack.resource_statuses[:asg_instances].keys.select do |id|
|
83
72
|
result = asg_driver(id).describe
|
84
|
-
result[:min] > 0
|
73
|
+
result[:min] > 0 && result[:max] > 0 && result[:desired] > 0
|
85
74
|
end
|
86
75
|
end
|
87
76
|
|
@@ -89,7 +78,7 @@ module CfDeployer
|
|
89
78
|
@auto_scaling_group_drivers[name] ||= CfDeployer::Driver::AutoScalingGroup.new name
|
90
79
|
end
|
91
80
|
|
92
|
-
def
|
81
|
+
def asg_name_outputs
|
93
82
|
@context[:settings][:'auto-scaling-group-name-output']
|
94
83
|
end
|
95
84
|
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
module CfDeployer
|
3
2
|
module DeploymentStrategy
|
4
3
|
|
@@ -74,29 +73,38 @@ module CfDeployer
|
|
74
73
|
end
|
75
74
|
|
76
75
|
def warm_up_inactive_stack
|
77
|
-
|
78
|
-
asg_driver(id).warm_up get_desired(id, index)
|
79
|
-
end
|
76
|
+
warm_up_stack(inactive_stack, active_stack)
|
80
77
|
end
|
81
78
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
79
|
+
def warm_up_stack stack, previous_stack = nil, adjust_min_max = false
|
80
|
+
previous_ids = previous_stack ? template_asg_name_to_ids(previous_stack) : {}
|
81
|
+
template_asg_name_to_ids(stack).each do |name, id|
|
82
|
+
driver = asg_driver(id)
|
83
|
+
description = asg_driver(previous_ids[name] || id).describe
|
84
|
+
if adjust_min_max
|
85
|
+
driver.warm_up_cooled_group(description)
|
86
|
+
else
|
87
|
+
driver.warm_up(description[:desired])
|
88
|
+
end
|
89
|
+
end
|
85
90
|
end
|
86
91
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
92
|
+
def template_asg_name_to_ids(stack)
|
93
|
+
{}.tap do |result|
|
94
|
+
(asg_name_outputs || []).each do |name|
|
95
|
+
id = stack.find_output(name)
|
96
|
+
result[name] = id if id
|
97
|
+
end
|
98
|
+
end
|
90
99
|
end
|
91
100
|
|
92
101
|
def asg_driver name
|
93
102
|
@auto_scaling_group_drivers[name] ||= CfDeployer::Driver::AutoScalingGroup.new name
|
94
103
|
end
|
95
104
|
|
96
|
-
def
|
105
|
+
def asg_name_outputs
|
97
106
|
@context[:settings][:'auto-scaling-group-name-output']
|
98
107
|
end
|
99
|
-
|
100
108
|
end
|
101
109
|
end
|
102
110
|
end
|
@@ -29,7 +29,7 @@ module CfDeployer
|
|
29
29
|
|
30
30
|
def warm_up_cooled_group options
|
31
31
|
CfDeployer::Driver::DryRun.guard 'Skipping update of ASG min & max instance count' do
|
32
|
-
aws_group.update :min_size =>
|
32
|
+
aws_group.update :min_size => options[:min], :max_size => options[:max]
|
33
33
|
end
|
34
34
|
warm_up options[:desired]
|
35
35
|
end
|
@@ -81,11 +81,9 @@ module CfDeployer
|
|
81
81
|
}
|
82
82
|
end
|
83
83
|
|
84
|
-
|
85
84
|
def aws_group
|
86
85
|
@my_group ||= AWS::AutoScaling.new.groups[group_name]
|
87
86
|
end
|
88
|
-
|
89
87
|
end
|
90
88
|
end
|
91
89
|
end
|
data/lib/cf_deployer/stack.rb
CHANGED
@@ -38,8 +38,12 @@ module CfDeployer
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def output key
|
41
|
+
find_output(key) || (raise ApplicationError.new("'#{key}' is empty from stack #{name} output"))
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_output key
|
41
45
|
begin
|
42
|
-
@cf_driver.query_output(key)
|
46
|
+
@cf_driver.query_output(key)
|
43
47
|
rescue AWS::CloudFormation::Errors::ValidationError => e
|
44
48
|
raise ResourceNotInReadyState.new("Resource stack not in ready state yet, perhaps you should provision it first?")
|
45
49
|
end
|
data/lib/cf_deployer/version.rb
CHANGED
data/spec/fakes/stack.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
module Fakes
|
2
2
|
class Stack
|
3
3
|
attr_reader :outputs, :parameters
|
4
|
+
attr_accessor :resource_statuses
|
5
|
+
|
4
6
|
def initialize(options)
|
5
7
|
@exists = options[:exists?].nil? ? true : options[:exists?]
|
6
8
|
@outputs = options[:outputs] || {}
|
7
9
|
@parameters = options[:parameters] || {}
|
8
10
|
@name = options[:name] || 'Unnamed'
|
9
11
|
@status = options[:status] || :ready
|
12
|
+
@resource_statuses = {}
|
10
13
|
end
|
11
14
|
|
12
15
|
def inspect
|
@@ -18,6 +21,7 @@ module Fakes
|
|
18
21
|
raise 'Stack is dead' unless @exists
|
19
22
|
@outputs[key]
|
20
23
|
end
|
24
|
+
alias_method :find_output, :output
|
21
25
|
|
22
26
|
def set_output(key, value)
|
23
27
|
@outputs[key] = value
|
@@ -61,6 +61,8 @@ describe 'Deploy' do
|
|
61
61
|
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-asg-swap-dev-base', 'base', anything) { base_stack }
|
62
62
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
63
63
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
64
|
+
allow(blue_stack).to receive(:resource_statuses) { asg_ids 'blueASG' }
|
65
|
+
allow(green_stack).to receive(:resource_statuses) { asg_ids 'greenASG' }
|
64
66
|
allow(blue_asg_driver).to receive(:describe) {{desired: 0, min: 0, max: 0}}
|
65
67
|
allow(green_asg_driver).to receive(:describe) {{desired: 2, min: 1, max: 5}}
|
66
68
|
expect(blue_asg_driver).to receive(:warm_up).with(2)
|
@@ -35,23 +35,39 @@ describe 'Kill Inactive' do
|
|
35
35
|
end
|
36
36
|
|
37
37
|
context 'asg-swap' do
|
38
|
-
let(:blue_stack)
|
38
|
+
let(:blue_stack) { Fakes::Stack.new(name: 'BLUE', outputs: {'ELBName' => 'BLUE-elb', 'AutoScalingGroupName' => 'templateBlueASG'}, parameters: {:name => 'blue'}) }
|
39
39
|
let(:green_stack) { Fakes::Stack.new(name: 'GREEN', outputs: {'ELBName' => 'GREEN-elb', 'AutoScalingGroupName' => 'greenASG'}, parameters: {:name => 'green'}) }
|
40
|
-
let(:
|
40
|
+
let(:template_blue_asg_driver) { double('template_blue_asg_driver') }
|
41
|
+
let(:actual_blue_asg_driver) { double('actual_blue_asg_driver') }
|
41
42
|
let(:green_asg_driver) { double('green_asg_driver') }
|
42
43
|
|
43
|
-
|
44
|
+
before :each do
|
44
45
|
blue_stack.live!
|
45
46
|
green_stack.live!
|
46
47
|
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-asg-swap-test-web-B', 'web', anything) { blue_stack }
|
47
48
|
allow(CfDeployer::Stack).to receive(:new).with('cf-deployer-sample-asg-swap-test-web-G', 'web', anything) { green_stack }
|
48
49
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
49
|
-
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('
|
50
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('templateBlueASG') { template_blue_asg_driver }
|
51
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('actualBlueASG') { actual_blue_asg_driver }
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should delete the stack that has no active instances' do
|
55
|
+
allow(blue_stack).to receive(:resource_statuses) { asg_ids 'actualBlueASG' }
|
56
|
+
allow(actual_blue_asg_driver).to receive(:'exists?').and_return(true)
|
50
57
|
allow(green_asg_driver).to receive(:describe) { {desired: 0, min: 0, max: 0} }
|
51
|
-
allow(
|
58
|
+
allow(actual_blue_asg_driver).to receive(:describe) { {desired: 1, min: 1, max: 2} }
|
52
59
|
CfDeployer::CLI.start(['kill_inactive', 'test', 'web', '-f', 'samples/simple/cf_deployer.yml'])
|
53
60
|
expect(green_stack).to be_deleted
|
54
61
|
expect(blue_stack).not_to be_deleted
|
55
62
|
end
|
63
|
+
|
64
|
+
it 'should determine active instances from CF stack' do
|
65
|
+
allow(blue_stack).to receive(:resource_statuses) { asg_ids 'actualBlueASG' }
|
66
|
+
allow(template_blue_asg_driver).to receive(:describe) { {desired: 0, min: 0, max: 0} }
|
67
|
+
allow(actual_blue_asg_driver).to receive(:describe) { {desired: 1, min: 1, max: 1} }
|
68
|
+
|
69
|
+
CfDeployer::CLI.start(['kill_inactive', 'test', 'web', '-f', 'samples/simple/cf_deployer.yml'])
|
70
|
+
expect(blue_stack).not_to be_deleted
|
71
|
+
end
|
56
72
|
end
|
57
73
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'functional_spec_helper'
|
2
2
|
|
3
3
|
describe 'Auto Scaling Group Swap Deployment Strategy' do
|
4
4
|
let(:app) { 'myapp' }
|
@@ -6,12 +6,12 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
6
6
|
let(:component) { 'worker' }
|
7
7
|
|
8
8
|
let(:context) {
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
{
|
10
|
+
:'deployment-strategy' => 'auto-scaling-group-swap',
|
11
|
+
:settings => {
|
12
12
|
:'auto-scaling-group-name-output' => ['AutoScalingGroupID']
|
13
|
-
}
|
14
13
|
}
|
14
|
+
}
|
15
15
|
}
|
16
16
|
|
17
17
|
let(:blue_asg_driver) { double('blue_asg_driver') }
|
@@ -23,6 +23,10 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
23
23
|
before :each do
|
24
24
|
allow(blue_stack).to receive(:output).with('AutoScalingGroupID'){'blueASG'}
|
25
25
|
allow(green_stack).to receive(:output).with('AutoScalingGroupID'){'greenASG'}
|
26
|
+
allow(blue_stack).to receive(:find_output).with('AutoScalingGroupID'){'blueASG'}
|
27
|
+
allow(green_stack).to receive(:find_output).with('AutoScalingGroupID'){'greenASG'}
|
28
|
+
allow(blue_stack).to receive(:resource_statuses) { asg_ids('blueASG') }
|
29
|
+
allow(green_stack).to receive(:resource_statuses) { asg_ids('greenASG') }
|
26
30
|
allow(CfDeployer::Stack).to receive(:new).with('myapp-dev-worker-B', 'worker', context) { blue_stack }
|
27
31
|
allow(CfDeployer::Stack).to receive(:new).with('myapp-dev-worker-G', 'worker', context) { green_stack }
|
28
32
|
end
|
@@ -59,7 +63,7 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
59
63
|
CfDeployer::DeploymentStrategy.create(app, env, component, context).deploy
|
60
64
|
end
|
61
65
|
|
62
|
-
|
66
|
+
it 'should deploy blue stack if green stack is not active' do
|
63
67
|
blue_stack.die!
|
64
68
|
green_stack.live!
|
65
69
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
@@ -137,7 +141,7 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
137
141
|
CfDeployer::DeploymentStrategy.create(app, env, component, context).deploy
|
138
142
|
end
|
139
143
|
|
140
|
-
|
144
|
+
it 'should deploy green stack if blue stack is active' do
|
141
145
|
blue_stack.live!
|
142
146
|
green_stack.live!
|
143
147
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
@@ -196,17 +200,17 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
196
200
|
|
197
201
|
context 'multiple ASG' do
|
198
202
|
let(:context) {
|
199
|
-
|
200
|
-
|
201
|
-
|
203
|
+
{
|
204
|
+
:'deployment-strategy' => 'auto-scaling-group-swap',
|
205
|
+
:settings => {
|
202
206
|
:'auto-scaling-group-name-output' => ['AutoScalingGroupID', 'AlternateASGID']
|
203
|
-
}
|
204
207
|
}
|
208
|
+
}
|
205
209
|
}
|
206
210
|
|
207
211
|
it 'should get error containing only "active" ASG if both blue and green stacks are active' do
|
208
|
-
allow(blue_stack).to receive(:
|
209
|
-
allow(green_stack).to receive(:
|
212
|
+
allow(blue_stack).to receive(:find_output).with('AlternateASGID'){'AltblueASG'}
|
213
|
+
allow(green_stack).to receive(:find_output).with('AlternateASGID'){'AltgreenASG'}
|
210
214
|
blue_stack.live!
|
211
215
|
green_stack.live!
|
212
216
|
alt_blue_asg_driver = double('alt_blue_asg_driver')
|
@@ -215,6 +219,7 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
215
219
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
216
220
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('AltgreenASG') { alt_green_asg_driver }
|
217
221
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
222
|
+
allow(blue_stack).to receive(:resource_statuses) { asg_ids('blueASG', 'AltblueASG') }
|
218
223
|
allow(alt_blue_asg_driver).to receive(:describe) {{desired: 1, min: 1, max: 2}}
|
219
224
|
allow(alt_green_asg_driver).to receive(:describe) {{desired: 0, min: 0, max: 0}}
|
220
225
|
allow(blue_asg_driver).to receive(:describe) {{desired: 1, min: 1, max: 2}}
|
@@ -345,6 +350,21 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
345
350
|
end
|
346
351
|
end
|
347
352
|
|
353
|
+
context '#cool_down_active_stack' do
|
354
|
+
it 'should cool down only those ASGs which actually exist' do
|
355
|
+
blue_stack.live!
|
356
|
+
green_stack.die!
|
357
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
358
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
359
|
+
allow(green_asg_driver).to receive(:describe) { {desired: 0, min: 0, max: 0 } }
|
360
|
+
allow(blue_asg_driver).to receive(:describe) { {desired: 1, min: 1, max: 3 } }
|
361
|
+
|
362
|
+
strategy = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
363
|
+
expect(blue_asg_driver).to receive(:cool_down)
|
364
|
+
strategy.send(:cool_down_active_stack)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
348
368
|
describe '#asg_driver' do
|
349
369
|
it 'returns the same driver for the same aws_group_name' do
|
350
370
|
strategy = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
@@ -358,7 +378,6 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
358
378
|
end
|
359
379
|
|
360
380
|
context '#output_value' do
|
361
|
-
|
362
381
|
it 'should get stack output if active stack exists' do
|
363
382
|
blue_stack.live!
|
364
383
|
green_stack.live!
|
@@ -383,53 +402,90 @@ describe 'Auto Scaling Group Swap Deployment Strategy' do
|
|
383
402
|
end
|
384
403
|
|
385
404
|
context '#status' do
|
386
|
-
|
405
|
+
before :each do
|
387
406
|
blue_stack.live!
|
388
407
|
green_stack.live!
|
389
408
|
allow(blue_stack).to receive(:status) { 'blue deployed' }
|
390
409
|
allow(green_stack).to receive(:status) { 'green deployed' }
|
391
|
-
allow(blue_stack).to receive(:resource_statuses) { 'blue resources' }
|
392
|
-
allow(green_stack).to receive(:resource_statuses) { 'green resources' }
|
393
|
-
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
394
410
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
395
|
-
allow(
|
411
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
396
412
|
allow(blue_asg_driver).to receive(:describe) {{desired: 3, min: 1, max: 5}}
|
413
|
+
allow(green_asg_driver).to receive(:describe) {{desired: 0, min: 0, max: 0}}
|
397
414
|
asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
398
415
|
|
399
416
|
end
|
400
417
|
|
401
418
|
it 'should get status for both green and blue stacks' do
|
402
419
|
asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
403
|
-
expected_result = {
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
420
|
+
expected_result = {
|
421
|
+
'BLUE' => {
|
422
|
+
:active => true,
|
423
|
+
:status => 'blue deployed'
|
424
|
+
},
|
425
|
+
'GREEN' => {
|
426
|
+
:active => false,
|
427
|
+
:status => 'green deployed'
|
428
|
+
}
|
412
429
|
}
|
413
430
|
asg_swap.status.should eq(expected_result)
|
414
431
|
end
|
415
432
|
|
416
433
|
it 'should get status for both green and blue stacks including resources info' do
|
417
434
|
asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
418
|
-
expected_result = {
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
435
|
+
expected_result = {
|
436
|
+
'BLUE' => {
|
437
|
+
:active => true,
|
438
|
+
:status => 'blue deployed',
|
439
|
+
:resources => {
|
440
|
+
:asg_instances => {
|
441
|
+
'blueASG' => nil
|
442
|
+
}
|
443
|
+
}
|
444
|
+
},
|
445
|
+
'GREEN' => {
|
446
|
+
:active => false,
|
447
|
+
:status => 'green deployed',
|
448
|
+
:resources => {
|
449
|
+
:asg_instances => {
|
450
|
+
'greenASG' => nil
|
451
|
+
}
|
452
|
+
}
|
453
|
+
}
|
429
454
|
}
|
430
455
|
asg_swap.status(true).should eq(expected_result)
|
431
456
|
end
|
457
|
+
end
|
458
|
+
|
459
|
+
context 'new ASG' do
|
460
|
+
let(:foo_asg_driver) { double('foo_asg_driver') }
|
461
|
+
let(:bar_asg_driver) { double('bar_asg_driver') }
|
462
|
+
|
463
|
+
before :each do
|
464
|
+
allow(foo_asg_driver).to receive(:describe) { {min: 1, desired: 2, max: 3} }
|
465
|
+
allow(bar_asg_driver).to receive(:describe) { {min: 0, desired: 0, max: 0} }
|
466
|
+
end
|
467
|
+
|
468
|
+
it 'should get active ASGs from CF stack' do
|
469
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('foo') { foo_asg_driver }
|
470
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('bar') { bar_asg_driver }
|
471
|
+
allow(blue_stack).to receive(:resource_statuses) { asg_ids('foo', 'bar') }
|
472
|
+
|
473
|
+
asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
474
|
+
asg_swap.send(:get_active_asgs, blue_stack).should eq(['foo'])
|
475
|
+
end
|
476
|
+
end
|
432
477
|
|
478
|
+
context '#stack_active' do
|
479
|
+
it 'should consider a stack active if it has any active ASGs' do
|
480
|
+
allow(CfDeployer::Stack).to receive(:new).with('myapp-dev-worker-B', 'worker', context) { blue_stack }
|
481
|
+
allow(CfDeployer::Stack).to receive(:new).with('myapp-dev-worker-G', 'worker', context) { green_stack }
|
482
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('greenASG') { green_asg_driver }
|
483
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blueASG') { blue_asg_driver }
|
484
|
+
allow(blue_asg_driver).to receive(:describe) { {min: 1, desired: 2, max: 3} }
|
485
|
+
allow(green_asg_driver).to receive(:describe) { {min: 0, desired: 0, max: 0} }
|
433
486
|
|
487
|
+
asg_swap = CfDeployer::DeploymentStrategy.create(app, env, component, context)
|
488
|
+
asg_swap.send(:stack_active?, blue_stack).should be(true)
|
489
|
+
end
|
434
490
|
end
|
435
491
|
end
|
@@ -134,4 +134,78 @@ describe 'Base Deployment Strategy' do
|
|
134
134
|
strategy.run_hook(:some_hook)
|
135
135
|
end
|
136
136
|
end
|
137
|
+
|
138
|
+
context '#warm_up_stack' do
|
139
|
+
let(:context) {
|
140
|
+
{
|
141
|
+
:'deployment-strategy' => 'base',
|
142
|
+
:settings => {
|
143
|
+
:'auto-scaling-group-name-output' => ['ASG1', 'ASG2']
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
let(:blue_stack) { double('blue_stack') }
|
148
|
+
let(:green_stack) { double('green_stack') }
|
149
|
+
let(:blue_asg_driver_1) { double('blue_asg_driver_1') }
|
150
|
+
let(:blue_asg_driver_2) { double('blue_asg_driver_2') }
|
151
|
+
let(:green_asg_driver_1) { double('green_asg_driver_1') }
|
152
|
+
let(:green_asg_driver_2) { double('green_asg_driver_2') }
|
153
|
+
|
154
|
+
before :each do
|
155
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blue_asg_driver_1') { blue_asg_driver_1 }
|
156
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('blue_asg_driver_2') { blue_asg_driver_2 }
|
157
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('green_asg_driver_1') { green_asg_driver_1 }
|
158
|
+
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('green_asg_driver_2') { green_asg_driver_2 }
|
159
|
+
|
160
|
+
allow(green_stack).to receive(:find_output).with('ASG1') { 'green_asg_driver_1' }
|
161
|
+
allow(green_stack).to receive(:find_output).with('ASG2') { 'green_asg_driver_2' }
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'should warm up ASG with previous stack ASG values when present' do
|
165
|
+
allow(blue_stack).to receive(:resource_statuses) { { 'blueASG1' => nil, 'blueASG2' => nil } }
|
166
|
+
allow(blue_stack).to receive(:find_output).with('ASG1') { 'blue_asg_driver_1' }
|
167
|
+
allow(blue_stack).to receive(:find_output).with('ASG2') { 'blue_asg_driver_2' }
|
168
|
+
allow(blue_asg_driver_1).to receive(:describe) { {min: 1, desired: 2, max: 3} }
|
169
|
+
allow(blue_asg_driver_2).to receive(:describe) { {min: 2, desired: 3, max: 4} }
|
170
|
+
strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', context)
|
171
|
+
|
172
|
+
expect(green_asg_driver_1).to receive(:warm_up).with(2)
|
173
|
+
expect(green_asg_driver_2).to receive(:warm_up).with(3)
|
174
|
+
strategy.send(:warm_up_stack, green_stack, blue_stack)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should warm up ASG with own values when previous stack does not contain ASG' do
|
178
|
+
allow(blue_stack).to receive(:find_output).with(anything) { nil }
|
179
|
+
allow(green_asg_driver_1).to receive(:describe) { {min: 3, desired: 4, max: 5} }
|
180
|
+
allow(green_asg_driver_2).to receive(:describe) { {min: 4, desired: 5, max: 6} }
|
181
|
+
strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', context)
|
182
|
+
|
183
|
+
expect(green_asg_driver_1).to receive(:warm_up).with(4)
|
184
|
+
expect(green_asg_driver_2).to receive(:warm_up).with(5)
|
185
|
+
strategy.send(:warm_up_stack, green_stack, blue_stack)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context '#template_asg_name_to_ids' do
|
190
|
+
let(:context) {
|
191
|
+
{
|
192
|
+
:'deployment-strategy' => 'base',
|
193
|
+
:settings => {
|
194
|
+
:'auto-scaling-group-name-output' => ['ASG1', 'ASG2']
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
it 'should map names in templates to stack outputs' do
|
200
|
+
allow(blue_stack).to receive(:find_output).with('ASG1') { 'blue_asg_driver_1' }
|
201
|
+
allow(blue_stack).to receive(:find_output).with('ASG2') { 'blue_asg_driver_2' }
|
202
|
+
expected = {
|
203
|
+
'ASG1' => 'blue_asg_driver_1',
|
204
|
+
'ASG2' => 'blue_asg_driver_2',
|
205
|
+
}
|
206
|
+
|
207
|
+
strategy = CfDeployer::DeploymentStrategy.create('myApp', 'uat', 'web', context)
|
208
|
+
expect(strategy.send(:template_asg_name_to_ids, blue_stack)).to eq(expected)
|
209
|
+
end
|
210
|
+
end
|
137
211
|
end
|
@@ -61,7 +61,7 @@ describe 'CreateOrUpdate Strategy' do
|
|
61
61
|
context[:settings] = {}
|
62
62
|
context[:settings][:'auto-scaling-group-name-output'] = ['AutoScalingGroupID']
|
63
63
|
@stack.should_receive(:exists?).and_return(false)
|
64
|
-
allow(@stack).to receive(:
|
64
|
+
allow(@stack).to receive(:find_output).with('AutoScalingGroupID') { 'asg_name' }
|
65
65
|
allow(CfDeployer::Driver::AutoScalingGroup).to receive(:new).with('asg_name') { asg_driver }
|
66
66
|
allow(asg_driver).to receive(:describe) { {desired:2, min:1, max:3} }
|
67
67
|
allow(@after_create_hook).to receive(:run).with(anything)
|
@@ -1,10 +1,28 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
|
2
3
|
describe 'CloudFormation' do
|
3
4
|
let(:outputs) { [output1, output2] }
|
4
5
|
let(:output1) { double('output1', :key => 'key1', :value => 'value1')}
|
5
6
|
let(:output2) { double('output2', :key => 'key2', :value => 'value2')}
|
6
7
|
let(:parameters) { double('parameters')}
|
7
|
-
let(:
|
8
|
+
let(:resource_summaries) { [
|
9
|
+
{
|
10
|
+
:resource_type => 'AWS::AutoScaling::AutoScalingGroup',
|
11
|
+
:physical_resource_id => 'asg_1',
|
12
|
+
:resource_status => 'STATUS_1'
|
13
|
+
},
|
14
|
+
{
|
15
|
+
:resource_type => 'AWS::AutoScaling::LaunchConfiguration',
|
16
|
+
:physical_resource_id => 'launch_config_1',
|
17
|
+
:resource_status => 'STATUS_2'
|
18
|
+
},
|
19
|
+
{
|
20
|
+
:resource_type => 'AWS::AutoScaling::AutoScalingGroup',
|
21
|
+
:physical_resource_id => 'asg_2',
|
22
|
+
:resource_status => 'STATUS_2'
|
23
|
+
}
|
24
|
+
] }
|
25
|
+
let(:stack) { double('stack', :outputs => outputs, :parameters => parameters, :resource_summaries => resource_summaries) }
|
8
26
|
let(:cloudFormation) {
|
9
27
|
double('cloudFormation',
|
10
28
|
:stacks =>
|
@@ -25,8 +43,18 @@ describe 'CloudFormation' do
|
|
25
43
|
end
|
26
44
|
|
27
45
|
context 'resource_statuses' do
|
28
|
-
it 'should
|
29
|
-
|
46
|
+
it 'should get resource statuses' do
|
47
|
+
expected = {
|
48
|
+
'AWS::AutoScaling::AutoScalingGroup' => {
|
49
|
+
'asg_1' => 'STATUS_1',
|
50
|
+
'asg_2' => 'STATUS_2'
|
51
|
+
},
|
52
|
+
'AWS::AutoScaling::LaunchConfiguration' => {
|
53
|
+
'launch_config_1' => 'STATUS_2'
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
CfDeployer::Driver::CloudFormation.new('testStack').resource_statuses.should eq(expected)
|
30
58
|
end
|
31
59
|
end
|
32
60
|
end
|
data/spec/unit/stack_spec.rb
CHANGED
@@ -59,8 +59,31 @@ describe CfDeployer::Stack do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
context
|
62
|
+
context '#output' do
|
63
|
+
it 'should get output value' do
|
64
|
+
expect(@cf_driver).to receive(:query_output).with('mykey'){ 'myvalue'}
|
65
|
+
@stack.output('mykey').should eq('myvalue')
|
66
|
+
end
|
63
67
|
|
68
|
+
it 'should get error if output is empty' do
|
69
|
+
expect(@cf_driver).to receive(:query_output).with('mykey'){ nil }
|
70
|
+
expect{@stack.output('mykey')}.to raise_error("'mykey' is empty from stack test output")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context '#find_output' do
|
75
|
+
it 'should get output value' do
|
76
|
+
expect(@cf_driver).to receive(:query_output).with('mykey'){ 'myvalue'}
|
77
|
+
@stack.find_output('mykey').should eq('myvalue')
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should return nil for non-existent value' do
|
81
|
+
expect(@cf_driver).to receive(:query_output).with('mykey'){ nil }
|
82
|
+
@stack.find_output('mykey').should be(nil)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "#ready?" do
|
64
87
|
CfDeployer::Stack::READY_STATS.each do |status|
|
65
88
|
it "should be ready when in #{status} status" do
|
66
89
|
allow(@cf_driver).to receive(:stack_status) { status }
|
@@ -72,16 +95,6 @@ describe CfDeployer::Stack do
|
|
72
95
|
allow(@cf_driver).to receive(:stack_status) { :my_fake_status }
|
73
96
|
expect(@stack).not_to be_ready
|
74
97
|
end
|
75
|
-
|
76
|
-
it "should get error if output is empty" do
|
77
|
-
expect(@cf_driver).to receive(:query_output).with('mykey'){ nil }
|
78
|
-
expect{@stack.output('mykey')}.to raise_error("'mykey' is empty from stack test output")
|
79
|
-
end
|
80
|
-
|
81
|
-
it "should get output value" do
|
82
|
-
expect(@cf_driver).to receive(:query_output).with('mykey'){ 'myvalue'}
|
83
|
-
@stack.output('mykey').should eq('myvalue')
|
84
|
-
end
|
85
98
|
end
|
86
99
|
|
87
100
|
describe '#delete' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cf_deployer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jame Brechtel
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2015-
|
14
|
+
date: 2015-11-09 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: aws-sdk
|
@@ -151,6 +151,7 @@ extensions: []
|
|
151
151
|
extra_rdoc_files: []
|
152
152
|
files:
|
153
153
|
- .gitignore
|
154
|
+
- .travis.yml
|
154
155
|
- ChangeLog.md
|
155
156
|
- DETAILS.md
|
156
157
|
- FAQ.md
|