furnish-aws 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MDdmMmNkNDA4ZTc1NzZiNTQ0MWY0NmRjMTg2ZGZlOTUxOTQ2ZjU0Yw==
5
+ data.tar.gz: !binary |-
6
+ MzkwNjE1M2Q5YzAyNDgzMGI5MWRhODg1ZmJjN2I1MGY2N2E0ZGNiMw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NDUyYTljYTBlMmI3YjRlNTI3ZTA2NjkyMDU5ZGE3MzRkZDMyMzYwMTE0YTI5
10
+ YTVkMTYwZTExODNmYWEyNGZjNDg0NGVjNDkzZDAyZjUzMmE5MWU4MmQxMjdi
11
+ OTZkMzkyNmVlN2ViZTM1ZjAyZGE2N2YyYTQ1Y2JiNTliOTJmZDY=
12
+ data.tar.gz: !binary |-
13
+ YjMzMGE5NTNhMzAwNWUyYWZmYTdmNTk4ODk1MzlmZTNjZmEwZjdmYTZhMDMy
14
+ MzI3NWVhZGEyZTNkN2FhNDFiMTY4NmYwMzhlOGY2MzRiMDZhNTNjMDg5Yzg2
15
+ YjJiYWIyZjY4OTYxY2JkMjMzN2Y4ZmMwMDQ3MmU0NWYzNWY5Zjg=
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .acknowledged
19
+ .aws-env
20
+ html
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in furnish-aws.gemspec
4
+ gemspec
@@ -0,0 +1,10 @@
1
+ # vim: ft=ruby
2
+ guard 'minitest' do
3
+ # with Minitest::Unit
4
+ watch(%r!^test/(.*)\/?test_(.*)\.rb!)
5
+ watch(%r!^test/helper\.rb!) { "test" }
6
+ end
7
+
8
+ guard 'rake', :run_on_all => false, :task => 'rdoc_cov' do
9
+ watch(%r!^lib/(.*)([^/]+)\.rb!)
10
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Erik Hollensbe
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ # AWS provisioners for Furnish
2
+
3
+ Several AWS-related provisioners for Furnish:
4
+
5
+ * EC2 Instances
6
+ * Security Groups
7
+
8
+ ## Usage
9
+
10
+ For usage, see the documentation for each Provisioner class. For general
11
+ Furnish usage, see the [Furnish documentation](http://rubydoc.info/gems/furnish/).
12
+
13
+ ## Testing
14
+
15
+ Tests do not use mocks. They are built in a way to do a minimum amount of API
16
+ traffic, but traffic does happen, machines and other resources get provisioned,
17
+ and yes, your card will be charged as a result of that.
18
+
19
+ Acknowledging that and still willing to test, there are two things you need to
20
+ do to get tests to run:
21
+
22
+ * `touch .acknowledged` in the root of the repo - this more or less indicates
23
+ you read the above.
24
+ * open `.aws-env` and put some ruby in it that sets your AWS credentials. This
25
+ will be evaluated at harness time. This is to ensure you don't do something
26
+ stupid to production.
27
+
28
+ e.g.:
29
+
30
+ ```ruby
31
+ ENV["AWS_ACCESS_KEY_ID"]="aaaaah"
32
+ ENV["AWS_SECRET_ACCESS_KEY"]="it's a space herpe"
33
+ ```
34
+
35
+ If you don't do these the test suite will not run. Both files are in
36
+ `.gitignore` so you can feel comfortable knowing they won't be committed.
37
+
38
+ ## Installation
39
+
40
+ Add this line to your application's Gemfile:
41
+
42
+ gem 'furnish-aws'
43
+
44
+ And then execute:
45
+
46
+ $ bundle
47
+
48
+ Or install it yourself as:
49
+
50
+ $ gem install furnish-aws
51
+
52
+
53
+ ## Contributing
54
+
55
+ 1. Fork it
56
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
57
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
58
+ 4. Push to the branch (`git push origin my-new-feature`)
59
+ 5. Create new Pull Request
@@ -0,0 +1,32 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList["test/test_*.rb"]
8
+ t.verbose = true
9
+ end
10
+
11
+ RDoc::Task.new do |rdoc|
12
+ rdoc.title = "AWS provisioners for Furnish"
13
+ rdoc.main = "README.md"
14
+ rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
15
+ rdoc.rdoc_files -= ["lib/furnish/aws/version.rb"]
16
+ if ENV["RDOC_COVER"]
17
+ rdoc.options << "-C"
18
+ end
19
+ end
20
+
21
+ desc "run tests with coverage report"
22
+ task "test:coverage" do
23
+ ENV["COVERAGE"] = "1"
24
+ Rake::Task["test"].invoke
25
+ end
26
+
27
+ desc "run rdoc with coverage report"
28
+ task :rdoc_cov do
29
+ # ugh
30
+ ENV["RDOC_COVER"] = "1"
31
+ ruby "-S rake rerdoc"
32
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'furnish/aws/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "furnish-aws"
8
+ gem.version = Furnish::AWS::VERSION
9
+ gem.authors = ["Erik Hollensbe"]
10
+ gem.email = ["erik+github@hollensbe.org"]
11
+ gem.description = %q{AWS provisioners for Furnish}
12
+ gem.summary = %q{AWS provisioners for Furnish}
13
+ gem.homepage = "https://github.com/chef-workflow/furnish-aws"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'furnish', '~> 0.1.0'
21
+ gem.add_dependency 'aws-sdk', '~> 1.8.5'
22
+
23
+ gem.add_development_dependency 'rake'
24
+ gem.add_development_dependency 'minitest', '~> 4.5.0'
25
+ gem.add_development_dependency 'guard-minitest'
26
+ gem.add_development_dependency 'guard-rake', '~> 0.0.8'
27
+ gem.add_development_dependency 'rdoc', '~> 4'
28
+ gem.add_development_dependency 'rb-fsevent'
29
+ gem.add_development_dependency 'simplecov'
30
+ end
@@ -0,0 +1,6 @@
1
+ module Furnish
2
+ module AWS
3
+ # version of furnish-aws
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
@@ -0,0 +1,101 @@
1
+ require 'aws'
2
+ require 'furnish/logger'
3
+ require 'furnish/provisioners/api'
4
+
5
+ module Furnish # :nodoc:
6
+ module Provisioner # :nodoc:
7
+ #
8
+ # AWS base class, handles AWS API basics, argument checking, general
9
+ # utility methods.
10
+ #
11
+ # See attributes and #new for contraints that apply to all
12
+ # provisioners that inherit from it.
13
+ #
14
+ class AWS < API
15
+ include Furnish::Logger::Mixins
16
+
17
+ ##
18
+ # :attr: access_key
19
+ #
20
+ # The AWS access key ID. Required.
21
+ #
22
+ furnish_property :access_key,
23
+ "AWS access key ID",
24
+ String
25
+
26
+ ##
27
+ # :attr: secret_key
28
+ #
29
+ # The AWS secret key ID. Required.
30
+ #
31
+ furnish_property :secret_key,
32
+ "AWS secret key ID",
33
+ String
34
+
35
+ ##
36
+ # :attr: region
37
+ #
38
+ # The AWS region. Not required. The default is nil. Inheriting
39
+ # provisioners may affect the default and/or make it required.
40
+ #
41
+ furnish_property :region,
42
+ "AWS region -- default is nil, may be defaulted or required by subclasses.",
43
+ String
44
+
45
+ #
46
+ # Construct a new provisioner. Hash for arguments is required, values
47
+ # supplied will be injected into attributes. If a hash is not supplied,
48
+ # an ArgumentError is raised.
49
+ #
50
+ # The AWS #access_key and #secret_key are required, and also raise an
51
+ # ArgumentError unless supplied.
52
+ #
53
+ # In some provisioners, a region may also be required. In those, an...
54
+ # ArgumentError will be raise if it isn't supplied.
55
+ #
56
+ # For further constraints, please see provisioners that inherit from this
57
+ # class.
58
+ #
59
+ def initialize(args)
60
+ super
61
+ check_aws_settings
62
+ end
63
+
64
+ #
65
+ # Ensures #access_key and #secret_key are set. Used by initializer.
66
+ #
67
+ def check_aws_settings
68
+ unless access_key and secret_key
69
+ raise ArgumentError, "AWS credentials must be provided to the provisioner."
70
+ end
71
+ end
72
+
73
+ #
74
+ # Ensures the region is set. Is not used by default in the initializer,
75
+ # but is common to many provisioners.
76
+ #
77
+ def check_region
78
+ unless region
79
+ raise ArgumentError, "AWS region must be provided to the provisioner."
80
+ end
81
+ end
82
+
83
+ #
84
+ # constructs an AWS::EC2 object. if a region is supplied, constraints it
85
+ # to the region and a AWS::EC2::Region object will be returned.
86
+ #
87
+ # these have the same interface for most things, and things that require
88
+ # a region in AWS::EC2 are defaulted to us-east-1. See the aws-sdk
89
+ # documentation for more details.
90
+ #
91
+ def ec2
92
+ ec2 = ::AWS::EC2.new(
93
+ :access_key_id => access_key,
94
+ :secret_access_key => secret_key
95
+ )
96
+
97
+ return region ? ec2.regions[region] : ec2
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,372 @@
1
+ require 'furnish/provisioners/aws'
2
+ require 'timeout'
3
+
4
+ module Furnish # :nodoc:
5
+ module Provisioner # :nodoc:
6
+ #
7
+ # EC2 Instance Provisioner. See attributes and #new for argument
8
+ # descriptions.
9
+ #
10
+ # If security groups are supplied to this provisioner as a part of a
11
+ # provisioner group (in #startup's argument list), they are appended to any
12
+ # list of security group id's already provided. This allows you to
13
+ # provision a security group with the instances without knowing about
14
+ # either beforehand.
15
+ #
16
+ # On startup, IP addresses of the instances are returned to the next
17
+ # provisioner in the provisioner group.
18
+ #
19
+ # It would be in your best interest to familiarize yourself with some of
20
+ # aws-sdk's API when trying to understand some of these parameters. Here is
21
+ # some documentation:
22
+ #
23
+ # http://rdoc.info/gems/aws-sdk
24
+ #
25
+ class EC2 < AWS
26
+ ##
27
+ # :attr: region
28
+ #
29
+ # See Furnish::Provisioner::AWS#region. nil by default, required in this
30
+ # provisioner. If #availability_zone is supplied, the region will be
31
+ # extracted from what is set as the AZ.
32
+ #
33
+
34
+ ##
35
+ # :attr: provision_timeout
36
+ #
37
+ # Give up after this long for the requested instances to be in a running
38
+ # state. Default is 300 seconds.
39
+ #
40
+ furnish_property :provision_timeout,
41
+ "Give up after this long for the instance to come alive after requesting from the API. Default is 300 seconds.",
42
+ Integer
43
+
44
+ ##
45
+ # :attr: poll_interval
46
+ #
47
+ # Wait this long between instance status requests. Default is one second.
48
+ # Fractional values are OK.
49
+ #
50
+ furnish_property :poll_interval,
51
+ "Wait this long between instance status requests. Default is 1 second, fractional values OK.",
52
+ Numeric
53
+
54
+ ##
55
+ # :attr: key_name
56
+ #
57
+ # The SSH key name for EC2. Must already exists. Required. No Default.
58
+ #
59
+ furnish_property :key_name,
60
+ "EC2 SSH Key Name, must already exist. Required. No Default.",
61
+ String
62
+
63
+ ##
64
+ # :attr: availability_zone
65
+ #
66
+ # EC2 availability zone. If not provided, EC2 will pick based on the
67
+ # region. If provided, region will be determined from this value.
68
+ #
69
+ furnish_property :availability_zone,
70
+ "EC2 Availability Zone. If provided and no region supplied, region will be determined from this. If this is not supplied, AZ will be EC2's choosing based on the region.",
71
+ String
72
+
73
+ ##
74
+ # :attr: image_id
75
+ #
76
+ # The AMI identifier. Required. No default.
77
+ #
78
+ furnish_property :image_id,
79
+ "AMI Identifier. Required. No Default.",
80
+ String
81
+
82
+ ##
83
+ # :attr: kernel_id
84
+ #
85
+ # The AKI identifier. No default, EC2 picks based on the AMI if not
86
+ # provided.
87
+ #
88
+ furnish_property :kernel_id,
89
+ "AKI Identifier. If not supplied, left to EC2 to sort out.",
90
+ String
91
+
92
+ ##
93
+ # :attr: ramdisk_id
94
+ #
95
+ # The ARI identifier. No default, EC2 picks based on the AMI and possibly
96
+ # the AKI if not provided.
97
+ #
98
+ furnish_property :ramdisk_id,
99
+ "ARI Identifier. If not supplied, left to EC2 to sort out.",
100
+ String
101
+
102
+ ##
103
+ # :attr: count
104
+ #
105
+ # The number of instances to provision. Default is 1.
106
+ #
107
+ furnish_property :count,
108
+ "Number of instances to allocate. Required, Default is 1.",
109
+ Integer
110
+
111
+ ##
112
+ # :attr: instance_type
113
+ #
114
+ # The size or "flavor" of instance(s). Required, default is 'c1.medium'.
115
+ #
116
+ furnish_property :instance_type,
117
+ "Size or 'flavor' of instance(s). Required, default is 'c1.medium'."
118
+ String
119
+
120
+ ##
121
+ # :attr: security_group_ids
122
+ #
123
+ # Array of security group identifiers (not *group names*) that these
124
+ # instances will be bound to. Appends any incoming group id's from
125
+ # previous provisioners. At least one must exist at provisioning time, or
126
+ # the provision will fail.
127
+ #
128
+ furnish_property :security_group_ids,
129
+ "list of group identifiers (not names) that these instances will be bound to. Appends any incoming group id's from previous provisioners. At least one must exist at provisioning time.",
130
+ Array
131
+
132
+ ##
133
+ # :attr: block_device_mappings
134
+ #
135
+ # Mapping for block device information. Passed directly to
136
+ # AWS::EC2::InstanceCollection#create -- see its documentation for more
137
+ # information.
138
+ #
139
+ furnish_property :block_device_mappings,
140
+ "block device information. Passed directly to AWS::EC2::InstanceCollection#create's argument list. Not required, no default."
141
+
142
+ ##
143
+ # :attr: user_data
144
+ #
145
+ # Mapping for user data. Passed directly to
146
+ # AWS::EC2::InstanceCollection#create -- see its documentation for more
147
+ # information.
148
+ #
149
+ furnish_property :user_data,
150
+ "user data. Passed directly to AWS::EC2::InstanceCollection#create's argument list. Not required, no default."
151
+
152
+ ##
153
+ # :attr: monitoring_enabled
154
+ #
155
+ # Mapping for configuring monitoring. Passed directly to
156
+ # AWS::EC2::InstanceCollection#create -- see its documentation for more
157
+ # information.
158
+ #
159
+ furnish_property :monitoring_enabled,
160
+ "enable montioring. Passed directly to AWS::EC2::InstanceCollection#create's argument list. Not required, no default."
161
+
162
+
163
+ ##
164
+ #
165
+ # After provisioning, instance identifiers managed by this provisioner
166
+ # will be set here.
167
+ #
168
+ attr_reader :instance_ids
169
+
170
+ ##
171
+ # Arguments that are passed through straight to
172
+ # AWS::EC2::InstanceCollection#create.
173
+ #
174
+ PASSTHROUGH_ATTRS = %w[
175
+ key_name
176
+ availability_zone
177
+ image_id
178
+ kernel_id
179
+ ramdisk_id
180
+ count
181
+ block_device_mappings
182
+ instance_type
183
+ user_data
184
+ security_group_ids
185
+ monitoring_enabled
186
+ ]
187
+
188
+ #
189
+ #
190
+ # Create a new EC2 Provisioner. Inherits the initializer from
191
+ # Furnish::Provisioner::AWS.new, see the documentation there for more
192
+ # information.
193
+ #
194
+ # Additionally, see the attribute listing for argument documentation and
195
+ # their defaults.
196
+ #
197
+ # Required arguments that do not have defaults. ArgumentError will be
198
+ # raised if they are not supplied at construction time:
199
+ #
200
+ # * #access_key (from Furnish::Provisioner::AWS)
201
+ # * #secret_key (from Furnish::Provisioner::AWS)
202
+ # * #instance_type
203
+ # * #image_id
204
+ # * #region (also see #availability_zone)
205
+ # * #key_name
206
+ #
207
+ # Additionally see #security_group_ids, #poll_interval and
208
+ # #provision_timeout for some detail on special logic surrounding the
209
+ # operation of this provisioner.
210
+ #
211
+ def initialize(args)
212
+ super
213
+
214
+ check_region
215
+ check_ec2_args
216
+
217
+ @count ||= 1
218
+ @instance_type ||= "c1.medium"
219
+ @security_group_ids ||= []
220
+ @provision_wait ||= 300
221
+ @poll_interval ||= 1
222
+ @instance_ids = []
223
+ end
224
+
225
+ #
226
+ # Overload of Furnish::Provisioner::AWS#check_region that also deals with
227
+ # availability zones.
228
+ def check_region
229
+ if availability_zone and !region
230
+ @region = availability_zone.sub(/\D+$/, '')
231
+ end
232
+
233
+ if availability_zone and region and !availability_zone.start_with?(region)
234
+ raise ArgumentError,
235
+ "region and availability zone are both supplied and do not match: [r: #{region}, a: #{availability_zone}]"
236
+ end
237
+
238
+ super
239
+ end
240
+
241
+ #
242
+ # Validate additional required EC2-specific arguments like #key_name and
243
+ # #image_id.
244
+ #
245
+ def check_ec2_args
246
+ unless key_name
247
+ raise ArgumentError, "key_name is required for instance provision"
248
+ end
249
+
250
+ unless image_id
251
+ raise ArgumentError, "an AMI image_id must be provided"
252
+ end
253
+ end
254
+
255
+ #
256
+ # generates options to pass to AWS::EC2::InstanceCollection#create from
257
+ # our attributes.
258
+ #
259
+ def launch_options
260
+ options = { }
261
+
262
+ PASSTHROUGH_ATTRS.each { |x| options[x.to_sym] = send(x) if send(x) }
263
+
264
+ return options
265
+ end
266
+
267
+ #
268
+ # puts the return value from ec2.instances.create into a normal array no
269
+ # matter what it returns. See the method comments for more information.
270
+ #
271
+ def coerce_instances(instances)
272
+ # if count == 1, returns a single instance id.
273
+ instances = [instances] unless instances.kind_of?(Array)
274
+
275
+ # XXX there are versions of aws-sdk that respond to #kind_of?(Array)
276
+ # but aren't actually arrays. This will always give us an array.
277
+
278
+ tmp_instances = []
279
+ instances.each { |i| tmp_instances.push(i) }
280
+ return tmp_instances
281
+ end
282
+
283
+ #
284
+ # Polls the EC2 API waiting for instances to enter the state passed.
285
+ # Uses #poll_interval to determine how long to wait between status
286
+ # requests, and #provision_timeout to determine how long to wait before
287
+ # giving up.
288
+ #
289
+ def wait_for_instances(instances, state)
290
+ Timeout.timeout(provision_timeout) do
291
+ not_running = instances.dup
292
+
293
+ until not_running.empty?
294
+ instance = not_running.shift
295
+
296
+ status = instance.status rescue nil
297
+
298
+ if status
299
+ unless status == state
300
+ if_debug(3) do
301
+ puts "instance #{instance.id} is not in #{state} state yet."
302
+ end
303
+
304
+ not_running.push(instance)
305
+ end
306
+ else
307
+ if_debug(3) do
308
+ puts "API server doesn't think #{instance.id} exists yet."
309
+ end
310
+
311
+ not_running.push(instance)
312
+ end
313
+
314
+ sleep poll_interval
315
+ end
316
+ end
317
+ rescue TimeoutError
318
+ raise "instances timed out waiting for ec2 API"
319
+ end
320
+
321
+ #
322
+ # Provision instance(s).
323
+ #
324
+ # If a security group id is supplied to this method, it will be
325
+ # permanently appended to #security_group_ids and used during instance
326
+ # creation.
327
+ #
328
+ # Regardless of the above behavior, if no security group ids exist, will
329
+ # raise RuntimeError (EC2 requires instances to be in at least one
330
+ # security group).
331
+ #
332
+ # Records the instance id's, waits for them to all enter the `:running`
333
+ # state, and then returns a list of their public IP addresses to the next
334
+ # provisioner.
335
+ #
336
+ def startup(args={})
337
+ if args[:security_group_ids]
338
+ @security_group_ids += args[:security_group_ids]
339
+ end
340
+
341
+ if security_group_ids.empty?
342
+ raise "no security groups supplied either at construction or provisioning time, cannot request instances."
343
+ end
344
+
345
+ instances = coerce_instances(ec2.instances.create(launch_options))
346
+ @instance_ids = instances.map(&:id)
347
+ wait_for_instances(instances, :running)
348
+
349
+ return({ :security_group_ids => @security_group_ids, :ips => Set[*instances.map(&:ip_address)], :ec2_instance_ids => @instance_ids })
350
+ end
351
+
352
+ #
353
+ # All instances are told to terminate, the method then waits for all of
354
+ # them to enter the terminated state, then returns true.
355
+ #
356
+ def shutdown(args={})
357
+ instances = instance_ids.map { |i| ec2.instances[i] }
358
+ instances.each { |i| i.terminate rescue nil }
359
+ wait_for_instances(instances, :terminated)
360
+
361
+ return { }
362
+ end
363
+
364
+ #
365
+ # Furnish reporter -- includes image id, number of servers, and instance id's.
366
+ #
367
+ def report
368
+ ["ami #{image_id}; #{count} servers; instance_ids: #{instance_ids.inspect}"]
369
+ end
370
+ end
371
+ end
372
+ end