vm_shepherd 1.2.0 → 1.3.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 +4 -4
- data/lib/vm_shepherd/aws_manager.rb +108 -32
- data/lib/vm_shepherd/shepherd.rb +6 -1
- data/lib/vm_shepherd/version.rb +1 -1
- data/spec/fixtures/shepherd/aws.yml +7 -0
- data/spec/vm_shepherd/aws_manager_spec.rb +250 -26
- data/spec/vm_shepherd/shepherd_spec.rb +16 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80b5490aee5b7e2b0432d36155d4e6b201692246
|
4
|
+
data.tar.gz: c7e60bc1d890dcb7f568c35f5d65306c4e269671
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7af47b2d7345668ef156e44bd9cabd60f15a561e66529f2ae7467af085725da385cae0126d81eee46ef8deab5d4f268e131b81eaea5d49ea9492a415838a38a2
|
7
|
+
data.tar.gz: 488c4b9d8393ce2ad2f93e5076820f9a57f8debb3ebeef571a306d1a8f5db4707f57517af700c4e4138bb1276d77b6c614c40ec4f8186f8e2b6c331910bb211c
|
@@ -8,6 +8,7 @@ module VmShepherd
|
|
8
8
|
AWS_REGION = 'us-east-1'
|
9
9
|
OPS_MANAGER_INSTANCE_TYPE = 'm3.medium'
|
10
10
|
DO_NOT_TERMINATE_TAG_KEY = 'do_not_terminate'
|
11
|
+
ELB_SECURITY_GROUP_NAME = 'ELB Security Group'
|
11
12
|
|
12
13
|
def initialize(env_config)
|
13
14
|
AWS.config(
|
@@ -22,7 +23,7 @@ module VmShepherd
|
|
22
23
|
template = File.read(cloudformation_template_file)
|
23
24
|
|
24
25
|
cfm = AWS::CloudFormation.new
|
25
|
-
stack = cfm.stacks.create(env_config.fetch(:stack_name), template, :
|
26
|
+
stack = cfm.stacks.create(env_config.fetch(:stack_name), template, parameters: env_config.fetch(:parameters), capabilities: ['CAPABILITY_IAM'])
|
26
27
|
|
27
28
|
retry_until(retry_limit: 360) do
|
28
29
|
status = stack.status
|
@@ -38,6 +39,10 @@ module VmShepherd
|
|
38
39
|
raise "Unexpected status for stack #{env_config.fetch(:stack_name)} : #{status}"
|
39
40
|
end
|
40
41
|
end
|
42
|
+
|
43
|
+
if (elb_config = env_config[:elb])
|
44
|
+
create_elb(stack, elb_config)
|
45
|
+
end
|
41
46
|
end
|
42
47
|
|
43
48
|
def deploy(ami_file_path:, vm_config:)
|
@@ -72,40 +77,21 @@ module VmShepherd
|
|
72
77
|
end
|
73
78
|
|
74
79
|
def clean_environment
|
75
|
-
|
76
|
-
|
77
|
-
|
80
|
+
[:public_subnet_id, :private_subnet_id].each do |subnet_id|
|
81
|
+
subnet_id = env_config.fetch(:outputs).fetch(subnet_id)
|
82
|
+
clear_subnet(subnet_id)
|
83
|
+
end
|
78
84
|
|
79
|
-
|
80
|
-
|
81
|
-
subnet.instances.each do |instance|
|
82
|
-
instance.attachments.each do |_, attachment|
|
83
|
-
volumes.push(attachment.volume) unless attachment.delete_on_termination
|
84
|
-
end
|
85
|
-
instance.terminate
|
86
|
-
end
|
85
|
+
if (elb_config = env_config[:elb])
|
86
|
+
delete_elb(elb_config[:name])
|
87
87
|
end
|
88
|
-
destroy_volumes(volumes)
|
89
88
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
case status
|
97
|
-
when 'DELETE_COMPLETE'
|
98
|
-
true
|
99
|
-
when 'DELETE_IN_PROGRESS'
|
100
|
-
false
|
101
|
-
else
|
102
|
-
raise "Unexpected status for stack #{env_config.fetch(:stack_name)} : #{status}"
|
103
|
-
end
|
104
|
-
rescue AWS::CloudFormation::Errors::ValidationError
|
105
|
-
raise if stack.exists?
|
106
|
-
true
|
107
|
-
end
|
108
|
-
end if stack
|
89
|
+
if (bucket_name = env_config.fetch(:outputs, {}).fetch(:s3_bucket_name, nil))
|
90
|
+
bucket = AWS::S3.new.buckets[bucket_name]
|
91
|
+
bucket.clear! if bucket && bucket.exists?
|
92
|
+
end
|
93
|
+
|
94
|
+
delete_stack(env_config.fetch(:stack_name))
|
109
95
|
end
|
110
96
|
|
111
97
|
def destroy(vm_config)
|
@@ -124,6 +110,26 @@ module VmShepherd
|
|
124
110
|
private
|
125
111
|
attr_reader :env_config
|
126
112
|
|
113
|
+
def subnet_id(stack, elb_config)
|
114
|
+
stack.outputs.detect { |o| o.key == elb_config[:stack_output_keys][:subnet_id] }.value
|
115
|
+
end
|
116
|
+
|
117
|
+
def vpc_id(stack, elb_config)
|
118
|
+
stack.outputs.detect { |o| o.key == elb_config[:stack_output_keys][:vpc_id] }.value
|
119
|
+
end
|
120
|
+
|
121
|
+
def clear_subnet(subnet_id)
|
122
|
+
subnet = AWS.ec2.subnets[subnet_id]
|
123
|
+
volumes = []
|
124
|
+
subnet.instances.each do |instance|
|
125
|
+
instance.attachments.each do |_, attachment|
|
126
|
+
volumes.push(attachment.volume) unless attachment.delete_on_termination
|
127
|
+
end
|
128
|
+
instance.terminate
|
129
|
+
end
|
130
|
+
destroy_volumes(volumes)
|
131
|
+
end
|
132
|
+
|
127
133
|
def destroy_volumes(volumes)
|
128
134
|
volumes.each do |volume|
|
129
135
|
begin
|
@@ -134,5 +140,75 @@ module VmShepherd
|
|
134
140
|
end
|
135
141
|
end
|
136
142
|
end
|
143
|
+
|
144
|
+
def delete_elb(elb_name)
|
145
|
+
if (elb = AWS::ELB.new.load_balancers.find { |lb| lb.name == elb_name })
|
146
|
+
sg = elb.security_groups.first
|
147
|
+
net_interfaces = AWS.ec2.network_interfaces.select { |ni| ni.security_groups.map(&:id).include? sg.id }
|
148
|
+
elb.delete
|
149
|
+
retry_until do
|
150
|
+
!elb.exists? && !net_interfaces.map(&:exists?).any?
|
151
|
+
end
|
152
|
+
sg.delete
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def create_elb(stack, elb_config)
|
157
|
+
elb = AWS::ELB.new
|
158
|
+
elb_params = {
|
159
|
+
load_balancer_name: elb_config[:name],
|
160
|
+
listeners: [],
|
161
|
+
subnets: [subnet_id(stack, elb_config)],
|
162
|
+
security_groups: [create_security_group(stack, elb_config).security_group_id]
|
163
|
+
}
|
164
|
+
|
165
|
+
elb_config[:port_mappings].each do |port_mapping|
|
166
|
+
elb_params[:listeners] << {
|
167
|
+
protocol: 'TCP', load_balancer_port: port_mapping[0],
|
168
|
+
instance_protocol: 'TCP', instance_port: port_mapping[1]
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
elb.client.create_load_balancer(elb_params)
|
173
|
+
end
|
174
|
+
|
175
|
+
def create_security_group(stack, elb_config)
|
176
|
+
vpc_id = vpc_id(stack, elb_config)
|
177
|
+
sg_params = {
|
178
|
+
group_name: stack.name,
|
179
|
+
description: 'ELB Security Group',
|
180
|
+
vpc_id: vpc_id,
|
181
|
+
}
|
182
|
+
|
183
|
+
security_group_response = AWS.ec2.client.create_security_group(sg_params)
|
184
|
+
|
185
|
+
AWS.ec2.security_groups[security_group_response[:group_id]].tap do |security_group|
|
186
|
+
elb_config[:port_mappings].each do |port_mapping|
|
187
|
+
security_group.authorize_ingress(:tcp, port_mapping[0], '0.0.0.0/0')
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete_stack(stack_name)
|
193
|
+
cfm = AWS::CloudFormation.new
|
194
|
+
stack = cfm.stacks[stack_name]
|
195
|
+
stack.delete
|
196
|
+
retry_until(retry_limit: 360) do
|
197
|
+
begin
|
198
|
+
status = stack.status
|
199
|
+
case status
|
200
|
+
when 'DELETE_COMPLETE'
|
201
|
+
true
|
202
|
+
when 'DELETE_IN_PROGRESS'
|
203
|
+
false
|
204
|
+
else
|
205
|
+
raise "Unexpected status for stack #{stack_name} : #{status}"
|
206
|
+
end
|
207
|
+
rescue AWS::CloudFormation::Errors::ValidationError
|
208
|
+
raise if stack.exists?
|
209
|
+
true
|
210
|
+
end
|
211
|
+
end if stack
|
212
|
+
end
|
137
213
|
end
|
138
214
|
end
|
data/lib/vm_shepherd/shepherd.rb
CHANGED
@@ -202,7 +202,12 @@ module VmShepherd
|
|
202
202
|
aws_secret_key: settings.vm_shepherd.env_config.aws_secret_key,
|
203
203
|
json_file: settings.vm_shepherd.env_config.json_file,
|
204
204
|
parameters: settings.vm_shepherd.env_config.parameters_as_a_hash,
|
205
|
-
outputs: settings.vm_shepherd.env_config.outputs.to_h
|
205
|
+
outputs: settings.vm_shepherd.env_config.outputs.to_h,
|
206
|
+
elb: {
|
207
|
+
name: settings.vm_shepherd.env_config.elb.name,
|
208
|
+
port_mappings: settings.vm_shepherd.env_config.elb.port_mappings,
|
209
|
+
stack_output_keys: settings.vm_shepherd.env_config.elb.stack_output_keys.to_h,
|
210
|
+
},
|
206
211
|
}
|
207
212
|
)
|
208
213
|
end
|
data/lib/vm_shepherd/version.rb
CHANGED
@@ -13,6 +13,13 @@ vm_shepherd:
|
|
13
13
|
security_group: security-group-id
|
14
14
|
public_subnet_id: public-subnet-id
|
15
15
|
private_subnet_id: private-subnet-id
|
16
|
+
s3_bucket_name: bucket-name
|
17
|
+
elb:
|
18
|
+
name: some-elb-name
|
19
|
+
port_mappings: [[1111,11]]
|
20
|
+
stack_output_keys:
|
21
|
+
vpc_id: CloudFormationVpcIdOutputKey
|
22
|
+
subnet_id: CloudFormationSubnetIdOutputKey
|
16
23
|
vm_configs:
|
17
24
|
- vm_name: vm-name
|
18
25
|
- vm_name: vm-name-2
|
@@ -23,10 +23,13 @@ module VmShepherd
|
|
23
23
|
security_group: 'security-group-id',
|
24
24
|
public_subnet_id: 'public-subnet-id',
|
25
25
|
private_subnet_id: 'private-subnet-id',
|
26
|
-
},
|
27
|
-
}
|
26
|
+
}.merge(extra_outputs),
|
27
|
+
}.merge(extra_configs)
|
28
28
|
end
|
29
29
|
|
30
|
+
let(:extra_outputs) { {} }
|
31
|
+
let(:extra_configs) { {} }
|
32
|
+
|
30
33
|
let(:vm_config) do
|
31
34
|
{
|
32
35
|
vm_name: 'some-vm-name',
|
@@ -51,43 +54,121 @@ module VmShepherd
|
|
51
54
|
let(:cfm) { instance_double(AWS::CloudFormation, stacks: stack_collection) }
|
52
55
|
let(:stack) { instance_double(AWS::CloudFormation::Stack, status: 'CREATE_COMPLETE') }
|
53
56
|
let(:stack_collection) { instance_double(AWS::CloudFormation::StackCollection) }
|
57
|
+
let(:elb) { instance_double(AWS::ELB, client: elb_client) }
|
58
|
+
let(:elb_client) { double(AWS::ELB::Client) }
|
54
59
|
|
55
60
|
before do
|
56
61
|
allow(AWS::CloudFormation).to receive(:new).and_return(cfm)
|
62
|
+
allow(AWS::ELB).to receive(:new).and_return(elb)
|
57
63
|
allow(stack_collection).to receive(:create).and_return(stack)
|
58
64
|
end
|
59
65
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
+
describe 'cloudformation' do
|
67
|
+
it 'creates the stack with the correct parameters' do
|
68
|
+
expect(stack_collection).to receive(:create).with(
|
69
|
+
'aws-stack-name',
|
70
|
+
'{}',
|
71
|
+
parameters: {
|
72
|
+
'some_parameter' => 'some-answer',
|
73
|
+
},
|
74
|
+
capabilities: ['CAPABILITY_IAM']
|
75
|
+
)
|
76
|
+
ami_manager.prepare_environment(cloudformation_template_file.path)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'waits for the stack to finish creating' do
|
80
|
+
expect(stack).to receive(:status).and_return('CREATE_IN_PROGRESS', 'CREATE_IN_PROGRESS', 'CREATE_IN_PROGRESS', 'CREATE_COMPLETE')
|
81
|
+
|
82
|
+
ami_manager.prepare_environment(cloudformation_template_file.path)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'stops retrying after 360 times' do
|
86
|
+
expect(stack).to receive(:status).and_return('CREATE_IN_PROGRESS').
|
87
|
+
exactly(360).times
|
88
|
+
|
89
|
+
expect { ami_manager.prepare_environment(cloudformation_template_file.path) }.to raise_error(AwsManager::RetryLimitExceeded)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'aborts if stack fails to create' do
|
93
|
+
expect(stack).to receive(:status).and_return('CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_COMPLETE').ordered
|
94
|
+
expect(stack).to receive(:delete)
|
95
|
+
expect {
|
96
|
+
ami_manager.prepare_environment(cloudformation_template_file.path)
|
97
|
+
}.to raise_error('Unexpected status for stack aws-stack-name : ROLLBACK_COMPLETE')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when the elb setting is present' do
|
102
|
+
let(:extra_configs) do
|
103
|
+
{
|
104
|
+
elb: {
|
105
|
+
name: 'elb-name',
|
106
|
+
port_mappings: [[1111, 11]],
|
107
|
+
stack_output_keys: {
|
108
|
+
vpc_id: 'vpc_id',
|
109
|
+
subnet_id: 'private_subnet',
|
110
|
+
},
|
66
111
|
},
|
67
|
-
|
112
|
+
}
|
113
|
+
end
|
114
|
+
let(:stack) do
|
115
|
+
instance_double(AWS::CloudFormation::Stack,
|
116
|
+
name: 'fake-stack-name',
|
117
|
+
creation_time: Time.utc(2015, 5, 29),
|
118
|
+
status: 'CREATE_COMPLETE',
|
119
|
+
outputs: stack_outputs
|
68
120
|
)
|
69
|
-
|
70
|
-
|
121
|
+
end
|
122
|
+
let(:stack_outputs) do
|
123
|
+
[
|
124
|
+
instance_double(AWS::CloudFormation::StackOutput, key: 'private_subnet', value: 'fake-subnet-id'),
|
125
|
+
instance_double(AWS::CloudFormation::StackOutput, key: 'vpc_id', value: 'fake-vpc-id'),
|
126
|
+
]
|
127
|
+
end
|
128
|
+
let(:ec2_client) { double(AWS::EC2::Client) }
|
129
|
+
let(:create_security_group_response) do
|
130
|
+
{:group_id => 'elb-security-group'}
|
131
|
+
end
|
132
|
+
let(:security_groups) do
|
133
|
+
{
|
134
|
+
'elb-security-group' => elb_security_group,
|
135
|
+
}
|
136
|
+
end
|
137
|
+
let(:elb_security_group) { instance_double(AWS::EC2::SecurityGroup, security_group_id: 'elb-security-group-id') }
|
71
138
|
|
72
|
-
|
73
|
-
|
139
|
+
before do
|
140
|
+
allow(ec2).to receive(:client).and_return(ec2_client)
|
141
|
+
allow(ec2_client).to receive(:create_security_group).and_return(create_security_group_response)
|
142
|
+
allow(ec2).to receive(:security_groups).and_return(security_groups)
|
143
|
+
allow(elb_security_group).to receive(:authorize_ingress)
|
144
|
+
allow(elb_client).to receive(:create_load_balancer)
|
145
|
+
end
|
74
146
|
|
75
|
-
|
76
|
-
|
147
|
+
it 'creates and attaches a security group' do
|
148
|
+
security_group_args = {
|
149
|
+
group_name: 'fake-stack-name',
|
150
|
+
description: 'ELB Security Group',
|
151
|
+
vpc_id: 'fake-vpc-id',
|
152
|
+
}
|
153
|
+
expect(ec2_client).to receive(:create_security_group).with(security_group_args).and_return(create_security_group_response)
|
154
|
+
expect(elb_security_group).to receive(:authorize_ingress).with(:tcp, 1111, '0.0.0.0/0')
|
77
155
|
|
78
|
-
|
79
|
-
|
80
|
-
exactly(360).times
|
156
|
+
ami_manager.prepare_environment(cloudformation_template_file.path)
|
157
|
+
end
|
81
158
|
|
82
|
-
|
83
|
-
|
159
|
+
it 'attaches an elb with the name of the stack' do
|
160
|
+
elb_params = {
|
161
|
+
load_balancer_name: 'elb-name',
|
162
|
+
listeners: [
|
163
|
+
{protocol: 'TCP', load_balancer_port: 1111, instance_protocol: 'TCP', instance_port: 11},
|
164
|
+
],
|
165
|
+
subnets: ['fake-subnet-id'],
|
166
|
+
security_groups: ['elb-security-group-id']
|
167
|
+
}
|
168
|
+
expect(elb_client).to receive(:create_load_balancer).with(elb_params)
|
84
169
|
|
85
|
-
it 'aborts if stack fails to create' do
|
86
|
-
expect(stack).to receive(:status).and_return('CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_COMPLETE').ordered
|
87
|
-
expect(stack).to receive(:delete)
|
88
|
-
expect {
|
89
170
|
ami_manager.prepare_environment(cloudformation_template_file.path)
|
90
|
-
|
171
|
+
end
|
91
172
|
end
|
92
173
|
end
|
93
174
|
|
@@ -183,6 +264,9 @@ module VmShepherd
|
|
183
264
|
instance_double(AWS::EC2::Attachment, volume: instance1_volume, delete_on_termination: true)
|
184
265
|
end
|
185
266
|
|
267
|
+
let(:buckets) { instance_double(AWS::S3::BucketCollection) }
|
268
|
+
let(:s3_client) { instance_double(AWS::S3, buckets: buckets) }
|
269
|
+
|
186
270
|
before do
|
187
271
|
allow(AWS::CloudFormation).to receive(:new).and_return(cfm)
|
188
272
|
allow(stack_collection).to receive(:[]).and_return(stack)
|
@@ -196,6 +280,9 @@ module VmShepherd
|
|
196
280
|
|
197
281
|
allow(instance1).to receive(:terminate)
|
198
282
|
allow(instance2).to receive(:terminate)
|
283
|
+
|
284
|
+
allow(AWS::S3).to receive(:new).and_return(s3_client)
|
285
|
+
allow(buckets).to receive(:[]).and_return(instance_double(AWS::S3::Bucket, exists?: false))
|
199
286
|
end
|
200
287
|
|
201
288
|
it 'terminates all VMs in the subnet' do
|
@@ -247,6 +334,118 @@ module VmShepherd
|
|
247
334
|
}.not_to raise_error
|
248
335
|
end
|
249
336
|
|
337
|
+
it 'when an elb is not configured' do
|
338
|
+
expect(AWS::ELB).not_to receive(:new)
|
339
|
+
ami_manager.clean_environment
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'when there is no s3 bucket configuration' do
|
343
|
+
expect_any_instance_of(AWS::S3::Bucket).not_to receive(:clear!)
|
344
|
+
ami_manager.clean_environment
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'does not look up buckets when there is no name' do
|
348
|
+
expect(buckets).to_not receive(:[])
|
349
|
+
ami_manager.clean_environment
|
350
|
+
end
|
351
|
+
|
352
|
+
context 'when a subnet is not provided' do
|
353
|
+
#the sdk never returns nil when looking up a subnet and its instances
|
354
|
+
let(:subnet2) { instance_double(AWS::EC2::Subnet, instances: []) }
|
355
|
+
|
356
|
+
it 'only deletes instance 1' do
|
357
|
+
expect(instance1).to receive(:terminate)
|
358
|
+
expect(instance2).not_to receive(:terminate)
|
359
|
+
|
360
|
+
ami_manager.clean_environment
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
context 'when an elb is configured' do
|
365
|
+
let(:extra_configs) do
|
366
|
+
{
|
367
|
+
elb: {
|
368
|
+
name: 'elb-name',
|
369
|
+
stack_output_keys: {
|
370
|
+
subnet_id: 'private_subnet',
|
371
|
+
},
|
372
|
+
},
|
373
|
+
}
|
374
|
+
end
|
375
|
+
|
376
|
+
let(:elb) { instance_double(AWS::ELB, load_balancers: [load_balancer_to_delete, other_load_balancer]) }
|
377
|
+
let(:load_balancer_to_delete) do
|
378
|
+
instance_double(AWS::ELB::LoadBalancer,
|
379
|
+
name: 'elb-name',
|
380
|
+
security_groups: [elb_security_group],
|
381
|
+
exists?: false,
|
382
|
+
)
|
383
|
+
end
|
384
|
+
let(:other_load_balancer) { instance_double(AWS::ELB::LoadBalancer, name: 'other-elb-name') }
|
385
|
+
let(:elb_security_group) { instance_double(AWS::EC2::SecurityGroup, name: 'elb-security-group', id: 'sg-id') }
|
386
|
+
let(:network_interface_1) do
|
387
|
+
instance_double(AWS::EC2::NetworkInterface,
|
388
|
+
security_groups: [elb_security_group],
|
389
|
+
exists?: false,
|
390
|
+
)
|
391
|
+
end
|
392
|
+
let(:network_interface_2) do
|
393
|
+
instance_double(AWS::EC2::NetworkInterface,
|
394
|
+
security_groups: [elb_security_group],
|
395
|
+
exists?: false,
|
396
|
+
)
|
397
|
+
end
|
398
|
+
|
399
|
+
before do
|
400
|
+
allow(AWS::ELB).to receive(:new).and_return(elb)
|
401
|
+
allow(ec2).to receive(:network_interfaces).and_return([network_interface_1, network_interface_2])
|
402
|
+
allow(load_balancer_to_delete).to receive(:delete)
|
403
|
+
allow(elb_security_group).to receive(:delete)
|
404
|
+
end
|
405
|
+
|
406
|
+
it 'waits for the elb to be deleted' do
|
407
|
+
expect(load_balancer_to_delete).to receive(:exists?).and_return(true).
|
408
|
+
exactly(60).times
|
409
|
+
|
410
|
+
expect(elb_security_group).not_to receive(:delete).ordered
|
411
|
+
expect { ami_manager.clean_environment }.to raise_error(AwsManager::RetryLimitExceeded)
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'waits for the network interfaces to be deleted' do
|
415
|
+
allow(load_balancer_to_delete).to receive(:exists?).and_return(false)
|
416
|
+
|
417
|
+
expect(network_interface_1).to receive(:exists?).and_return(false).
|
418
|
+
exactly(60).times
|
419
|
+
|
420
|
+
expect(network_interface_2).to receive(:exists?).and_return(true).
|
421
|
+
exactly(60).times
|
422
|
+
|
423
|
+
expect(elb_security_group).not_to receive(:delete).ordered
|
424
|
+
expect { ami_manager.clean_environment }.to raise_error(AwsManager::RetryLimitExceeded)
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'terminates the ELB then removes the security group' do
|
428
|
+
expect(load_balancer_to_delete).to receive(:delete).ordered
|
429
|
+
expect(elb_security_group).to receive(:delete).ordered
|
430
|
+
|
431
|
+
ami_manager.clean_environment
|
432
|
+
end
|
433
|
+
|
434
|
+
it 'leaves unknown ELBs alone' do
|
435
|
+
expect(other_load_balancer).not_to receive(:delete)
|
436
|
+
|
437
|
+
ami_manager.clean_environment
|
438
|
+
end
|
439
|
+
|
440
|
+
context 'when the ELB does not exist' do
|
441
|
+
let(:elb) { instance_double(AWS::ELB, load_balancers: []) }
|
442
|
+
|
443
|
+
it 'does not throw an error' do
|
444
|
+
expect { ami_manager.clean_environment }.not_to raise_error
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
250
449
|
context 'when the instance has volumes that are NOT delete_on_termination' do
|
251
450
|
let(:instance1_attachment) do
|
252
451
|
instance_double(AWS::EC2::Attachment, volume: instance1_volume, delete_on_termination: false)
|
@@ -267,7 +466,6 @@ module VmShepherd
|
|
267
466
|
before do
|
268
467
|
expect(instance1_volume).to receive(:delete).and_raise(AWS::EC2::Errors::VolumeInUse)
|
269
468
|
expect(instance1_volume).to receive(:delete).and_return(nil)
|
270
|
-
allow(ami_manager).to receive(:sleep)
|
271
469
|
end
|
272
470
|
|
273
471
|
it 'retries the delete' do
|
@@ -275,6 +473,32 @@ module VmShepherd
|
|
275
473
|
end
|
276
474
|
end
|
277
475
|
end
|
476
|
+
|
477
|
+
context 'when there is an s3 bucket configuration' do
|
478
|
+
let(:bucket) { instance_double(AWS::S3::Bucket) }
|
479
|
+
let(:extra_outputs) { {s3_bucket_name: bucket_name} }
|
480
|
+
let(:bucket_name) { 'bucket-name' }
|
481
|
+
|
482
|
+
before { allow(buckets).to receive(:[]).with(bucket_name).and_return(bucket) }
|
483
|
+
|
484
|
+
context 'and the bucket does exist' do
|
485
|
+
before { allow(bucket).to receive(:exists?).and_return(true) }
|
486
|
+
|
487
|
+
it 'clears the bucket' do
|
488
|
+
expect(bucket).to receive(:clear!)
|
489
|
+
ami_manager.clean_environment
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
context 'and the bucket does not exist' do
|
494
|
+
before { allow(bucket).to receive(:exists?).and_return(false) }
|
495
|
+
|
496
|
+
it 'fails silently' do
|
497
|
+
expect(bucket).not_to receive(:clear!)
|
498
|
+
ami_manager.clean_environment
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
278
502
|
end
|
279
503
|
|
280
504
|
describe '#destroy' do
|
@@ -9,7 +9,7 @@ module VmShepherd
|
|
9
9
|
let(:settings) do
|
10
10
|
RecursiveOpenStruct.new(YAML.load_file(File.join(SPEC_ROOT, 'fixtures', 'shepherd', settings_fixture_name)), recurse_over_arrays: true)
|
11
11
|
end
|
12
|
-
let(:
|
12
|
+
let(:aws_env_config) do
|
13
13
|
{
|
14
14
|
stack_name: 'aws-stack-name',
|
15
15
|
aws_access_key: 'aws-access-key',
|
@@ -22,8 +22,17 @@ module VmShepherd
|
|
22
22
|
ssh_key_name: 'ssh-key-name',
|
23
23
|
security_group: 'security-group-id',
|
24
24
|
public_subnet_id: 'public-subnet-id',
|
25
|
-
private_subnet_id: 'private-subnet-id'
|
26
|
-
|
25
|
+
private_subnet_id: 'private-subnet-id',
|
26
|
+
s3_bucket_name: 'bucket-name',
|
27
|
+
},
|
28
|
+
elb: {
|
29
|
+
name: 'some-elb-name',
|
30
|
+
port_mappings: [[1111, 11]],
|
31
|
+
stack_output_keys: {
|
32
|
+
vpc_id: 'CloudFormationVpcIdOutputKey',
|
33
|
+
subnet_id: 'CloudFormationSubnetIdOutputKey',
|
34
|
+
},
|
35
|
+
},
|
27
36
|
}
|
28
37
|
end
|
29
38
|
|
@@ -168,7 +177,7 @@ module VmShepherd
|
|
168
177
|
let(:last_aws_options) { {vm_name: 'vm-name-2'} }
|
169
178
|
|
170
179
|
it 'uses AwsManager to launch a VM' do
|
171
|
-
expect(AwsManager).to receive(:new).with(
|
180
|
+
expect(AwsManager).to receive(:new).with(aws_env_config).and_return(aws_manager)
|
172
181
|
expect(aws_manager).to receive(:deploy).with(ami_file_path: first_ami_file_path, vm_config: first_aws_options)
|
173
182
|
expect(aws_manager).to receive(:deploy).with(ami_file_path: last_ami_file_path, vm_config: last_aws_options)
|
174
183
|
|
@@ -333,7 +342,7 @@ module VmShepherd
|
|
333
342
|
let(:last_ami_options) { {vm_name: 'vm-name-2'} }
|
334
343
|
|
335
344
|
it 'uses AwsManager to destroy a VM' do
|
336
|
-
expect(AwsManager).to receive(:new).with(
|
345
|
+
expect(AwsManager).to receive(:new).with(aws_env_config).and_return(aws_manager)
|
337
346
|
expect(aws_manager).to receive(:destroy).with(first_ami_options)
|
338
347
|
expect(aws_manager).to receive(:destroy).with(last_ami_options)
|
339
348
|
|
@@ -500,7 +509,7 @@ module VmShepherd
|
|
500
509
|
let(:aws_manager) { instance_double(AwsManager) }
|
501
510
|
|
502
511
|
it 'uses AwsManager to destroy a VM' do
|
503
|
-
expect(AwsManager).to receive(:new).with(
|
512
|
+
expect(AwsManager).to receive(:new).with(aws_env_config).and_return(aws_manager)
|
504
513
|
expect(aws_manager).to receive(:clean_environment)
|
505
514
|
manager.clean_environment
|
506
515
|
end
|
@@ -552,7 +561,7 @@ module VmShepherd
|
|
552
561
|
let(:ams_manager) { instance_double(AwsManager) }
|
553
562
|
|
554
563
|
it 'uses AwsManager to create an environment' do
|
555
|
-
expect(AwsManager).to receive(:new).with(
|
564
|
+
expect(AwsManager).to receive(:new).with(aws_env_config).and_return(ams_manager)
|
556
565
|
expect(ams_manager).to receive(:prepare_environment).with('cloudformation.json')
|
557
566
|
manager.prepare_environment
|
558
567
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vm_shepherd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ops Manager Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-v1
|