furnish-aws 0.0.1

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.
@@ -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