furnish-aws 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +59 -0
- data/Rakefile +32 -0
- data/furnish-aws.gemspec +30 -0
- data/lib/furnish/aws/version.rb +6 -0
- data/lib/furnish/provisioners/aws.rb +101 -0
- data/lib/furnish/provisioners/ec2.rb +372 -0
- data/lib/furnish/provisioners/security_group.rb +380 -0
- data/test/helper.rb +61 -0
- data/test/test_aws_base_class.rb +30 -0
- data/test/test_ec2.rb +225 -0
- data/test/test_security_group.rb +322 -0
- metadata +188 -0
checksums.yaml
ADDED
@@ -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=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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
|
data/furnish-aws.gemspec
ADDED
@@ -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,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
|