cf_deployer 1.3.8 → 1.3.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/cf_deployer.svg)](https://badge.fury.io/rb/cf_deployer)
|
2
|
+
[![Build Status](https://travis-ci.org/manheim/cf_deployer.svg?branch=master)](https://travis-ci.org/manheim/cf_deployer)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/manheim/cf_deployer/badges/gpa.svg)](https://codeclimate.com/github/manheim/cf_deployer)
|
4
|
+
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](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
|