cf_deployer 1.6.0 → 2.0.0
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 +5 -5
- data/.github/workflows/ci.yml +29 -0
- data/.github/workflows/publish.yml +40 -0
- data/.rspec +4 -0
- data/ChangeLog.md +7 -0
- data/QUICKSTART.md +1 -1
- data/Rakefile +1 -1
- data/cf_deployer.gemspec +16 -10
- data/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml +2 -2
- data/lib/cf_deployer/cli.rb +1 -1
- data/lib/cf_deployer/config_loader.rb +2 -2
- data/lib/cf_deployer/config_validation.rb +1 -1
- data/lib/cf_deployer/driver/auto_scaling_group.rb +22 -26
- data/lib/cf_deployer/driver/cloud_formation_driver.rb +28 -18
- data/lib/cf_deployer/driver/elb_driver.rb +11 -4
- data/lib/cf_deployer/driver/instance.rb +4 -3
- data/lib/cf_deployer/driver/route53_driver.rb +17 -9
- data/lib/cf_deployer/logger.rb +10 -11
- data/lib/cf_deployer/stack.rb +18 -30
- data/lib/cf_deployer/status_presenter.rb +1 -1
- data/lib/cf_deployer/version.rb +1 -1
- data/lib/cf_deployer.rb +10 -7
- data/spec/aws_call_count_spec_helper.rb +14 -2
- data/spec/fakes/instance.rb +5 -4
- data/spec/fakes/route53_client.rb +4 -7
- data/spec/spec_helper.rb +11 -2
- data/spec/unit/application_spec.rb +9 -9
- data/spec/unit/component_spec.rb +3 -3
- data/spec/unit/config_loader_spec.rb +70 -69
- data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +10 -10
- data/spec/unit/deployment_strategy/base_spec.rb +17 -18
- data/spec/unit/deployment_strategy/cname_swap_spec.rb +9 -9
- data/spec/unit/deployment_strategy/create_or_update_spec.rb +8 -8
- data/spec/unit/driver/auto_scaling_group_spec.rb +75 -65
- data/spec/unit/driver/aws_sdk_v3_spec.rb +163 -0
- data/spec/unit/driver/cloud_formation_spec.rb +19 -17
- data/spec/unit/driver/dry_run_spec.rb +8 -8
- data/spec/unit/driver/elb_spec.rb +10 -4
- data/spec/unit/driver/instance_spec.rb +1 -7
- data/spec/unit/driver/route53_spec.rb +34 -25
- data/spec/unit/hook_spec.rb +3 -3
- data/spec/unit/stack_spec.rb +22 -21
- metadata +134 -56
- data/.travis.yml +0 -20
|
@@ -2,38 +2,39 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe 'Autoscaling group driver' do
|
|
4
4
|
let(:group) { double('group', :desired_capacity => 2, :min_size => 1, :max_size => 4)}
|
|
5
|
-
let(:scaling) { double('scaling', :groups => { 'myAsg' => group}) }
|
|
6
5
|
let(:ec2_instance1) { double('ec2_instance1') }
|
|
7
6
|
let(:ec2_instance2) { double('ec2_instance2') }
|
|
8
7
|
let(:ec2_instance3) { double('ec2_instance3') }
|
|
9
8
|
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)}
|
|
9
|
+
let(:instance1) { double('instance1', :id => 'instance1', :health_status => 'HEALTHY', :ec2_instance => ec2_instance1)}
|
|
10
|
+
let(:instance2) { double('instance2', :id => 'instance2', :health_status => 'HEALTHY', :ec2_instance => ec2_instance2)}
|
|
11
|
+
let(:instance3) { double('instance3', :id => 'instance3', :health_status => 'HEALTHY', :ec2_instance => ec2_instance3)}
|
|
12
|
+
let(:instance4) { double('instance4', :id => 'instance4', :health_status => 'HEALTHY', :ec2_instance => ec2_instance4)}
|
|
14
13
|
let(:load_balancer) { double('load_balancer', :instances => [instance1, instance2, instance3, instance4]) }
|
|
14
|
+
let(:elb_driver) { double(Aws::ElasticLoadBalancing::Client) }
|
|
15
15
|
|
|
16
16
|
before :each do
|
|
17
|
-
allow(
|
|
18
|
-
allow(group).to receive(:
|
|
19
|
-
allow(group).to receive(:
|
|
20
|
-
allow(
|
|
17
|
+
allow(Aws::ElasticLoadBalancing::Client).to receive(:new) { elb_driver }
|
|
18
|
+
allow(group).to receive(:load_balancer_names) { [] }
|
|
19
|
+
allow(group).to receive(:instances) { [] }
|
|
20
|
+
allow(Aws::AutoScaling::AutoScalingGroup).to receive(:new).with('myAsg') { group }
|
|
21
|
+
allow(group).to receive(:reload) { group }
|
|
21
22
|
@driver = CfDeployer::Driver::AutoScalingGroup.new('myAsg', 1)
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
it 'should describe group' do
|
|
25
|
-
@driver.describe.
|
|
26
|
+
expect(@driver.describe).to eq({ min: 1, max: 4, desired: 2})
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
describe '#warm_up' do
|
|
29
30
|
it 'should warm up the group to the desired size' do
|
|
30
|
-
expect(group).to receive(:set_desired_capacity).with(2)
|
|
31
|
+
expect(group).to receive(:set_desired_capacity).with({desired_capacity: 2})
|
|
31
32
|
expect(@driver).to receive(:wait_for_desired_capacity)
|
|
32
33
|
@driver.warm_up 2
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
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(:set_desired_capacity).with(1)
|
|
37
|
+
expect(group).to receive(:set_desired_capacity).with({desired_capacity: 1})
|
|
37
38
|
expect(@driver).to receive(:wait_for_desired_capacity)
|
|
38
39
|
@driver.warm_up 1
|
|
39
40
|
end
|
|
@@ -45,7 +46,7 @@ describe 'Autoscaling group driver' do
|
|
|
45
46
|
end
|
|
46
47
|
|
|
47
48
|
it 'should warm up to maximum if desired number is greater than maximum size of group' do
|
|
48
|
-
expect(group).to receive(:set_desired_capacity).with(4)
|
|
49
|
+
expect(group).to receive(:set_desired_capacity).with({desired_capacity: 4})
|
|
49
50
|
expect(@driver).to receive(:wait_for_desired_capacity)
|
|
50
51
|
@driver.warm_up 5
|
|
51
52
|
end
|
|
@@ -57,14 +58,14 @@ describe 'Autoscaling group driver' do
|
|
|
57
58
|
instance2 = double('instance2', :health_status => 'HEALTHY', id: 'instance2')
|
|
58
59
|
instance3 = double('instance3', :health_status => 'UNHEALTHY', id: 'instance3')
|
|
59
60
|
instance4 = double('instance4', :health_status => 'HEALTHY', id: 'instance4')
|
|
60
|
-
allow(group).to receive(:
|
|
61
|
+
allow(group).to receive(:instances){[instance1, instance2, instance3, instance4]}
|
|
61
62
|
|
|
62
63
|
expect(@driver.healthy_instance_ids).to eql ['instance1', 'instance2', 'instance4']
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
it 'returns the ids of all instances that are healthy (case insensitive)' do
|
|
66
67
|
instance1 = double('instance1', :health_status => 'HealThy', id: 'instance1')
|
|
67
|
-
allow(group).to receive(:
|
|
68
|
+
allow(group).to receive(:instances){[instance1]}
|
|
68
69
|
|
|
69
70
|
expect(@driver.healthy_instance_ids).to eql ['instance1']
|
|
70
71
|
end
|
|
@@ -73,69 +74,80 @@ describe 'Autoscaling group driver' do
|
|
|
73
74
|
describe '#in_service_instance_ids' do
|
|
74
75
|
context 'when there are no load balancers' do
|
|
75
76
|
it 'returns no ids' do
|
|
76
|
-
allow(group).to receive(:
|
|
77
|
+
allow(group).to receive(:load_balancer_names).and_return([])
|
|
77
78
|
|
|
78
79
|
expect(@driver.in_service_instance_ids).to eq []
|
|
79
80
|
end
|
|
80
81
|
end
|
|
81
82
|
|
|
82
83
|
context 'when there is only 1 elb' do
|
|
84
|
+
let(:elb1_instance_health) { double('elb1_instance_health') }
|
|
85
|
+
|
|
86
|
+
before(:each) do
|
|
87
|
+
allow(group).to receive(:load_balancer_names).and_return(['elb1'])
|
|
88
|
+
allow(elb_driver).to receive(:describe_instance_health)
|
|
89
|
+
.with(hash_including(load_balancer_name: 'elb1')) { elb1_instance_health }
|
|
90
|
+
end
|
|
91
|
+
|
|
83
92
|
it 'returns the ids of all instances that are in service' do
|
|
84
|
-
health1 =
|
|
85
|
-
health2 =
|
|
86
|
-
health3 =
|
|
87
|
-
health4 =
|
|
93
|
+
health1 = double(:state => 'InService', instance_id: 'instance1')
|
|
94
|
+
health2 = double(:state => 'OutOfService', instance_id: 'instance2')
|
|
95
|
+
health3 = double(:state => 'InService', instance_id: 'instance3')
|
|
96
|
+
health4 = double(:state => 'InService', instance_id: 'instance4')
|
|
88
97
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
allow(group).to receive(:load_balancers).and_return([ elb ])
|
|
98
|
+
allow(elb1_instance_health).to receive(:instance_states) { [health1, health2, health3, health4] }
|
|
99
|
+
allow(group).to receive(:load_balancer_names).and_return([ 'elb1' ])
|
|
92
100
|
|
|
93
101
|
expect(@driver.in_service_instance_ids).to eql ['instance1', 'instance3', 'instance4']
|
|
94
102
|
end
|
|
95
103
|
end
|
|
96
104
|
|
|
97
105
|
context 'when there are multiple elbs' do
|
|
106
|
+
let(:elb1_instance_health) { double('elb1_instance_health') }
|
|
107
|
+
let(:elb2_instance_health) { double('elb2_instance_health') }
|
|
108
|
+
let(:elb3_instance_health) { double('elb3_instance_health') }
|
|
109
|
+
|
|
110
|
+
before(:each) do
|
|
111
|
+
allow(group).to receive(:load_balancer_names).and_return(['elb1', 'elb2', 'elb3'])
|
|
112
|
+
allow(elb_driver).to receive(:describe_instance_health)
|
|
113
|
+
.with(hash_including(load_balancer_name: 'elb1')) { elb1_instance_health }
|
|
114
|
+
allow(elb_driver).to receive(:describe_instance_health)
|
|
115
|
+
.with(hash_including(load_balancer_name: 'elb2')) { elb2_instance_health }
|
|
116
|
+
allow(elb_driver).to receive(:describe_instance_health)
|
|
117
|
+
.with(hash_including(load_balancer_name: 'elb3')) { elb3_instance_health }
|
|
118
|
+
end
|
|
119
|
+
|
|
98
120
|
it 'returns only the ids of instances that are in all ELBs' do
|
|
99
|
-
health1 =
|
|
100
|
-
health2 =
|
|
101
|
-
health3 =
|
|
102
|
-
health4 =
|
|
103
|
-
health5 =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
elb1 = double('elb1', instances: instance_collection1)
|
|
109
|
-
elb2 = double('elb2', instances: instance_collection2)
|
|
110
|
-
elb3 = double('elb3', instances: instance_collection3)
|
|
111
|
-
allow(group).to receive(:load_balancers).and_return([ elb1, elb2, elb3 ])
|
|
121
|
+
health1 = double(state: 'InService', instance_id: 'instance1')
|
|
122
|
+
health2 = double(state: 'InService', instance_id: 'instance2')
|
|
123
|
+
health3 = double(state: 'InService', instance_id: 'instance3')
|
|
124
|
+
health4 = double(state: 'InService', instance_id: 'instance4')
|
|
125
|
+
health5 = double(state: 'InService', instance_id: 'instance5')
|
|
126
|
+
|
|
127
|
+
allow(elb1_instance_health).to receive(:instance_states) { [health1, health2, health3] }
|
|
128
|
+
allow(elb2_instance_health).to receive(:instance_states) { [health2, health3, health4] }
|
|
129
|
+
allow(elb3_instance_health).to receive(:instance_states) { [health2, health3, health5] }
|
|
112
130
|
|
|
113
131
|
# Only instance 2 and 3 are associated with all ELB's
|
|
114
132
|
expect(@driver.in_service_instance_ids).to eql ['instance2', 'instance3']
|
|
115
133
|
end
|
|
116
134
|
|
|
117
135
|
it 'returns only the ids instances that are InService in all ELBs' do
|
|
118
|
-
health11 =
|
|
119
|
-
health12 =
|
|
120
|
-
health13 =
|
|
121
|
-
|
|
122
|
-
health21 = { state: 'InService', instance: double('i1', id: 'instance1') }
|
|
123
|
-
health22 = { state: 'InService', instance: double('i2', id: 'instance2') }
|
|
124
|
-
health23 = { state: 'OutOfService', instance: double('i3', id: 'instance3') }
|
|
125
|
-
|
|
126
|
-
health31 = { state: 'InService', instance: double('i1', id: 'instance1') }
|
|
127
|
-
health32 = { state: 'InService', instance: double('i2', id: 'instance2') }
|
|
128
|
-
health33 = { state: 'InService', instance: double('i3', id: 'instance3') }
|
|
136
|
+
health11 = double(state: 'OutOfService', instance_id: 'instance1')
|
|
137
|
+
health12 = double(state: 'InService', instance_id: 'instance2')
|
|
138
|
+
health13 = double(state: 'InService', instance_id: 'instance3')
|
|
129
139
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
health21 = double(state: 'InService', instance_id: 'instance1')
|
|
141
|
+
health22 = double(state: 'InService', instance_id: 'instance2')
|
|
142
|
+
health23 = double(state: 'OutOfService', instance_id: 'instance3')
|
|
133
143
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
health31 = double(state: 'InService', instance_id: 'instance1')
|
|
145
|
+
health32 = double(state: 'InService', instance_id: 'instance2')
|
|
146
|
+
health33 = double(state: 'InService', instance_id: 'instance3')
|
|
137
147
|
|
|
138
|
-
allow(
|
|
148
|
+
allow(elb1_instance_health).to receive(:instance_states) { [health11, health12, health13] }
|
|
149
|
+
allow(elb2_instance_health).to receive(:instance_states) { [health21, health22, health23] }
|
|
150
|
+
allow(elb3_instance_health).to receive(:instance_states) { [health31, health32, health33] }
|
|
139
151
|
|
|
140
152
|
# Only instance 2 is InService across all ELB's
|
|
141
153
|
expect(@driver.in_service_instance_ids).to eql ['instance2']
|
|
@@ -158,7 +170,7 @@ describe 'Autoscaling group driver' do
|
|
|
158
170
|
it 'should return the number of instances that are both healthy, and in service' do
|
|
159
171
|
healthy_instance_ids = ['1', '3', '4', '5']
|
|
160
172
|
in_service_instance_ids = ['3', '4']
|
|
161
|
-
allow(@driver).to receive(:
|
|
173
|
+
allow(@driver).to receive(:load_balancer_names).and_return(['elb1'])
|
|
162
174
|
expect(@driver).to receive(:healthy_instance_ids).and_return(healthy_instance_ids)
|
|
163
175
|
expect(@driver).to receive(:in_service_instance_ids).and_return(in_service_instance_ids)
|
|
164
176
|
|
|
@@ -176,7 +188,7 @@ describe 'Autoscaling group driver' do
|
|
|
176
188
|
describe '#cool_down' do
|
|
177
189
|
it 'should cool down group' do
|
|
178
190
|
expect(group).to receive(:update).with({min_size: 0, max_size: 0})
|
|
179
|
-
expect(group).to receive(:set_desired_capacity).with(0)
|
|
191
|
+
expect(group).to receive(:set_desired_capacity).with({desired_capacity: 0})
|
|
180
192
|
@driver.cool_down
|
|
181
193
|
end
|
|
182
194
|
end
|
|
@@ -184,7 +196,7 @@ describe 'Autoscaling group driver' do
|
|
|
184
196
|
describe '#warm_up_cooled_group' do
|
|
185
197
|
it 'should set min, max, and desired from a hash' do
|
|
186
198
|
hash = {:max => 5, :min => 2, :desired => 3}
|
|
187
|
-
allow(group).to receive(:
|
|
199
|
+
allow(group).to receive(:instances){[instance1, instance2, instance3]}
|
|
188
200
|
expect(group).to receive(:update).with({:min_size => 2, :max_size => 5})
|
|
189
201
|
expect(@driver).to receive(:warm_up).with(3)
|
|
190
202
|
@driver.warm_up_cooled_group hash
|
|
@@ -193,15 +205,13 @@ describe 'Autoscaling group driver' do
|
|
|
193
205
|
|
|
194
206
|
describe '#instance_statuses' do
|
|
195
207
|
it 'should get the status for any EC2 instances' do
|
|
196
|
-
|
|
197
|
-
expect(aws_instance).to receive(:id) { 'i-abcd1234' }
|
|
198
|
-
allow(@driver).to receive(:ec2_instances) { [ aws_instance ] }
|
|
208
|
+
allow(@driver).to receive(:instances) { [ instance1 ] }
|
|
199
209
|
|
|
200
210
|
returned_status = { :status => :some_status }
|
|
201
211
|
cfd_instance = double CfDeployer::Driver::Instance
|
|
202
|
-
expect(CfDeployer::Driver::Instance).to receive(:new).with(
|
|
212
|
+
expect(CfDeployer::Driver::Instance).to receive(:new).with(instance1.id) { cfd_instance }
|
|
203
213
|
expect(cfd_instance).to receive(:status) { returned_status }
|
|
204
|
-
expect(@driver.instance_statuses).to eq( { '
|
|
214
|
+
expect(@driver.instance_statuses).to eq( { 'instance1' => returned_status } )
|
|
205
215
|
end
|
|
206
216
|
end
|
|
207
217
|
|
|
@@ -226,7 +236,7 @@ describe 'Autoscaling group driver' do
|
|
|
226
236
|
expect(group).to receive(:desired_capacity).and_return(expected_number)
|
|
227
237
|
expect(@driver).to receive(:healthy_instance_count).and_return(expected_number)
|
|
228
238
|
|
|
229
|
-
expect(@driver.desired_capacity_reached?).to
|
|
239
|
+
expect(@driver.desired_capacity_reached?).to be_truthy
|
|
230
240
|
end
|
|
231
241
|
|
|
232
242
|
it 'returns false if healthy instance count is less than desired capacity' do
|
|
@@ -235,7 +245,7 @@ describe 'Autoscaling group driver' do
|
|
|
235
245
|
expect(group).to receive(:desired_capacity).and_return(expected_number)
|
|
236
246
|
expect(@driver).to receive(:healthy_instance_count).and_return(expected_number - 1)
|
|
237
247
|
|
|
238
|
-
expect(@driver.desired_capacity_reached?).to
|
|
248
|
+
expect(@driver.desired_capacity_reached?).to be_falsey
|
|
239
249
|
end
|
|
240
250
|
|
|
241
251
|
it 'returns true if healthy instance count is more than desired capacity' do
|
|
@@ -244,7 +254,7 @@ describe 'Autoscaling group driver' do
|
|
|
244
254
|
expect(group).to receive(:desired_capacity).and_return(expected_number)
|
|
245
255
|
expect(@driver).to receive(:healthy_instance_count).and_return(expected_number + 1)
|
|
246
256
|
|
|
247
|
-
expect(@driver.desired_capacity_reached?).to
|
|
257
|
+
expect(@driver.desired_capacity_reached?).to be_truthy
|
|
248
258
|
end
|
|
249
259
|
end
|
|
250
260
|
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
# Exercises the drivers against real AWS SDK v3 classes backed by stubbed
|
|
4
|
+
# clients, so SDK return types and resource caching behave as they do in AWS.
|
|
5
|
+
describe 'drivers against the AWS SDK v3' do
|
|
6
|
+
def stub_aws(service, stubs)
|
|
7
|
+
Aws.config.update(service => { stub_responses: stubs })
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
around(:each) do |example|
|
|
11
|
+
saved = Aws.config.dup
|
|
12
|
+
Aws.config.update(region: 'us-east-1', credentials: Aws::Credentials.new('akid', 'secret'))
|
|
13
|
+
example.run
|
|
14
|
+
ensure
|
|
15
|
+
Aws.config = saved
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'configures 20 retries for AWS calls' do
|
|
19
|
+
expect(Aws::CloudFormation::Client.new(stub_responses: true).config.retry_limit).to eq(20)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe CfDeployer::Driver::AutoScalingGroup do
|
|
23
|
+
def group(desired:, healthy:)
|
|
24
|
+
{
|
|
25
|
+
auto_scaling_group_name: 'myAsg', min_size: 0, max_size: 5, desired_capacity: desired,
|
|
26
|
+
default_cooldown: 300, availability_zones: ['us-east-1a'], health_check_type: 'EC2', created_time: Time.now,
|
|
27
|
+
load_balancer_names: [],
|
|
28
|
+
instances: Array.new(healthy) do |i|
|
|
29
|
+
{ instance_id: "i-#{i}", availability_zone: 'us-east-1a', lifecycle_state: 'InService',
|
|
30
|
+
health_status: 'Healthy', protected_from_scale_in: false }
|
|
31
|
+
end
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'sees fresh group data while waiting for desired capacity' do
|
|
36
|
+
stub_aws(:autoscaling, describe_auto_scaling_groups: [
|
|
37
|
+
{ auto_scaling_groups: [group(desired: 2, healthy: 0)] },
|
|
38
|
+
{ auto_scaling_groups: [group(desired: 2, healthy: 1)] },
|
|
39
|
+
{ auto_scaling_groups: [group(desired: 2, healthy: 2)] }
|
|
40
|
+
])
|
|
41
|
+
driver = CfDeployer::Driver::AutoScalingGroup.new('myAsg', 5)
|
|
42
|
+
allow(driver).to receive(:sleep)
|
|
43
|
+
|
|
44
|
+
expect { driver.wait_for_desired_capacity }.not_to raise_error
|
|
45
|
+
expect(driver).to have_received(:sleep).twice
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
describe CfDeployer::Driver::CloudFormation do
|
|
50
|
+
let(:stack) do
|
|
51
|
+
{ stack_name: 'myStack', creation_time: Time.now, stack_status: 'UPDATE_COMPLETE',
|
|
52
|
+
parameters: [{ parameter_key: 'foo', parameter_value: 'bar' }],
|
|
53
|
+
outputs: [{ output_key: 'elb', output_value: 'my-elb' }] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'raises validation errors other than "No updates are to be performed"' do
|
|
57
|
+
stub_aws(:cloudformation, update_stack: Aws::CloudFormation::Errors::ValidationError.new(nil, 'Template format error'))
|
|
58
|
+
|
|
59
|
+
CfDeployer::Driver::DryRun.disable_for do
|
|
60
|
+
expect { CfDeployer::Driver::CloudFormation.new('myStack').update_stack('{}', {}) }
|
|
61
|
+
.to raise_error(Aws::CloudFormation::Errors::ValidationError, 'Template format error')
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'returns false when there are no updates to perform' do
|
|
66
|
+
stub_aws(:cloudformation, update_stack: Aws::CloudFormation::Errors::ValidationError.new(nil, 'No updates are to be performed.'))
|
|
67
|
+
|
|
68
|
+
CfDeployer::Driver::DryRun.disable_for do
|
|
69
|
+
expect(CfDeployer::Driver::CloudFormation.new('myStack').update_stack('{}', {})).to eq(false)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'gets the template body' do
|
|
74
|
+
stub_aws(:cloudformation, get_template: { template_body: '{"Resources":{}}' })
|
|
75
|
+
expect(CfDeployer::Driver::CloudFormation.new('myStack').template).to eq('{"Resources":{}}')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'gets parameters and outputs as hashes' do
|
|
79
|
+
stub_aws(:cloudformation, describe_stacks: { stacks: [stack] })
|
|
80
|
+
driver = CfDeployer::Driver::CloudFormation.new('myStack')
|
|
81
|
+
|
|
82
|
+
expect(driver.parameters).to eq('foo' => 'bar')
|
|
83
|
+
expect(driver.outputs).to eq('elb' => 'my-elb')
|
|
84
|
+
expect(driver.query_output('elb')).to eq('my-elb')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'treats a missing stack as not existing' do
|
|
88
|
+
stub_aws(:cloudformation, describe_stacks: Aws::CloudFormation::Errors::ValidationError.new(nil, 'Stack with id myStack does not exist'))
|
|
89
|
+
driver = CfDeployer::Driver::CloudFormation.new('myStack')
|
|
90
|
+
|
|
91
|
+
expect(driver.stack_exists?).to eq(false)
|
|
92
|
+
expect(driver.stack_status).to be_nil
|
|
93
|
+
expect { driver.query_output('elb') }.to raise_error(Aws::CloudFormation::Errors::ValidationError)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'raises other errors when describing the stack' do
|
|
97
|
+
stub_aws(:cloudformation, describe_stacks: Aws::CloudFormation::Errors::ValidationError.new(nil, 'Rate exceeded'))
|
|
98
|
+
expect { CfDeployer::Driver::CloudFormation.new('myStack').stack_exists? }.to raise_error(Aws::CloudFormation::Errors::ValidationError, 'Rate exceeded')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'reads resource statuses from every page' do
|
|
102
|
+
summary = ->(id) { { logical_resource_id: id, physical_resource_id: id, resource_type: 'AWS::EC2::Instance',
|
|
103
|
+
resource_status: 'CREATE_COMPLETE', last_updated_timestamp: Time.now } }
|
|
104
|
+
stub_aws(:cloudformation, list_stack_resources: [
|
|
105
|
+
{ stack_resource_summaries: [summary.call('i-1')], next_token: 'page2' },
|
|
106
|
+
{ stack_resource_summaries: [summary.call('i-2')] }
|
|
107
|
+
])
|
|
108
|
+
|
|
109
|
+
statuses = CfDeployer::Driver::CloudFormation.new('myStack').resource_statuses
|
|
110
|
+
expect(statuses['AWS::EC2::Instance'].keys).to eq(['i-1', 'i-2'])
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe CfDeployer::Driver::Instance do
|
|
115
|
+
it 'reports the instance state as a symbol' do
|
|
116
|
+
stub_aws(:ec2, describe_instances: { reservations: [{ instances: [{
|
|
117
|
+
instance_id: 'i-1', state: { code: 32, name: 'shutting-down' }, key_name: 'my-key',
|
|
118
|
+
public_ip_address: '1.2.3.4', private_ip_address: '10.0.0.1', image_id: 'ami-1'
|
|
119
|
+
}] }] })
|
|
120
|
+
|
|
121
|
+
status = CfDeployer::Driver::Instance.new('i-1').status
|
|
122
|
+
expect(status).to eq(status: :shutting_down, key_pair: 'my-key', public_ip_address: '1.2.3.4',
|
|
123
|
+
private_ip_address: '10.0.0.1', image_id: 'ami-1')
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe CfDeployer::Driver::Route53 do
|
|
128
|
+
let(:client) do
|
|
129
|
+
Aws::Route53::Client.new(stub_responses: {
|
|
130
|
+
list_hosted_zones_by_name: { hosted_zones: [{ id: '/hostedzone/Z1', name: 'target.com.', caller_reference: 'ref' }],
|
|
131
|
+
is_truncated: false, max_items: 100 },
|
|
132
|
+
list_resource_record_sets: { resource_record_sets: [
|
|
133
|
+
{ name: 'foo.target.com.', type: 'A', alias_target: { hosted_zone_id: 'Z2', dns_name: 'elb.aws.com.', evaluate_target_health: false } }
|
|
134
|
+
], is_truncated: false, max_items: 300 }
|
|
135
|
+
})
|
|
136
|
+
end
|
|
137
|
+
let(:driver) { CfDeployer::Driver::Route53.new(client) }
|
|
138
|
+
|
|
139
|
+
it 'looks up the zone and record by name rather than listing everything' do
|
|
140
|
+
expect(driver.find_alias_target('Target.com', 'Foo.target.com')).to eq('elb.aws.com')
|
|
141
|
+
|
|
142
|
+
zone_request = client.api_requests.find { |r| r[:operation_name] == :list_hosted_zones_by_name }
|
|
143
|
+
record_request = client.api_requests.find { |r| r[:operation_name] == :list_resource_record_sets }
|
|
144
|
+
expect(zone_request[:params]).to include(dns_name: 'target.com.')
|
|
145
|
+
expect(record_request[:params]).to include(hosted_zone_id: 'Z1', start_record_name: 'foo.target.com.')
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'deletes a record set' do
|
|
149
|
+
CfDeployer::Driver::DryRun.disable_for do
|
|
150
|
+
driver.delete_record_set('target.com', 'foo.target.com')
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
delete = client.api_requests.find { |r| r[:operation_name] == :change_resource_record_sets }
|
|
154
|
+
expect(delete[:params]).to eq(
|
|
155
|
+
hosted_zone_id: 'Z1', # the SDK strips the /hostedzone/ prefix
|
|
156
|
+
change_batch: { changes: [{ action: 'DELETE', resource_record_set: {
|
|
157
|
+
name: 'foo.target.com.', type: 'A',
|
|
158
|
+
alias_target: { hosted_zone_id: 'Z2', dns_name: 'elb.aws.com.', evaluate_target_health: false }
|
|
159
|
+
} }] }
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -2,9 +2,9 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe 'CloudFormation' do
|
|
4
4
|
let(:outputs) { [output1, output2] }
|
|
5
|
-
let(:output1) { double('output1', :
|
|
6
|
-
let(:output2) { double('output2', :
|
|
7
|
-
let(:parameters) { double('
|
|
5
|
+
let(:output1) { double('output1', :output_key => 'key1', :output_value => 'value1')}
|
|
6
|
+
let(:output2) { double('output2', :output_key => 'key2', :output_value => 'value2')}
|
|
7
|
+
let(:parameters) { [double('parameter1', :parameter_key => 'param1', :parameter_value => 'value1')] }
|
|
8
8
|
let(:resource_summaries) { [
|
|
9
9
|
{
|
|
10
10
|
:resource_type => 'AWS::AutoScaling::AutoScalingGroup',
|
|
@@ -22,24 +22,27 @@ describe 'CloudFormation' do
|
|
|
22
22
|
:resource_status => 'STATUS_2'
|
|
23
23
|
}
|
|
24
24
|
] }
|
|
25
|
-
let(:stack) { double('stack', :
|
|
25
|
+
let(:stack) { double('stack', :stack_name => 'testStack', :outputs => outputs, :parameters => parameters) }
|
|
26
|
+
let(:cloudFormationStacks) { [stack] }
|
|
26
27
|
let(:cloudFormation) {
|
|
27
28
|
double('cloudFormation',
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
:describe_stacks => double(:stacks => cloudFormationStacks),
|
|
30
|
+
:list_stack_resources => [double(:stack_resource_summaries => resource_summaries)]
|
|
31
|
+
)
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
before(:each) do
|
|
34
|
-
allow(
|
|
35
|
+
allow(Aws::CloudFormation::Client).to receive(:new) { cloudFormation }
|
|
36
|
+
allow(cloudFormation).to receive(:create_stack)
|
|
37
|
+
allow(cloudFormation).to receive(:update_stack)
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
it 'should get outputs of stack' do
|
|
38
|
-
CfDeployer::Driver::CloudFormation.new('testStack').outputs.
|
|
41
|
+
expect(CfDeployer::Driver::CloudFormation.new('testStack').outputs).to eq({'key1' => 'value1', 'key2' => 'value2'})
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
it 'should get parameters of stack' do
|
|
42
|
-
CfDeployer::Driver::CloudFormation.new('testStack').parameters.
|
|
45
|
+
expect(CfDeployer::Driver::CloudFormation.new('testStack').parameters).to eq({'param1' => 'value1'})
|
|
43
46
|
end
|
|
44
47
|
|
|
45
48
|
context 'update_stack' do
|
|
@@ -60,32 +63,31 @@ describe 'CloudFormation' do
|
|
|
60
63
|
result = cloud_formation.update_stack :template, {}
|
|
61
64
|
end
|
|
62
65
|
|
|
63
|
-
expect(result).to
|
|
66
|
+
expect(result).to be_falsey
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
it 'returns false if no updates were performed (because no difference in template)' do
|
|
67
70
|
cloud_formation = CfDeployer::Driver::CloudFormation.new 'my_stack'
|
|
68
|
-
expect(
|
|
71
|
+
expect(cloudFormation).to receive(:update_stack).and_raise(Aws::CloudFormation::Errors::ValidationError.new(nil, 'No updates are to be performed'))
|
|
69
72
|
result = nil
|
|
70
73
|
|
|
71
74
|
CfDeployer::Driver::DryRun.disable_for do
|
|
72
75
|
result = cloud_formation.update_stack :template, {}
|
|
73
76
|
end
|
|
74
77
|
|
|
75
|
-
expect(result).to
|
|
78
|
+
expect(result).to be_falsey
|
|
76
79
|
end
|
|
77
80
|
|
|
78
81
|
it 'returns true when updates are performed' do
|
|
79
82
|
cloud_formation = CfDeployer::Driver::CloudFormation.new 'my_stack'
|
|
80
|
-
aws_stack = double(:update => :did_something)
|
|
81
|
-
expect(cloud_formation).to receive(:aws_stack).and_return aws_stack
|
|
82
83
|
result = nil
|
|
83
84
|
|
|
84
85
|
CfDeployer::Driver::DryRun.disable_for do
|
|
85
86
|
result = cloud_formation.update_stack :template, {}
|
|
86
87
|
end
|
|
87
88
|
|
|
88
|
-
expect(
|
|
89
|
+
expect(cloudFormation).to have_received(:update_stack)
|
|
90
|
+
expect(result).to be_truthy
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
end
|
|
@@ -102,7 +104,7 @@ describe 'CloudFormation' do
|
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
CfDeployer::Driver::CloudFormation.new('testStack').resource_statuses.
|
|
107
|
+
expect(CfDeployer::Driver::CloudFormation.new('testStack').resource_statuses).to eq(expected)
|
|
106
108
|
end
|
|
107
109
|
end
|
|
108
110
|
end
|
|
@@ -11,8 +11,8 @@ describe 'DryRun' do
|
|
|
11
11
|
enabled_in_block = CfDeployer::Driver::DryRun.enabled?
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
expect(enabled_in_block).to
|
|
15
|
-
expect(CfDeployer::Driver::DryRun.enabled?).to
|
|
14
|
+
expect(enabled_in_block).to be_truthy
|
|
15
|
+
expect(CfDeployer::Driver::DryRun.enabled?).to be_falsey
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -27,8 +27,8 @@ describe 'DryRun' do
|
|
|
27
27
|
end
|
|
28
28
|
end.to raise_error('boom')
|
|
29
29
|
|
|
30
|
-
expect(enabled_in_block).to
|
|
31
|
-
expect(CfDeployer::Driver::DryRun.enabled?).to
|
|
30
|
+
expect(enabled_in_block).to be_truthy
|
|
31
|
+
expect(CfDeployer::Driver::DryRun.enabled?).to be_falsey
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
@@ -42,8 +42,8 @@ describe 'DryRun' do
|
|
|
42
42
|
enabled_in_block = CfDeployer::Driver::DryRun.enabled?
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
expect(enabled_in_block).to
|
|
46
|
-
expect(CfDeployer::Driver::DryRun.enabled?).to
|
|
45
|
+
expect(enabled_in_block).to be_falsey
|
|
46
|
+
expect(CfDeployer::Driver::DryRun.enabled?).to be_truthy
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
@@ -58,8 +58,8 @@ describe 'DryRun' do
|
|
|
58
58
|
end
|
|
59
59
|
end.to raise_error('boom')
|
|
60
60
|
|
|
61
|
-
expect(enabled_in_block).to
|
|
62
|
-
expect(CfDeployer::Driver::DryRun.enabled?).to
|
|
61
|
+
expect(enabled_in_block).to be_falsey
|
|
62
|
+
expect(CfDeployer::Driver::DryRun.enabled?).to be_truthy
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
@@ -2,10 +2,16 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
describe CfDeployer::Driver::Elb do
|
|
4
4
|
it 'should get dns name and hosted zone id' do
|
|
5
|
-
elb = double('
|
|
6
|
-
|
|
5
|
+
elb = double('elasticloadbalancing', :dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id')
|
|
6
|
+
|
|
7
|
+
aws = double('aws')
|
|
8
|
+
|
|
7
9
|
elb_name = 'myelb'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
load_balancer_descriptions = double('elb', :load_balancer_descriptions => [elb])
|
|
11
|
+
|
|
12
|
+
expect(Aws::ElasticLoadBalancing::Client).to receive(:new){aws}
|
|
13
|
+
expect(aws).to receive(:describe_load_balancers).with(hash_including(load_balancer_names: [elb_name])) { load_balancer_descriptions }
|
|
14
|
+
|
|
15
|
+
expect(CfDeployer::Driver::Elb.new.find_dns_and_zone_id(elb_name)).to eq({:dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id'})
|
|
10
16
|
end
|
|
11
17
|
end
|
|
@@ -11,14 +11,8 @@ describe CfDeployer::Driver::Instance do
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
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
14
|
instance = Fakes::Instance.new expected.merge( { :id => 'i-wxyz1234' } )
|
|
21
|
-
expect(
|
|
15
|
+
expect(Aws::EC2::Instance).to receive(:new).with('i-wxyz1234') { instance }
|
|
22
16
|
|
|
23
17
|
instance_status = CfDeployer::Driver::Instance.new('i-wxyz1234').status
|
|
24
18
|
|