chef-provisioning-aws 1.0.4 → 1.1.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/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
|