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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +29 -0
  3. data/.github/workflows/publish.yml +40 -0
  4. data/.rspec +4 -0
  5. data/ChangeLog.md +7 -0
  6. data/QUICKSTART.md +1 -1
  7. data/Rakefile +1 -1
  8. data/cf_deployer.gemspec +16 -10
  9. data/fixtures/vcr_cassettes/aws_call_count/driver/auto_scaling_group/healthy_instance_count.yml +2 -2
  10. data/lib/cf_deployer/cli.rb +1 -1
  11. data/lib/cf_deployer/config_loader.rb +2 -2
  12. data/lib/cf_deployer/config_validation.rb +1 -1
  13. data/lib/cf_deployer/driver/auto_scaling_group.rb +22 -26
  14. data/lib/cf_deployer/driver/cloud_formation_driver.rb +28 -18
  15. data/lib/cf_deployer/driver/elb_driver.rb +11 -4
  16. data/lib/cf_deployer/driver/instance.rb +4 -3
  17. data/lib/cf_deployer/driver/route53_driver.rb +17 -9
  18. data/lib/cf_deployer/logger.rb +10 -11
  19. data/lib/cf_deployer/stack.rb +18 -30
  20. data/lib/cf_deployer/status_presenter.rb +1 -1
  21. data/lib/cf_deployer/version.rb +1 -1
  22. data/lib/cf_deployer.rb +10 -7
  23. data/spec/aws_call_count_spec_helper.rb +14 -2
  24. data/spec/fakes/instance.rb +5 -4
  25. data/spec/fakes/route53_client.rb +4 -7
  26. data/spec/spec_helper.rb +11 -2
  27. data/spec/unit/application_spec.rb +9 -9
  28. data/spec/unit/component_spec.rb +3 -3
  29. data/spec/unit/config_loader_spec.rb +70 -69
  30. data/spec/unit/deployment_strategy/auto_scaling_group_swap_spec.rb +10 -10
  31. data/spec/unit/deployment_strategy/base_spec.rb +17 -18
  32. data/spec/unit/deployment_strategy/cname_swap_spec.rb +9 -9
  33. data/spec/unit/deployment_strategy/create_or_update_spec.rb +8 -8
  34. data/spec/unit/driver/auto_scaling_group_spec.rb +75 -65
  35. data/spec/unit/driver/aws_sdk_v3_spec.rb +163 -0
  36. data/spec/unit/driver/cloud_formation_spec.rb +19 -17
  37. data/spec/unit/driver/dry_run_spec.rb +8 -8
  38. data/spec/unit/driver/elb_spec.rb +10 -4
  39. data/spec/unit/driver/instance_spec.rb +1 -7
  40. data/spec/unit/driver/route53_spec.rb +34 -25
  41. data/spec/unit/hook_spec.rb +3 -3
  42. data/spec/unit/stack_spec.rb +22 -21
  43. metadata +134 -56
  44. 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(AWS::AutoScaling).to receive(:new) { scaling }
18
- allow(group).to receive(:load_balancers) { [] }
19
- allow(group).to receive(:auto_scaling_instances) { [] }
20
- allow(group).to receive(:ec2_instances) { [] }
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.should eq({ min: 1, max: 4, desired: 2})
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(:auto_scaling_instances){[instance1, instance2, instance3, instance4]}
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(:auto_scaling_instances){[instance1]}
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(:load_balancers).and_return([])
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 = { state: 'InService', instance: double('i1', id: 'instance1') }
85
- health2 = { state: 'OutOfService', instance: double('i2', id: 'instance2') }
86
- health3 = { state: 'InService', instance: double('i3', id: 'instance3') }
87
- health4 = { state: 'InService', instance: double('i4', id: 'instance4') }
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
- instance_collection = double('instance_collection', health: [health1, health2, health3, health4])
90
- elb = double('elb', instances: instance_collection)
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 = { state: 'InService', instance: double('i1', id: 'instance1') }
100
- health2 = { state: 'InService', instance: double('i2', id: 'instance2') }
101
- health3 = { state: 'InService', instance: double('i3', id: 'instance3') }
102
- health4 = { state: 'InService', instance: double('i4', id: 'instance4') }
103
- health5 = { state: 'InService', instance: double('i5', id: 'instance5') }
104
-
105
- instance_collection1 = double('instance_collection1', health: [health1, health2, health3])
106
- instance_collection2 = double('instance_collection2', health: [health2, health3, health4])
107
- instance_collection3 = double('instance_collection3', health: [health2, health3, health5])
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 = { state: 'OutOfService', instance: double('i1', id: 'instance1') }
119
- health12 = { state: 'InService', instance: double('i2', id: 'instance2') }
120
- health13 = { state: 'InService', instance: double('i3', id: 'instance3') }
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
- instance_collection1 = double('instance_collection1', health: [health11, health12, health13])
131
- instance_collection2 = double('instance_collection2', health: [health21, health22, health23])
132
- instance_collection3 = double('instance_collection3', health: [health31, health32, health33])
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
- elb1 = double('elb1', instances: instance_collection1)
135
- elb2 = double('elb2', instances: instance_collection2)
136
- elb3 = double('elb3', instances: instance_collection3)
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(group).to receive(:load_balancers).and_return([ elb1, elb2, elb3 ])
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(:load_balancers).and_return(double('elb', empty?: false))
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(:auto_scaling_instances){[instance1, instance2, instance3]}
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
- aws_instance = double AWS::EC2::Instance
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(aws_instance) { cfd_instance }
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( { 'i-abcd1234' => returned_status } )
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 be_true
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 be_false
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 be_true
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', :key => 'key1', :value => 'value1')}
6
- let(:output2) { double('output2', :key => 'key2', :value => 'value2')}
7
- let(:parameters) { double('parameters')}
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', :outputs => outputs, :parameters => parameters, :resource_summaries => resource_summaries) }
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
- :stacks =>
29
- {'testStack' => stack
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(AWS::CloudFormation).to receive(:new) { cloudFormation }
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.should eq({'key1' => 'value1', 'key2' => 'value2'})
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.should eq(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 be_false
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(cloud_formation).to receive(:aws_stack).and_raise(AWS::CloudFormation::Errors::ValidationError.new('No updates are to be performed'))
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 be_false
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(result).to be_true
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.should eq(expected)
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 be_true
15
- expect(CfDeployer::Driver::DryRun.enabled?).to be_false
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 be_true
31
- expect(CfDeployer::Driver::DryRun.enabled?).to be_false
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 be_false
46
- expect(CfDeployer::Driver::DryRun.enabled?).to be_true
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 be_false
62
- expect(CfDeployer::Driver::DryRun.enabled?).to be_true
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('elb', :dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id')
6
- aws = double('aws', :load_balancers => {'myelb' => elb})
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
- expect(AWS::ELB).to receive(:new){aws}
9
- CfDeployer::Driver::Elb.new.find_dns_and_zone_id(elb_name).should eq({:dns_name => 'mydns', :canonical_hosted_zone_name_id => 'zone_id'})
10
+ 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(instance_collection).to receive(:[]).with(instance.id) { instance }
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