internuity-awsum 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.rdoc +42 -0
- data/Rakefile +85 -0
- data/lib/awsum.rb +24 -0
- data/lib/ec2/ec2.rb +656 -0
- data/lib/ec2/image.rb +161 -0
- data/lib/ec2/instance.rb +144 -0
- data/lib/ec2/snapshot.rb +82 -0
- data/lib/ec2/volume.rb +137 -0
- data/lib/parser.rb +18 -0
- data/lib/requestable.rb +214 -0
- data/lib/support.rb +94 -0
- data/test/dump.rb +42 -0
- data/test/fixtures/ec2/attach_volume.xml +9 -0
- data/test/fixtures/ec2/available_volume.xml +14 -0
- data/test/fixtures/ec2/create_snapshot.xml +9 -0
- data/test/fixtures/ec2/create_volume.xml +10 -0
- data/test/fixtures/ec2/delete_snapshot.xml +5 -0
- data/test/fixtures/ec2/delete_volume.xml +5 -0
- data/test/fixtures/ec2/detach_volume.xml +9 -0
- data/test/fixtures/ec2/image.xml +15 -0
- data/test/fixtures/ec2/images.xml +77 -0
- data/test/fixtures/ec2/instance.xml +36 -0
- data/test/fixtures/ec2/instances.xml +88 -0
- data/test/fixtures/ec2/run_instances.xml +30 -0
- data/test/fixtures/ec2/snapshots.xml +13 -0
- data/test/fixtures/ec2/terminate_instances.xml +17 -0
- data/test/fixtures/ec2/volumes.xml +23 -0
- data/test/fixtures/errors/invalid_parameter_value.xml +2 -0
- data/test/helper.rb +21 -0
- data/test/units/ec2/test_ec2.rb +1024 -0
- data/test/units/ec2/test_image.rb +114 -0
- data/test/units/ec2/test_instance.rb +127 -0
- data/test/units/ec2/test_snapshot.rb +45 -0
- data/test/units/ec2/test_volume.rb +65 -0
- data/test/units/test_awsum.rb +7 -0
- metadata +135 -0
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2009 Internuity Ltd, Andrew Timberlake <andrew@andrewtimberlake.com>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
=Awsum
|
2
|
+
|
3
|
+
Awsum (Pronounced Awesome) is a library for working with Amazon web services.
|
4
|
+
The concept of Awsum is to expose the AWS library is the most ruby way possible
|
5
|
+
allowing you to work with objects in a very natural way.
|
6
|
+
|
7
|
+
==Quick Start
|
8
|
+
|
9
|
+
#Create a snapshot for every volume of every instance you own
|
10
|
+
ec2 = Awsum::Ec2.new(<access key>, <secret key>)
|
11
|
+
ec2.instances.each do |instance|
|
12
|
+
instance.volumes.each do |volume|
|
13
|
+
volume.create_snapshot
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
==Working with different Regions
|
18
|
+
You can use blocks to wrap your calls for a specific Region
|
19
|
+
ec2.region('eu-west-1').use do
|
20
|
+
#Run an instance in the eu-west-1 region
|
21
|
+
run_instance(...)
|
22
|
+
end
|
23
|
+
|
24
|
+
==Using the library on an EC2 instance
|
25
|
+
|
26
|
+
There are two methods specifically for using a library on an EC2 instance
|
27
|
+
Awsum::Ec2#me
|
28
|
+
Awsum::Ec2#user_data
|
29
|
+
|
30
|
+
To extend the quick start example, you could do
|
31
|
+
|
32
|
+
#Create a snapshot of every volume of the currently running instance
|
33
|
+
ec2 = Awsum::Ec2.new(<access key>, <secret key>)
|
34
|
+
ec2.me.volumes.each do |volume|
|
35
|
+
volume.create_snapshot
|
36
|
+
end
|
37
|
+
|
38
|
+
==Note:
|
39
|
+
|
40
|
+
Awsum is currently under active development and only supports EC2 at the moment.
|
41
|
+
|
42
|
+
Once EC2 is complete, I will focus on S3, SQS, CloudSpace and then others
|
data/Rakefile
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
|
6
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
7
|
+
require 'awsum'
|
8
|
+
|
9
|
+
desc 'Default: run unit tests.'
|
10
|
+
task :default => [:clean, :test]
|
11
|
+
|
12
|
+
desc 'Run tests'
|
13
|
+
Rake::TestTask.new(:test) do |t|
|
14
|
+
t.libs << 'lib'
|
15
|
+
t.pattern = 'test/**/test_*.rb'
|
16
|
+
t.verbose = true
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Run code coverage'
|
20
|
+
task :coverage do |t|
|
21
|
+
puts `rcov -T #{Dir.glob('test/**/test_*.rb').join(' ')}`
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Start an IRB session with all necessary files required.'
|
25
|
+
task :shell do |t|
|
26
|
+
chdir File.dirname(__FILE__)
|
27
|
+
exec 'irb -I lib/ -I lib/awsum -r rubygems -r awsum'
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Generate documentation.'
|
31
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
32
|
+
rdoc.rdoc_dir = 'doc'
|
33
|
+
rdoc.title = 'AWSum'
|
34
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
35
|
+
rdoc.rdoc_files.include('README*')
|
36
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'Clean up files.'
|
40
|
+
task :clean do |t|
|
41
|
+
FileUtils.rm_rf "doc"
|
42
|
+
FileUtils.rm_rf "tmp"
|
43
|
+
FileUtils.rm_rf "pkg"
|
44
|
+
end
|
45
|
+
|
46
|
+
spec = Gem::Specification.new do |s|
|
47
|
+
s.name = "awsum"
|
48
|
+
s.version = Awsum::VERSION
|
49
|
+
s.author = "Andrew Timberlake"
|
50
|
+
s.email = "andrew@andrewtimberlake.com"
|
51
|
+
s.homepage = "http://www.internuity.net/projects/awsum"
|
52
|
+
s.platform = Gem::Platform::RUBY
|
53
|
+
s.summary = "Ruby library for working with Amazon Web Services"
|
54
|
+
s.files = FileList["README*",
|
55
|
+
"LICENSE",
|
56
|
+
"Rakefile",
|
57
|
+
"{lib,test}/**/*"].to_a
|
58
|
+
s.require_path = "lib"
|
59
|
+
s.test_files = FileList["test/**/test_*.rb"].to_a + FileList["test/fixtures/**/*.xml"].to_a
|
60
|
+
s.rubyforge_project = "awsum"
|
61
|
+
s.has_rdoc = true
|
62
|
+
s.extra_rdoc_files = FileList["README*"].to_a
|
63
|
+
s.rdoc_options << '--line-numbers' << '--inline-source'
|
64
|
+
s.add_development_dependency 'thoughtbot-shoulda'
|
65
|
+
s.add_development_dependency 'mocha'
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Release new version"
|
69
|
+
task :release => [:test, :sync_docs, :gem] do
|
70
|
+
require 'rubygems'
|
71
|
+
require 'rubyforge'
|
72
|
+
r = RubyForge.new
|
73
|
+
r.login
|
74
|
+
r.add_release spec.rubyforge_project,
|
75
|
+
spec.name,
|
76
|
+
spec.version,
|
77
|
+
File.join("pkg", "#{spec.name}-#{spec.version}.gem")
|
78
|
+
end
|
79
|
+
|
80
|
+
desc "Generate a gemspec file for GitHub"
|
81
|
+
task :gemspec do
|
82
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
83
|
+
f.write spec.to_ruby
|
84
|
+
end
|
85
|
+
end
|
data/lib/awsum.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# AWSum is a library facilitating access to Amazon's web services in (hopefully)
|
2
|
+
# a very object-oriented, ruby way.
|
3
|
+
#
|
4
|
+
# Author:: Andrew Timberlake
|
5
|
+
# Copyright:: Copyright (c) 2009 Internuity Ltd
|
6
|
+
# Licence:: MIT License (http://www.opensource.org/licenses/mit-license.php)
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'parser'
|
10
|
+
require 'requestable'
|
11
|
+
require 'support'
|
12
|
+
|
13
|
+
require 'ec2/ec2'
|
14
|
+
require 's3/s3'
|
15
|
+
|
16
|
+
module Awsum
|
17
|
+
|
18
|
+
VERSION = "0.1"
|
19
|
+
|
20
|
+
API_VERSION = '2008-12-01'
|
21
|
+
SIGNATURE_VERSION = 2
|
22
|
+
end
|
23
|
+
|
24
|
+
|
data/lib/ec2/ec2.rb
ADDED
@@ -0,0 +1,656 @@
|
|
1
|
+
require 'ec2/address'
|
2
|
+
require 'ec2/availability_zone'
|
3
|
+
require 'ec2/image'
|
4
|
+
require 'ec2/instance'
|
5
|
+
require 'ec2/keypair'
|
6
|
+
require 'ec2/security_group'
|
7
|
+
require 'ec2/snapshot'
|
8
|
+
require 'ec2/region'
|
9
|
+
require 'ec2/volume'
|
10
|
+
|
11
|
+
module Awsum
|
12
|
+
# Handles all interaction with Amazon EC2
|
13
|
+
#
|
14
|
+
# ==Getting Started
|
15
|
+
# Create an Awsum::Ec2 object and begin calling methods on it.
|
16
|
+
# require 'rubygems'
|
17
|
+
# require 'awsum'
|
18
|
+
# ec2 = Awsum::Ec2.new('your access id', 'your secret key')
|
19
|
+
# images = ec2.my_images
|
20
|
+
# ...
|
21
|
+
#
|
22
|
+
# All calls to EC2 can be done directly in this class, or through a more object oriented way through the various returned classes
|
23
|
+
#
|
24
|
+
# ==Examples
|
25
|
+
# ec2.image('ami-ABCDEF').run
|
26
|
+
#
|
27
|
+
# ec2.instance('i-123456789').volumes.each do |vol|
|
28
|
+
# vol.create_snapsot
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# ec2.regions.each do |region|
|
32
|
+
# region.use
|
33
|
+
# images.each do |img|
|
34
|
+
# puts "#{img.id} - #{region.name}"
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# ==Errors
|
40
|
+
# All methods will raise an Awsum::Error if an error is returned from Amazon
|
41
|
+
#
|
42
|
+
# ==Missing Methods
|
43
|
+
# * ConfirmProductInstance
|
44
|
+
# * ModifyImageAttribute
|
45
|
+
# * DescribeImageAttribute
|
46
|
+
# * ResetImageAttribute
|
47
|
+
# If you need any of this functionality, please consider getting involved and help complete this library.
|
48
|
+
class Ec2
|
49
|
+
include Awsum::Requestable
|
50
|
+
|
51
|
+
# Create an new ec2 instance
|
52
|
+
#
|
53
|
+
# The access_key and secret_key are both required to do any meaningful work.
|
54
|
+
#
|
55
|
+
# If you want to get these keys from environment variables, you can do that in your code as follows:
|
56
|
+
# ec2 = Awsum::Ec2.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])
|
57
|
+
def initialize(access_key = nil, secret_key = nil)
|
58
|
+
@access_key = access_key
|
59
|
+
@secret_key = secret_key
|
60
|
+
end
|
61
|
+
|
62
|
+
# Retrieve a list of available Images
|
63
|
+
#
|
64
|
+
# ===Options:
|
65
|
+
# * <tt>:image_ids</tt> - array of Image id's, default: []
|
66
|
+
# * <tt>:owners</tt> - array of owner id's, default: []
|
67
|
+
# * <tt>:executable_by</tt> - array of user id's who have executable permission, default: []
|
68
|
+
def images(options = {})
|
69
|
+
options = {:image_ids => [], :owners => [], :executable_by => []}.merge(options)
|
70
|
+
action = 'DescribeImages'
|
71
|
+
params = {
|
72
|
+
'Action' => action
|
73
|
+
}
|
74
|
+
#Add options
|
75
|
+
params.merge!(array_to_params(options[:image_ids], "ImageId"))
|
76
|
+
params.merge!(array_to_params(options[:owners], "Owner"))
|
77
|
+
params.merge!(array_to_params(options[:executable_by], "ExecutableBy"))
|
78
|
+
|
79
|
+
response = send_query_request(params)
|
80
|
+
parser = Awsum::Ec2::ImageParser.new(self)
|
81
|
+
parser.parse(response.body)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Retrieve all Image(s) owned by you
|
85
|
+
def my_images
|
86
|
+
images :owners => 'self'
|
87
|
+
end
|
88
|
+
|
89
|
+
# Retrieve a single Image
|
90
|
+
def image(image_id)
|
91
|
+
images(:image_ids => [image_id])[0]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Register an Image
|
95
|
+
def register_image(image_location)
|
96
|
+
action = 'RegisterImage'
|
97
|
+
params = {
|
98
|
+
'Action' => action,
|
99
|
+
'ImageLocation' => image_location
|
100
|
+
}
|
101
|
+
|
102
|
+
response = send_query_request(params)
|
103
|
+
parser = Awsum::Ec2::RegisterImageParser.new(self)
|
104
|
+
parser.parse(response.body)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Deregister an Image. Once deregistered, you can no longer launch the Image
|
108
|
+
def deregister_image(image_id)
|
109
|
+
action = 'DeregisterImage'
|
110
|
+
params = {
|
111
|
+
'Action' => action,
|
112
|
+
'ImageId' => image_id
|
113
|
+
}
|
114
|
+
|
115
|
+
response = send_query_request(params)
|
116
|
+
response.is_a?(Net::HTTPSuccess)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Launch an ec2 Instance
|
120
|
+
#
|
121
|
+
# ===Options:
|
122
|
+
# * <tt>:min</tt> - The minimum number of instances to launch. Default: 1
|
123
|
+
# * <tt>:max</tt> - The maximum number of instances to launch. Default: 1
|
124
|
+
# * <tt>:key_name</tt> - The name of the key pair with which to launch instances
|
125
|
+
# * <tt>:security_groups</tt> - The names of security groups to associate launched instances with
|
126
|
+
# * <tt>:user_data</tt> - User data made available to instances (Note: Must be 16K or less, will be base64 encoded by Awsum)
|
127
|
+
# * <tt>:instance_type</tt> - The size of the instances to launch, can be one of [m1.small, m1.large, m1.xlarge, c1.medium, c1.xlarge], default is m1.small
|
128
|
+
# * <tt>:availability_zone</tt> - The name of the availability zone to launch this Instance in
|
129
|
+
# * <tt>:kernel_id</tt> - The ID of the kernel with which to launch instances
|
130
|
+
# * <tt>:ramdisk_id</tt> - The ID of the RAM disk with which to launch instances
|
131
|
+
# * <tt>:block_device_map</tt> - A 'hash' of mappings. E.g. {'instancestore0' => 'sdb'}
|
132
|
+
def run_instances(image_id, options = {})
|
133
|
+
options = {:min => 1, :max => 1}.merge(options)
|
134
|
+
action = 'RunInstances'
|
135
|
+
params = {
|
136
|
+
'Action' => action,
|
137
|
+
'ImageId' => image_id,
|
138
|
+
'MinCount' => options[:min],
|
139
|
+
'MaxCount' => options[:max],
|
140
|
+
'KeyName' => options[:key_name],
|
141
|
+
'UserData' => options[:user_data].nil? ? nil : Base64::encode64(options[:user_data]).gsub(/\n/, ''),
|
142
|
+
'InstanceType' => options[:instance_type],
|
143
|
+
'Placement.AvailabilityZone' => options[:availability_zone],
|
144
|
+
'KernelId' => options[:kernel_id],
|
145
|
+
'RamdiskId' => options[:ramdisk_id]
|
146
|
+
}
|
147
|
+
if options[:block_device_map].respond_to?(:keys)
|
148
|
+
map = options[:block_device_map]
|
149
|
+
map.keys.each_with_index do |key, i|
|
150
|
+
params["BlockDeviceMapping.#{i+1}.VirtualName"] = key
|
151
|
+
params["BlockDeviceMapping.#{i+1}.DeviceName"] = map[key]
|
152
|
+
end
|
153
|
+
else
|
154
|
+
raise ArgumentError.new("options[:block_device_map] - must be a key => value map") unless options[:block_device_map].nil?
|
155
|
+
end
|
156
|
+
params.merge!(array_to_params(options[:security_groups], "SecurityGroup"))
|
157
|
+
|
158
|
+
response = send_query_request(params)
|
159
|
+
parser = Awsum::Ec2::InstanceParser.new(self)
|
160
|
+
parser.parse(response.body)
|
161
|
+
end
|
162
|
+
alias_method :launch_instances, :run_instances
|
163
|
+
|
164
|
+
#Retrieve the information on a number of Instance(s)
|
165
|
+
def instances(*instance_ids)
|
166
|
+
action = 'DescribeInstances'
|
167
|
+
params = {
|
168
|
+
'Action' => action
|
169
|
+
}
|
170
|
+
params.merge!(array_to_params(instance_ids, 'InstanceId'))
|
171
|
+
|
172
|
+
response = send_query_request(params)
|
173
|
+
parser = Awsum::Ec2::InstanceParser.new(self)
|
174
|
+
parser.parse(response.body)
|
175
|
+
end
|
176
|
+
|
177
|
+
#Retrieve the information on a single Instance
|
178
|
+
def instance(instance_id)
|
179
|
+
instances([instance_id])[0]
|
180
|
+
end
|
181
|
+
|
182
|
+
# Retrieves the currently running Instance
|
183
|
+
# This should only be run on a running EC2 instance
|
184
|
+
def me
|
185
|
+
require 'open-uri'
|
186
|
+
begin
|
187
|
+
instance_id = open('http://169.254.169.254/latest/meta-data/instance-id').read
|
188
|
+
instance instance_id
|
189
|
+
rescue OpenURI::HTTPError => e
|
190
|
+
nil
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Retreives the user-data supplied when starting the currently running Instance
|
195
|
+
# This should only be run on a running EC2 instance
|
196
|
+
def user_data
|
197
|
+
require 'open-uri'
|
198
|
+
begin
|
199
|
+
open('http://169.254.169.254/latest/user-data').read
|
200
|
+
rescue OpenURI::HTTPError => e
|
201
|
+
nil
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Terminates the Instance(s)
|
206
|
+
#
|
207
|
+
# Returns true if the terminations succeeds, false otherwise
|
208
|
+
def terminate_instances(*instance_ids)
|
209
|
+
action = 'TerminateInstances'
|
210
|
+
params = {
|
211
|
+
'Action' => action
|
212
|
+
}
|
213
|
+
params.merge!(array_to_params(instance_ids, 'InstanceId'))
|
214
|
+
|
215
|
+
response = send_query_request(params)
|
216
|
+
response.is_a?(Net::HTTPSuccess)
|
217
|
+
end
|
218
|
+
|
219
|
+
#Retrieve the information on a number of Volume(s)
|
220
|
+
def volumes(*volume_ids)
|
221
|
+
action = 'DescribeVolumes'
|
222
|
+
params = {
|
223
|
+
'Action' => action
|
224
|
+
}
|
225
|
+
params.merge!(array_to_params(volume_ids, 'VolumeId'))
|
226
|
+
|
227
|
+
response = send_query_request(params)
|
228
|
+
parser = Awsum::Ec2::VolumeParser.new(self)
|
229
|
+
parser.parse(response.body)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Retreive information on a Volume
|
233
|
+
def volume(volume_id)
|
234
|
+
volumes(volume_id)[0]
|
235
|
+
end
|
236
|
+
|
237
|
+
# Create a new volume
|
238
|
+
#
|
239
|
+
# ===Options:
|
240
|
+
# * <tt>:size</tt> - The size of the volume to be created (in GB) (<b>NOTE:</b> Required if you are not creating from a snapshot)
|
241
|
+
# * <tt>:snapshot_id</tt> - The snapshot id from which to create the volume
|
242
|
+
#
|
243
|
+
def create_volume(availability_zone, options = {})
|
244
|
+
raise ArgumentError.new('You must specify a size if not creating a volume from a snapshot') if options[:snapshot_id].blank? && options[:size].blank?
|
245
|
+
|
246
|
+
action = 'CreateVolume'
|
247
|
+
params = {
|
248
|
+
'Action' => action,
|
249
|
+
'AvailabilityZone' => availability_zone
|
250
|
+
}
|
251
|
+
params['Size'] = options[:size] unless options[:size].blank?
|
252
|
+
params['SnapshotId'] = options[:snapshot_id] unless options[:snapshot_id].blank?
|
253
|
+
|
254
|
+
response = send_query_request(params)
|
255
|
+
parser = Awsum::Ec2::VolumeParser.new(self)
|
256
|
+
parser.parse(response.body)[0]
|
257
|
+
end
|
258
|
+
|
259
|
+
# Attach a volume to an instance
|
260
|
+
def attach_volume(volume_id, instance_id, device = '/dev/sdh')
|
261
|
+
action = 'AttachVolume'
|
262
|
+
params = {
|
263
|
+
'Action' => action,
|
264
|
+
'VolumeId' => volume_id,
|
265
|
+
'InstanceId' => instance_id,
|
266
|
+
'Device' => device
|
267
|
+
}
|
268
|
+
|
269
|
+
response = send_query_request(params)
|
270
|
+
response.is_a?(Net::HTTPSuccess)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Detach a volume from an instance
|
274
|
+
#
|
275
|
+
# ===Options
|
276
|
+
# * <tt>:instance_id</tt> - The ID of the instance from which the volume will detach
|
277
|
+
# * <tt>:device</tt> - The device name
|
278
|
+
# * <tt>:force</tt> - Whether to force the detachment. <b>NOTE:</b> If forced you may have data corruption issues.
|
279
|
+
def detach_volume(volume_id, options = {})
|
280
|
+
action = 'DetachVolume'
|
281
|
+
params = {
|
282
|
+
'Action' => action,
|
283
|
+
'VolumeId' => volume_id
|
284
|
+
}
|
285
|
+
params['InstanceId'] = options[:instance_id] unless options[:instance_id].blank?
|
286
|
+
params['Device'] = options[:device] unless options[:device].blank?
|
287
|
+
params['Force'] = options[:force] unless options[:force].blank?
|
288
|
+
|
289
|
+
response = send_query_request(params)
|
290
|
+
response.is_a?(Net::HTTPSuccess)
|
291
|
+
end
|
292
|
+
|
293
|
+
# Delete a volume
|
294
|
+
def delete_volume(volume_id)
|
295
|
+
action = 'DeleteVolume'
|
296
|
+
params = {
|
297
|
+
'Action' => action,
|
298
|
+
'VolumeId' => volume_id
|
299
|
+
}
|
300
|
+
|
301
|
+
response = send_query_request(params)
|
302
|
+
response.is_a?(Net::HTTPSuccess)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Create a Snapshot of a Volume
|
306
|
+
def create_snapshot(volume_id)
|
307
|
+
action = 'CreateSnapshot'
|
308
|
+
params = {
|
309
|
+
'Action' => action,
|
310
|
+
'VolumeId' => volume_id
|
311
|
+
}
|
312
|
+
|
313
|
+
response = send_query_request(params)
|
314
|
+
parser = Awsum::Ec2::SnapshotParser.new(self)
|
315
|
+
parser.parse(response.body)[0]
|
316
|
+
end
|
317
|
+
|
318
|
+
# List Snapshot(s)
|
319
|
+
def snapshots(*snapshot_ids)
|
320
|
+
action = 'DescribeSnapshots'
|
321
|
+
params = {
|
322
|
+
'Action' => action
|
323
|
+
}
|
324
|
+
params.merge!(array_to_params(snapshot_ids, 'SnapshotId'))
|
325
|
+
|
326
|
+
response = send_query_request(params)
|
327
|
+
parser = Awsum::Ec2::SnapshotParser.new(self)
|
328
|
+
parser.parse(response.body)
|
329
|
+
end
|
330
|
+
|
331
|
+
# Get the information about a Snapshot
|
332
|
+
def snapshot(snapshot_id)
|
333
|
+
snapshots(snapshot_id)[0]
|
334
|
+
end
|
335
|
+
|
336
|
+
# Delete a Snapshot
|
337
|
+
def delete_snapshot(snapshot_id)
|
338
|
+
action = 'DeleteSnapshot'
|
339
|
+
params = {
|
340
|
+
'Action' => action,
|
341
|
+
'SnapshotId' => snapshot_id
|
342
|
+
}
|
343
|
+
|
344
|
+
response = send_query_request(params)
|
345
|
+
response.is_a?(Net::HTTPSuccess)
|
346
|
+
end
|
347
|
+
|
348
|
+
# List all AvailabilityZone(s)
|
349
|
+
def availability_zones(*zone_names)
|
350
|
+
action = 'DescribeAvailabilityZones'
|
351
|
+
params = {
|
352
|
+
'Action' => action
|
353
|
+
}
|
354
|
+
params.merge!(array_to_params(zone_names, 'ZoneName'))
|
355
|
+
|
356
|
+
response = send_query_request(params)
|
357
|
+
parser = Awsum::Ec2::AvailabilityZoneParser.new(self)
|
358
|
+
parser.parse(response.body)
|
359
|
+
end
|
360
|
+
|
361
|
+
# List all Region(s)
|
362
|
+
def regions(*region_names)
|
363
|
+
action = 'DescribeRegions'
|
364
|
+
params = {
|
365
|
+
'Action' => action
|
366
|
+
}
|
367
|
+
params.merge!(array_to_params(region_names, 'Region'))
|
368
|
+
|
369
|
+
response = send_query_request(params)
|
370
|
+
parser = Awsum::Ec2::RegionParser.new(self)
|
371
|
+
parser.parse(response.body)
|
372
|
+
end
|
373
|
+
|
374
|
+
# List a Region
|
375
|
+
def region(region_name)
|
376
|
+
regions(region_name)[0]
|
377
|
+
end
|
378
|
+
|
379
|
+
# List Addresses
|
380
|
+
def addresses(*public_ips)
|
381
|
+
action = 'DescribeAddresses'
|
382
|
+
params = {
|
383
|
+
'Action' => action
|
384
|
+
}
|
385
|
+
params.merge!(array_to_params(public_ips, 'PublicIp'))
|
386
|
+
|
387
|
+
response = send_query_request(params)
|
388
|
+
parser = Awsum::Ec2::AddressParser.new(self)
|
389
|
+
parser.parse(response.body)
|
390
|
+
end
|
391
|
+
|
392
|
+
# Get the Address with a specific public ip
|
393
|
+
def address(public_ip)
|
394
|
+
addresses(public_ip)[0]
|
395
|
+
end
|
396
|
+
|
397
|
+
# Allocate Address
|
398
|
+
#
|
399
|
+
# Will aquire an elastic ip address for use with your account
|
400
|
+
def allocate_address
|
401
|
+
action = 'AllocateAddress'
|
402
|
+
params = {
|
403
|
+
'Action' => action
|
404
|
+
}
|
405
|
+
|
406
|
+
response = send_query_request(params)
|
407
|
+
parser = Awsum::Ec2::AddressParser.new(self)
|
408
|
+
parser.parse(response.body)[0]
|
409
|
+
end
|
410
|
+
|
411
|
+
# Associate Address
|
412
|
+
#
|
413
|
+
# Will link an allocated elastic ip address to an Instance
|
414
|
+
#
|
415
|
+
# <b>NOTE:</b> If the ip address is already associated with another instance, it will be associated with the new instance.
|
416
|
+
#
|
417
|
+
# You can run this command more than once and it will not return an error.
|
418
|
+
def associate_address(instance_id, public_ip)
|
419
|
+
action = 'AssociateAddress'
|
420
|
+
params = {
|
421
|
+
'Action' => action,
|
422
|
+
'InstanceId' => instance_id,
|
423
|
+
'PublicIp' => public_ip
|
424
|
+
}
|
425
|
+
|
426
|
+
response = send_query_request(params)
|
427
|
+
response.is_a?(Net::HTTPSuccess)
|
428
|
+
end
|
429
|
+
|
430
|
+
# Disassociate Address
|
431
|
+
#
|
432
|
+
# Will disassociate an allocated elastic ip address from the Instance it's allocated to
|
433
|
+
#
|
434
|
+
# <b>NOTE:</b> You can run this command more than once and it will not return an error.
|
435
|
+
def disassociate_address(public_ip)
|
436
|
+
action = 'DisassociateAddress'
|
437
|
+
params = {
|
438
|
+
'Action' => action,
|
439
|
+
'PublicIp' => public_ip
|
440
|
+
}
|
441
|
+
|
442
|
+
response = send_query_request(params)
|
443
|
+
response.is_a?(Net::HTTPSuccess)
|
444
|
+
end
|
445
|
+
|
446
|
+
# Releases an associated Address
|
447
|
+
#
|
448
|
+
# <b>NOTE:</b> This is not a direct call to the Amazon web service. This is a safe operation that will first check to see if the address is allocated to an instance and fail if it is
|
449
|
+
def release_address(public_ip)
|
450
|
+
address = address(public_ip)
|
451
|
+
|
452
|
+
if address.instance_id.nil?
|
453
|
+
action = 'ReleaseAddress'
|
454
|
+
params = {
|
455
|
+
'Action' => action,
|
456
|
+
'PublicIp' => public_ip
|
457
|
+
}
|
458
|
+
|
459
|
+
response = send_query_request(params)
|
460
|
+
response.is_a?(Net::HTTPSuccess)
|
461
|
+
else
|
462
|
+
raise 'Address is currently allocated' #FIXME: Add a proper Awsum error here
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
# Releases an associated Address
|
467
|
+
#
|
468
|
+
# <b>NOTE:</b> This will disassociate an address automatically if it is associated with an instance
|
469
|
+
def release_address!(public_ip)
|
470
|
+
action = 'ReleaseAddress'
|
471
|
+
params = {
|
472
|
+
'Action' => action,
|
473
|
+
'PublicIp' => public_ip
|
474
|
+
}
|
475
|
+
|
476
|
+
response = send_query_request(params)
|
477
|
+
response.is_a?(Net::HTTPSuccess)
|
478
|
+
end
|
479
|
+
|
480
|
+
# List KeyPair(s)
|
481
|
+
def key_pairs(*key_names)
|
482
|
+
action = 'DescribeKeyPairs'
|
483
|
+
params = {
|
484
|
+
'Action' => action
|
485
|
+
}
|
486
|
+
params.merge!(array_to_params(key_names, 'KeyName'))
|
487
|
+
|
488
|
+
response = send_query_request(params)
|
489
|
+
parser = Awsum::Ec2::KeyPairParser.new(self)
|
490
|
+
parser.parse(response.body)
|
491
|
+
end
|
492
|
+
|
493
|
+
# Get a single KeyPair
|
494
|
+
def key_pair(key_name)
|
495
|
+
key_pairs(key_name)[0]
|
496
|
+
end
|
497
|
+
|
498
|
+
# Create a new KeyPair
|
499
|
+
def create_key_pair(key_name)
|
500
|
+
action = 'CreateKeyPair'
|
501
|
+
params = {
|
502
|
+
'Action' => action,
|
503
|
+
'KeyName' => key_name
|
504
|
+
}
|
505
|
+
|
506
|
+
response = send_query_request(params)
|
507
|
+
parser = Awsum::Ec2::KeyPairParser.new(self)
|
508
|
+
parser.parse(response.body)[0]
|
509
|
+
end
|
510
|
+
|
511
|
+
# Delete a KeyPair
|
512
|
+
def delete_key_pair(key_name)
|
513
|
+
action = 'DeleteKeyPair'
|
514
|
+
params = {
|
515
|
+
'Action' => action,
|
516
|
+
'KeyName' => key_name
|
517
|
+
}
|
518
|
+
|
519
|
+
response = send_query_request(params)
|
520
|
+
response.is_a?(Net::HTTPSuccess)
|
521
|
+
end
|
522
|
+
|
523
|
+
# List SecurityGroup(s)
|
524
|
+
def security_groups(*group_names)
|
525
|
+
action = 'DescribeSecurityGroups'
|
526
|
+
params = {
|
527
|
+
'Action' => action
|
528
|
+
}
|
529
|
+
params.merge!(array_to_params(group_names, 'GroupName'))
|
530
|
+
|
531
|
+
response = send_query_request(params)
|
532
|
+
parser = Awsum::Ec2::SecurityGroupParser.new(self)
|
533
|
+
parser.parse(response.body)
|
534
|
+
end
|
535
|
+
|
536
|
+
# Get a single SecurityGroup
|
537
|
+
def security_group(group_name)
|
538
|
+
security_groups(group_name)[0]
|
539
|
+
end
|
540
|
+
|
541
|
+
# Create a new SecurityGroup
|
542
|
+
def create_security_group(name, description)
|
543
|
+
action = 'CreateSecurityGroup'
|
544
|
+
params = {
|
545
|
+
'Action' => action,
|
546
|
+
'GroupName' => name,
|
547
|
+
'GroupDescription' => description
|
548
|
+
}
|
549
|
+
|
550
|
+
response = send_query_request(params)
|
551
|
+
response.is_a?(Net::HTTPSuccess)
|
552
|
+
end
|
553
|
+
|
554
|
+
# Delete a SecurityGroup
|
555
|
+
def delete_security_group(group_name)
|
556
|
+
action = 'DeleteSecurityGroup'
|
557
|
+
params = {
|
558
|
+
'Action' => action,
|
559
|
+
'GroupName' => group_name
|
560
|
+
}
|
561
|
+
|
562
|
+
response = send_query_request(params)
|
563
|
+
response.is_a?(Net::HTTPSuccess)
|
564
|
+
end
|
565
|
+
|
566
|
+
# Authorize access on a specific security group
|
567
|
+
#
|
568
|
+
# ===Options:
|
569
|
+
# ====User/Group access
|
570
|
+
# * <tt>:source_security_group_name</tt> - Name of the security group to authorize access to when operating on a user/group pair
|
571
|
+
# * <tt>:source_security_group_owner_id</tt> - Owner of the security group to authorize access to when operating on a user/group pair
|
572
|
+
# ====CIDR IP access
|
573
|
+
# * <tt>:ip_protocol</tt> - IP protocol to authorize access to when operating on a CIDR IP (tcp, udp or icmp) (default: tcp)
|
574
|
+
# * <tt>:from_port</tt> - Bottom of port range to authorize access to when operating on a CIDR IP. This contains the ICMP type if ICMP is being authorized.
|
575
|
+
# * <tt>:to_port</tt> - Top of port range to authorize access to when operating on a CIDR IP. This contains the ICMP type if ICMP is being authorized.
|
576
|
+
# * <tt>:cidr_ip</tt> - CIDR IP range to authorize access to when operating on a CIDR IP. (default: 0.0.0.0/0)
|
577
|
+
def authorize_security_group_ingress(group_name, options = {})
|
578
|
+
got_at_least_one_user_group_option = !options[:source_security_group_name].nil? || !options[:source_security_group_owner_id].nil?
|
579
|
+
got_user_group_options = !options[:source_security_group_name].nil? && !options[:source_security_group_owner_id].nil?
|
580
|
+
got_at_least_one_cidr_option = !options[:ip_protocol].nil? || !options[:from_port].nil? || !options[:to_port].nil? || !options[:cidr_ip].nil?
|
581
|
+
#Add in defaults
|
582
|
+
options = {:cidr_ip => '0.0.0.0/0'}.merge(options) if got_at_least_one_cidr_option
|
583
|
+
options = {:ip_protocol => 'tcp'}.merge(options) if got_at_least_one_cidr_option
|
584
|
+
got_cidr_options = !options[:ip_protocol].nil? && !options[:from_port].nil? && !options[:to_port].nil? && !options[:cidr_ip].nil?
|
585
|
+
raise ArgumentError.new('Can only authorize user/group or CIDR IP, not both') if got_at_least_one_user_group_option && got_at_least_one_cidr_option
|
586
|
+
raise ArgumentError.new('Need all user/group options when authorizing user/group access') if got_at_least_one_user_group_option && !got_user_group_options
|
587
|
+
raise ArgumentError.new('Need all CIDR IP options when authorizing CIDR IP access') if got_at_least_one_cidr_option && !got_cidr_options
|
588
|
+
raise ArgumentError.new('ip_protocol can only be one of tcp, udp or icmp') if got_at_least_one_cidr_option && !%w(tcp udp icmp).detect{|p| p == options[:ip_protocol] }
|
589
|
+
|
590
|
+
action = 'AuthorizeSecurityGroupIngress'
|
591
|
+
params = {
|
592
|
+
'Action' => action,
|
593
|
+
'GroupName' => group_name
|
594
|
+
}
|
595
|
+
params['SourceSecurityGroupName'] = options[:source_security_group_name] unless options[:source_security_group_name].nil?
|
596
|
+
params['SourceSecurityGroupOwnerId'] = options[:source_security_group_owner_id] unless options[:source_security_group_owner_id].nil?
|
597
|
+
params['IpProtocol'] = options[:ip_protocol] unless options[:ip_protocol].nil?
|
598
|
+
params['FromPort'] = options[:from_port] unless options[:from_port].nil?
|
599
|
+
params['ToPort'] = options[:to_port] unless options[:to_port].nil?
|
600
|
+
params['CidrIp'] = options[:cidr_ip] unless options[:cidr_ip].nil?
|
601
|
+
|
602
|
+
response = send_query_request(params)
|
603
|
+
response.is_a?(Net::HTTPSuccess)
|
604
|
+
end
|
605
|
+
|
606
|
+
# Revoke access on a specific SecurityGroup
|
607
|
+
#
|
608
|
+
# ===Options:
|
609
|
+
# ====User/Group access
|
610
|
+
# * <tt>:source_security_group_name</tt> - Name of the security group to authorize access to when operating on a user/group pair
|
611
|
+
# * <tt>:source_security_group_owner_id</tt> - Owner of the security group to authorize access to when operating on a user/group pair
|
612
|
+
# ====CIDR IP access
|
613
|
+
# * <tt>:ip_protocol</tt> - IP protocol to authorize access to when operating on a CIDR IP (tcp, udp or icmp) (default: tcp)
|
614
|
+
# * <tt>:from_port</tt> - Bottom of port range to authorize access to when operating on a CIDR IP. This contains the ICMP type if ICMP is being authorized.
|
615
|
+
# * <tt>:to_port</tt> - Top of port range to authorize access to when operating on a CIDR IP. This contains the ICMP type if ICMP is being authorized.
|
616
|
+
# * <tt>:cidr_ip</tt> - CIDR IP range to authorize access to when operating on a CIDR IP. (default: 0.0.0.0/0)
|
617
|
+
def revoke_security_group_ingress(group_name, options = {})
|
618
|
+
got_at_least_one_user_group_option = !options[:source_security_group_name].nil? || !options[:source_security_group_owner_id].nil?
|
619
|
+
got_user_group_options = !options[:source_security_group_name].nil? && !options[:source_security_group_owner_id].nil?
|
620
|
+
got_at_least_one_cidr_option = !options[:ip_protocol].nil? || !options[:from_port].nil? || !options[:to_port].nil? || !options[:cidr_ip].nil?
|
621
|
+
#Add in defaults
|
622
|
+
options = {:cidr_ip => '0.0.0.0/0'}.merge(options) if got_at_least_one_cidr_option
|
623
|
+
options = {:ip_protocol => 'tcp'}.merge(options) if got_at_least_one_cidr_option
|
624
|
+
got_cidr_options = !options[:ip_protocol].nil? && !options[:from_port].nil? && !options[:to_port].nil? && !options[:cidr_ip].nil?
|
625
|
+
raise ArgumentError.new('Can only authorize user/group or CIDR IP, not both') if got_at_least_one_user_group_option && got_at_least_one_cidr_option
|
626
|
+
raise ArgumentError.new('Need all user/group options when revoking user/group access') if got_at_least_one_user_group_option && !got_user_group_options
|
627
|
+
raise ArgumentError.new('Need all CIDR IP options when revoking CIDR IP access') if got_at_least_one_cidr_option && !got_cidr_options
|
628
|
+
raise ArgumentError.new('ip_protocol can only be one of tcp, udp or icmp') if got_at_least_one_cidr_option && !%w(tcp udp icmp).detect{|p| p == options[:ip_protocol] }
|
629
|
+
|
630
|
+
action = 'RevokeSecurityGroupIngress'
|
631
|
+
params = {
|
632
|
+
'Action' => action,
|
633
|
+
'GroupName' => group_name
|
634
|
+
}
|
635
|
+
params['SourceSecurityGroupName'] = options[:source_security_group_name] unless options[:source_security_group_name].nil?
|
636
|
+
params['SourceSecurityGroupOwnerId'] = options[:source_security_group_owner_id] unless options[:source_security_group_owner_id].nil?
|
637
|
+
params['IpProtocol'] = options[:ip_protocol] unless options[:ip_protocol].nil?
|
638
|
+
params['FromPort'] = options[:from_port] unless options[:from_port].nil?
|
639
|
+
params['ToPort'] = options[:to_port] unless options[:to_port].nil?
|
640
|
+
params['CidrIp'] = options[:cidr_ip] unless options[:cidr_ip].nil?
|
641
|
+
|
642
|
+
response = send_query_request(params)
|
643
|
+
response.is_a?(Net::HTTPSuccess)
|
644
|
+
end
|
645
|
+
|
646
|
+
#private
|
647
|
+
#The host to make all requests against
|
648
|
+
def host
|
649
|
+
@host ||= 'ec2.amazonaws.com'
|
650
|
+
end
|
651
|
+
|
652
|
+
def host=(host)
|
653
|
+
@host = host
|
654
|
+
end
|
655
|
+
end
|
656
|
+
end
|