chef-provisioning-aws 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -0
- data/Rakefile +5 -0
- data/lib/chef/provider/aws_ebs_volume.rb +14 -4
- data/lib/chef/provider/aws_image.rb +31 -0
- data/lib/chef/provider/aws_instance.rb +14 -0
- data/lib/chef/provider/aws_load_balancer.rb +9 -0
- data/lib/chef/provider/aws_network_interface.rb +209 -0
- data/lib/chef/provider/aws_security_group.rb +9 -4
- data/lib/chef/provider/aws_subnet.rb +16 -1
- data/lib/chef/provider/aws_vpc.rb +16 -0
- data/lib/chef/provisioning/aws_driver/aws_provider.rb +44 -0
- data/lib/chef/provisioning/aws_driver/aws_resource.rb +1 -1
- data/lib/chef/provisioning/aws_driver/driver.rb +6 -5
- data/lib/chef/provisioning/aws_driver/version.rb +1 -1
- data/lib/chef/resource/aws_image.rb +1 -2
- data/lib/chef/resource/aws_instance.rb +1 -2
- data/lib/chef/resource/aws_load_balancer.rb +1 -1
- data/lib/chef/resource/aws_network_interface.rb +23 -5
- data/lib/chef/resource/aws_vpc.rb +0 -8
- data/spec/aws_support.rb +235 -0
- data/spec/aws_support/aws_resource_run_wrapper.rb +45 -0
- data/spec/aws_support/deep_matcher.rb +40 -0
- data/spec/aws_support/deep_matcher/fuzzy_match_objects.rb +57 -0
- data/spec/aws_support/deep_matcher/match_values_failure_messages.rb +145 -0
- data/spec/aws_support/deep_matcher/matchable_array.rb +24 -0
- data/spec/aws_support/deep_matcher/matchable_object.rb +25 -0
- data/spec/aws_support/deep_matcher/rspec_monkeypatches.rb +25 -0
- data/spec/aws_support/delayed_stream.rb +41 -0
- data/spec/aws_support/matchers/create_an_aws_object.rb +60 -0
- data/spec/aws_support/matchers/update_an_aws_object.rb +66 -0
- data/spec/integration/aws_ebs_volume_spec.rb +31 -0
- data/spec/integration/aws_key_pair_spec.rb +21 -0
- data/spec/integration/aws_route_table_spec.rb +40 -0
- data/spec/integration/aws_security_group_spec.rb +7 -5
- data/spec/integration/aws_subnet_spec.rb +56 -0
- data/spec/integration/aws_vpc_spec.rb +109 -0
- data/spec/integration/machine_batch_spec.rb +36 -0
- data/spec/integration/machine_image_spec.rb +49 -0
- data/spec/integration/machine_spec.rb +64 -0
- data/spec/spec_helper.rb +8 -2
- data/spec/unit/aws_driver/credentials_spec.rb +54 -0
- metadata +27 -5
- data/spec/support/aws_support.rb +0 -211
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 66de7d20ac16f1c99866f9d4195f38586bfb667d
|
4
|
+
data.tar.gz: 5cbae8b8fcc08af3538333bbf6bc29c96beb0f22
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36907c00f23caf788569f528f6b12ad64afb4c316f02794dd05927c06e234b7271257d7d83c85f517c7bb370928507fe2616bb9379e5b161e48cdce50dc3fed6
|
7
|
+
data.tar.gz: 616a2bcd0b104326714aa001462f042eedec30d06825b5e9556b6e326a03037833ddd4d79879183bd4b20a7335272140f253be91b380817acf4a2b13c4c298ae
|
data/README.md
CHANGED
@@ -12,3 +12,21 @@ An implementation of the AWS driver using the AWS Ruby SDK (v1). It also implem
|
|
12
12
|
* Autoscaling Groups
|
13
13
|
* SSH Key pairs
|
14
14
|
* Launch configs
|
15
|
+
|
16
|
+
# Running Integration Tests
|
17
|
+
|
18
|
+
To run the integration tests execute `bundle exec rake integration`. If you have not set it up,
|
19
|
+
you should see an error message about a missing environment variable `AWS_TEST_DRIVER`. You can add
|
20
|
+
this as a normal environment variable or set it for a single run with `AWS_TEST_DRIVER=aws::eu-west-1
|
21
|
+
bundle exec rake integration`. The format should match what `with_driver` expects.
|
22
|
+
|
23
|
+
You will also need to have configured your `~/.aws/config` or environment variables with your
|
24
|
+
AWS credentials.
|
25
|
+
|
26
|
+
This creates real objects within AWS. The tests make their best effort to delete these objects
|
27
|
+
after each test finishes but errors can happen which prevent this. Be aware that this may charge
|
28
|
+
you!
|
29
|
+
|
30
|
+
If you find the tests leaving behind resources during normal conditions (IE, not when there is an
|
31
|
+
unexpected exception) please file a bug. Most objects can be cleaned up by deleting the `test_vpc`
|
32
|
+
from within the AWS browser console.
|
data/Rakefile
CHANGED
@@ -10,3 +10,8 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
|
|
10
10
|
# TODO add back integration tests whenever we have strategy for keys
|
11
11
|
spec.exclude_pattern = 'spec/integration/**/*_spec.rb'
|
12
12
|
end
|
13
|
+
|
14
|
+
desc "Run Integration Specs"
|
15
|
+
RSpec::Core::RakeTask.new(:integration) do |spec|
|
16
|
+
spec.pattern = 'spec/integration/**/*_spec.rb'
|
17
|
+
end
|
@@ -47,8 +47,8 @@ class Chef::Provider::AwsEbsVolume < Chef::Provisioning::AWSDriver::AWSProvider
|
|
47
47
|
|
48
48
|
def update_aws_object(volume)
|
49
49
|
if initial_options.has_key?(:availability_zone)
|
50
|
-
if
|
51
|
-
raise "#{new_resource}.availability_zone is #{
|
50
|
+
if availability_zone != volume.availability_zone_name
|
51
|
+
raise "#{new_resource}.availability_zone is #{availability_zone}, but actual volume has availability_zone_name set to #{volume.availability_zone_name}. Cannot be modified!"
|
52
52
|
end
|
53
53
|
end
|
54
54
|
if initial_options.has_key?(:size)
|
@@ -99,7 +99,7 @@ class Chef::Provider::AwsEbsVolume < Chef::Provisioning::AWSDriver::AWSProvider
|
|
99
99
|
def initial_options
|
100
100
|
@initial_options ||= begin
|
101
101
|
options = {}
|
102
|
-
options[:availability_zone] =
|
102
|
+
options[:availability_zone] = availability_zone if !new_resource.availability_zone.nil?
|
103
103
|
options[:size] = new_resource.size if !new_resource.size.nil?
|
104
104
|
options[:snapshot_id] = new_resource.snapshot if !new_resource.snapshot.nil?
|
105
105
|
options[:iops] = new_resource.iops if !new_resource.iops.nil?
|
@@ -146,7 +146,7 @@ class Chef::Provider::AwsEbsVolume < Chef::Provisioning::AWSDriver::AWSProvider
|
|
146
146
|
end
|
147
147
|
volume
|
148
148
|
end
|
149
|
-
|
149
|
+
|
150
150
|
def wait_for_volume_status(volume, expected_status)
|
151
151
|
initial_status = volume.status
|
152
152
|
log_callback = proc {
|
@@ -204,4 +204,14 @@ class Chef::Provider::AwsEbsVolume < Chef::Provisioning::AWSDriver::AWSProvider
|
|
204
204
|
volume
|
205
205
|
end
|
206
206
|
end
|
207
|
+
|
208
|
+
def availability_zone
|
209
|
+
az = new_resource.availability_zone
|
210
|
+
if /^#{region}/ =~ az
|
211
|
+
Chef::Log.warn("availability_zone attribute should only be set to the letter designation. Attempting to use '#{az[-1]}' to correct the issue.")
|
212
|
+
elsif az.length == 1
|
213
|
+
az = "#{region}#{az[-1]}"
|
214
|
+
end
|
215
|
+
az
|
216
|
+
end
|
207
217
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
2
|
+
|
3
|
+
class Chef::Provider::AwsImage < Chef::Provisioning::AWSDriver::AWSProvider
|
4
|
+
def destroy_aws_object(image)
|
5
|
+
instance_id = image.tags['From-Instance']
|
6
|
+
Chef::Log.debug("Found From-Instance tag [#{instance_id}] on #{image.id}")
|
7
|
+
unless instance_id
|
8
|
+
# This is an old image and doesn't have the tag added - lets try and find it from the block device mapping
|
9
|
+
image.block_device_mappings.map do |dev, opts|
|
10
|
+
snapshot = ec2.snapshots[opts[:snapshot_id]]
|
11
|
+
desc = snapshot.description
|
12
|
+
m = /CreateImage\(([^\)]+)\)/.match(desc)
|
13
|
+
if m
|
14
|
+
Chef::Log.debug("Found [#{instance_id}] from snapshot #{snapshot.id} on #{image.id}")
|
15
|
+
instance_id = m[1]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
converge_by "delete image #{new_resource} in #{region}" do
|
20
|
+
image.delete
|
21
|
+
end
|
22
|
+
if instance_id
|
23
|
+
# As part of the image creation process, the source instance was automatically
|
24
|
+
# destroyed - we just need to make sure that has completed successfully
|
25
|
+
instance = new_resource.driver.ec2.instances[instance_id]
|
26
|
+
converge_by "waiting until instance #{instance.id} is :terminated" do
|
27
|
+
wait_for_status(instance, :terminated, [AWS::EC2::Errors::InvalidInstanceID::NotFound])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
2
|
+
|
3
|
+
class Chef::Provider::AwsInstance < Chef::Provisioning::AWSDriver::AWSProvider
|
4
|
+
def destroy_aws_object(instance)
|
5
|
+
converge_by "delete instance #{new_resource} in VPC #{instance.vpc.id} in #{region}" do
|
6
|
+
instance.delete
|
7
|
+
end
|
8
|
+
converge_by "waited until instance #{new_resource} is :terminated" do
|
9
|
+
# When purging, we must wait until the instance is fully terminated - thats the only way
|
10
|
+
# to delete the network interface that I can see
|
11
|
+
wait_for_status(instance, :terminated, [AWS::EC2::Errors::InvalidInstanceID::NotFound])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
2
|
+
|
3
|
+
class Chef::Provider::AwsLoadBalancer < Chef::Provisioning::AWSDriver::AWSProvider
|
4
|
+
def destroy_aws_object(load_balancer)
|
5
|
+
converge_by "delete load balancer #{new_resource.name} (#{load_balancer.id}) in VPC #{load_balancer.vpc.id} in #{region}" do
|
6
|
+
load_balancer.delete
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'chef/provisioning/aws_driver/aws_provider'
|
2
|
+
require 'cheffish'
|
3
|
+
require 'date'
|
4
|
+
require 'retryable'
|
5
|
+
|
6
|
+
class Chef::Provider::AwsNetworkInterface < Chef::Provisioning::AWSDriver::AWSProvider
|
7
|
+
class NetworkInterfaceStatusTimeoutError < TimeoutError
|
8
|
+
def initialize(new_resource, initial_status, expected_status)
|
9
|
+
super("timed out waiting for #{new_resource} status to change from #{initial_status} to #{expected_status}!")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class NetworkInterfaceStatusTimeoutError < TimeoutError
|
14
|
+
def initialize(new_resource, initial_status, expected_status)
|
15
|
+
super("timed out waiting for #{new_resource} status to change from #{initial_status} to #{expected_status}!")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class NetworkInterfaceInvalidStatusError < RuntimeError
|
20
|
+
def initialize(new_resource, status)
|
21
|
+
super("#{new_resource} is in #{status} state!")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def action_create
|
26
|
+
eni = super
|
27
|
+
|
28
|
+
if !new_resource.machine.nil?
|
29
|
+
update_eni(eni)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def create_aws_object
|
36
|
+
eni = nil
|
37
|
+
converge_by "create new #{new_resource} in #{region}" do
|
38
|
+
eni = new_resource.driver.ec2.network_interfaces.create(options)
|
39
|
+
eni.tags['Name'] = new_resource.name
|
40
|
+
end
|
41
|
+
|
42
|
+
converge_by "wait for new #{new_resource} in #{region} to become available" do
|
43
|
+
wait_for_eni_status(eni, :available)
|
44
|
+
eni
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update_aws_object(eni)
|
49
|
+
if options.has_key?(:subnet)
|
50
|
+
if Chef::Resource::AwsSubnet.get_aws_object(options[:subnet], resource: new_resource) != eni.subnet
|
51
|
+
raise "#{new_resource} subnet is #{new_resource.subnet}, but actual network interface has subnet set to #{eni.subnet_id}. Cannot be modified!"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO implement private ip reassignment
|
56
|
+
if options.has_key?(:private_ip_address)
|
57
|
+
if options[:private_ip_address] != eni.private_ip_address
|
58
|
+
raise "#{new_resource} private IP is #{new_resource.private_ip_address}, but actual network interface has private IP set to #{eni.private_ip_address}. Private IP reassignment not implemented. Cannot be modified!"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if options.has_key?(:description)
|
63
|
+
if options[:description] != eni.description
|
64
|
+
converge_by "set #{new_resource} description to #{new_resource.description}" do
|
65
|
+
eni.client.modify_network_interface_attribute(:network_interface_id => eni.network_interface_id,
|
66
|
+
:description => {
|
67
|
+
:value => new_resource.description
|
68
|
+
})
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if options.has_key?(:security_groups)
|
74
|
+
groups = new_resource.security_groups.map { |sg|
|
75
|
+
Chef::Resource::AwsSecurityGroup.get_aws_object(sg, resource: new_resource)
|
76
|
+
}
|
77
|
+
if groups.sort != eni.security_groups.sort
|
78
|
+
converge_by "set #{new_resource} security groups to #{groups}" do
|
79
|
+
eni.set_security_groups(groups)
|
80
|
+
eni
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
eni
|
86
|
+
end
|
87
|
+
|
88
|
+
def destroy_aws_object(eni)
|
89
|
+
detach(eni) if eni.status == :in_use
|
90
|
+
delete(eni)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def expected_instance
|
96
|
+
# use instance if already set
|
97
|
+
@expected_instance ||= new_resource.machine ?
|
98
|
+
# if not, and machine is set, find and return the instance
|
99
|
+
Chef::Resource::AwsInstance.get_aws_object(new_resource.machine, resource: new_resource) :
|
100
|
+
# otherwise return nil
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def options
|
105
|
+
@options ||= begin
|
106
|
+
options = {}
|
107
|
+
options[:subnet] = new_resource.subnet if !new_resource.subnet.nil?
|
108
|
+
options[:private_ip_address] = new_resource.private_ip_address if !new_resource.private_ip_address.nil?
|
109
|
+
options[:description] = new_resource.description if !new_resource.description.nil?
|
110
|
+
options[:security_groups] = new_resource.security_groups if !new_resource.security_groups.nil?
|
111
|
+
options[:device_index] = new_resource.device_index if !new_resource.device_index.nil?
|
112
|
+
|
113
|
+
AWSResource.lookup_options(options, resource: new_resource)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def update_eni(eni)
|
118
|
+
status = eni.status
|
119
|
+
#
|
120
|
+
# If we were told to attach the network interface to a machine, do so
|
121
|
+
#
|
122
|
+
if expected_instance.is_a?(AWS::EC2::Instance)
|
123
|
+
case status
|
124
|
+
when :available
|
125
|
+
attach(eni)
|
126
|
+
when :in_use
|
127
|
+
# We don't want to attempt to reattach to the same instance or device index
|
128
|
+
attachment = current_attachment(eni)
|
129
|
+
if attachment.instance != expected_instance || (options[:device_index] && attachment.device_index != new_resource.device_index)
|
130
|
+
detach(eni)
|
131
|
+
attach(eni)
|
132
|
+
end
|
133
|
+
when nil
|
134
|
+
raise NetworkInterfaceNotFoundError.new(new_resource)
|
135
|
+
else
|
136
|
+
raise NetworkInterfaceInvalidStatusError.new(new_resource, status)
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# If we were told to set the machine to false, detach it.
|
141
|
+
#
|
142
|
+
else
|
143
|
+
case status
|
144
|
+
when nil
|
145
|
+
Chef::Log.warn NetworkInterfaceNotFoundError.new(new_resource)
|
146
|
+
when :in_use
|
147
|
+
detach(eni)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
eni
|
151
|
+
end
|
152
|
+
|
153
|
+
def wait_for_eni_status(eni, expected_status)
|
154
|
+
initial_status = eni.status
|
155
|
+
log_callback = proc {
|
156
|
+
Chef::Log.info("waiting for #{new_resource} status to change to #{expected_status}...")
|
157
|
+
}
|
158
|
+
|
159
|
+
Retryable.retryable(:tries => 30, :sleep => 2, :on => NetworkInterfaceStatusTimeoutError, :ensure => log_callback) do
|
160
|
+
raise NetworkInterfaceStatusTimeoutError.new(new_resource, initial_status, expected_status) if eni.status != expected_status
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def detach(eni)
|
165
|
+
attachment = current_attachment(eni)
|
166
|
+
instance = attachment.instance
|
167
|
+
|
168
|
+
converge_by "detach #{new_resource} from #{instance.instance_id}" do
|
169
|
+
eni.detach
|
170
|
+
end
|
171
|
+
|
172
|
+
converge_by "wait for #{new_resource} to detach" do
|
173
|
+
wait_for_eni_status(eni, :available)
|
174
|
+
eni
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def attach(eni)
|
179
|
+
converge_by "attach #{new_resource} to #{new_resource.machine} (#{expected_instance.instance_id})" do
|
180
|
+
eni.attach(expected_instance, options)
|
181
|
+
end
|
182
|
+
|
183
|
+
converge_by "wait for #{new_resource} to attach" do
|
184
|
+
wait_for_eni_status(eni, :in_use)
|
185
|
+
eni
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def current_attachment(eni)
|
190
|
+
eni.attachment
|
191
|
+
end
|
192
|
+
|
193
|
+
def delete(eni)
|
194
|
+
converge_by "delete #{new_resource} in #{region}" do
|
195
|
+
eni.delete
|
196
|
+
end
|
197
|
+
|
198
|
+
converge_by "wait for #{new_resource} in #{region} to delete" do
|
199
|
+
log_callback = proc {
|
200
|
+
Chef::Log.info('waiting for network interface to delete...')
|
201
|
+
}
|
202
|
+
|
203
|
+
Retryable.retryable(:tries => 30, :sleep => 2, :on => NetworkInterfaceStatusTimeoutError, :ensure => log_callback) do
|
204
|
+
raise NetworkInterfaceStatusTimeoutError.new(new_resource, 'exists', 'deleted') if eni.exists?
|
205
|
+
end
|
206
|
+
eni
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -71,7 +71,7 @@ class Chef::Provider::AwsSecurityGroup < Chef::Provisioning::AWSDriver::AWSProvi
|
|
71
71
|
when Array
|
72
72
|
# [ { port: X, protocol: Y, sources: [ ... ]}]
|
73
73
|
new_resource.inbound_rules.each do |rule|
|
74
|
-
port_ranges = get_port_ranges(
|
74
|
+
port_ranges = get_port_ranges(rule)
|
75
75
|
add_rule(desired_rules, port_ranges, get_actors(vpc, rule[:sources]))
|
76
76
|
end
|
77
77
|
|
@@ -115,7 +115,7 @@ class Chef::Provider::AwsSecurityGroup < Chef::Provisioning::AWSDriver::AWSProvi
|
|
115
115
|
when Array
|
116
116
|
# [ { port: X, protocol: Y, sources: [ ... ]}]
|
117
117
|
new_resource.outbound_rules.each do |rule|
|
118
|
-
port_ranges = get_port_ranges(
|
118
|
+
port_ranges = get_port_ranges(rule)
|
119
119
|
add_rule(desired_rules, port_ranges, get_actors(vpc, rule[:destinations]))
|
120
120
|
end
|
121
121
|
|
@@ -204,11 +204,16 @@ class Chef::Provider::AwsSecurityGroup < Chef::Provisioning::AWSDriver::AWSProvi
|
|
204
204
|
[ { port_range: port_spec, protocol: :tcp } ]
|
205
205
|
when Array
|
206
206
|
port_spec.map { |p| get_port_ranges(p) }.flatten
|
207
|
+
when :icmp
|
208
|
+
{ port_range: port_spec, protocol: :icmp }
|
207
209
|
when Hash
|
210
|
+
port_range = port_spec[:port_range] || port_spec[:ports] || port_spec[:port]
|
211
|
+
port_range = port_range..port_range if port_range.is_a?(Integer)
|
208
212
|
if port_spec[:protocol]
|
209
|
-
|
213
|
+
port_range ||= -1..-1
|
214
|
+
[ { port_range: port_range, protocol: port_spec[:protocol].to_s.to_sym || :tcp } ]
|
210
215
|
else
|
211
|
-
get_port_ranges(
|
216
|
+
get_port_ranges(port_range)
|
212
217
|
end
|
213
218
|
# The to_s.to_sym dance is because if you specify a protocol number, AWS symbolifies it,
|
214
219
|
# but 26.to_sym doesn't work (so we have to to_s it first).
|
@@ -51,8 +51,23 @@ class Chef::Provider::AwsSubnet < Chef::Provisioning::AWSDriver::AWSProvider
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def destroy_aws_object(subnet)
|
54
|
+
if purging
|
55
|
+
# TODO possibly convert to http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/EC2/Client.html#terminate_instances-instance_method
|
56
|
+
p = Chef::ChefFS::Parallelizer.new(5)
|
57
|
+
p.parallel_do(subnet.instances.to_a) do |instance|
|
58
|
+
Cheffish.inline_resource(self, action) do
|
59
|
+
aws_instance instance do
|
60
|
+
action :purge
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
54
65
|
converge_by "delete subnet #{new_resource.name} in VPC #{new_resource.vpc} in #{region}" do
|
55
|
-
subnet
|
66
|
+
# If the subnet doesn't exist we can't check state on it - state can only be :pending or :available
|
67
|
+
begin
|
68
|
+
subnet.delete
|
69
|
+
rescue AWS::EC2::Errors::InvalidSubnetID::NotFound
|
70
|
+
end
|
56
71
|
end
|
57
72
|
end
|
58
73
|
|
@@ -55,6 +55,22 @@ class Chef::Provider::AwsVpc < Chef::Provisioning::AWSDriver::AWSProvider
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def destroy_aws_object(vpc)
|
58
|
+
if purging
|
59
|
+
vpc.subnets.each do |s|
|
60
|
+
Cheffish.inline_resource(self, action) do # if action isn't defined, we want :purge
|
61
|
+
aws_subnet s do
|
62
|
+
action :purge
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# If any of the below resources start needing complicated delete logic (dependent resources needing to
|
67
|
+
# be deleted) move that logic into `delete_aws_resource` and add the purging logic to the resource
|
68
|
+
vpc.network_acls.each { |o| o.delete unless o.default? }
|
69
|
+
vpc.network_interfaces.each { |o| o.delete }
|
70
|
+
vpc.route_tables.each { |o| o.delete unless o.main? }
|
71
|
+
vpc.security_groups.each { |o| o.delete unless o.name == 'default' }
|
72
|
+
end
|
73
|
+
|
58
74
|
# Detach or destroy the internet gateway
|
59
75
|
ig = vpc.internet_gateway
|
60
76
|
if ig
|
@@ -3,6 +3,7 @@ require 'chef/provisioning/aws_driver/aws_resource'
|
|
3
3
|
require 'chef/provisioning/aws_driver/aws_resource_with_entry'
|
4
4
|
require 'chef/provisioning/chef_managed_entry_store'
|
5
5
|
require 'chef/provisioning/chef_provider_action_handler'
|
6
|
+
require 'retryable'
|
6
7
|
|
7
8
|
module Chef::Provisioning::AWSDriver
|
8
9
|
class AWSProvider < Chef::Provider::LWRPBase
|
@@ -10,6 +11,12 @@ class AWSProvider < Chef::Provider::LWRPBase
|
|
10
11
|
|
11
12
|
AWSResource = Chef::Provisioning::AWSDriver::AWSResource
|
12
13
|
|
14
|
+
class StatusTimeoutError < TimeoutError
|
15
|
+
def initialize(aws_object, initial_status, expected_status)
|
16
|
+
super("timed out waiting for #{aws_object.id} status to change from #{initial_status.inspect} to #{expected_status.inspect}!")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
13
20
|
def action_handler
|
14
21
|
@action_handler ||= Chef::Provisioning::ChefProviderActionHandler.new(self)
|
15
22
|
end
|
@@ -124,6 +131,18 @@ class AWSProvider < Chef::Provider::LWRPBase
|
|
124
131
|
aws_object
|
125
132
|
end
|
126
133
|
|
134
|
+
# TODO having a @purging flag feels weird
|
135
|
+
action :purge do
|
136
|
+
@purging = true
|
137
|
+
begin
|
138
|
+
action_destroy
|
139
|
+
ensure
|
140
|
+
@purging = false
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
attr_reader :purging
|
145
|
+
|
127
146
|
action :destroy do
|
128
147
|
desired_driver = new_resource.driver
|
129
148
|
desired_id = new_resource.public_send(new_resource.class.aws_id_attribute) if new_resource.class.aws_id_attribute
|
@@ -202,5 +221,30 @@ class AWSProvider < Chef::Provider::LWRPBase
|
|
202
221
|
raise NotImplementedError, :destroy_aws_object
|
203
222
|
end
|
204
223
|
|
224
|
+
# Wait until aws_object obtains one of expected_status
|
225
|
+
#
|
226
|
+
# @param aws_object Aws SDK Object to check status on
|
227
|
+
# @param expected_status [Symbol,Array<Symbol>] Final status(s) to look for
|
228
|
+
# @param acceptable_errors [Exception,Array<Exception>] Acceptable errors that are caught and squelched
|
229
|
+
# @param tries [Integer] Number of times to check status
|
230
|
+
# @param sleep [Integer] Time to wait between checking status
|
231
|
+
#
|
232
|
+
def wait_for_status(aws_object, expected_status, acceptable_errors = [], tries=60, sleep=5)
|
233
|
+
acceptable_errors = [acceptable_errors].flatten
|
234
|
+
expected_status = [expected_status].flatten
|
235
|
+
current_status = aws_object.status
|
236
|
+
|
237
|
+
Retryable.retryable(:tries => tries, :sleep => sleep, :on => StatusTimeoutError) do |retries, exception|
|
238
|
+
action_handler.report_progress "waited #{retries*sleep}/#{tries*sleep}s for #{aws_object.id} status to change to #{expected_status.inspect}..."
|
239
|
+
begin
|
240
|
+
current_status = aws_object.status
|
241
|
+
unless expected_status.include?(current_status)
|
242
|
+
raise StatusTimeoutError.new(aws_object, current_status, expected_status)
|
243
|
+
end
|
244
|
+
rescue *acceptable_errors
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
205
249
|
end
|
206
250
|
end
|