amazon-ec2 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +6 -0
- data/History.txt +8 -0
- data/Manifest.txt +12 -0
- data/README.txt +186 -0
- data/Rakefile +57 -0
- data/examples/ec2-example.rb +45 -0
- data/lib/EC2.rb +555 -0
- data/lib/EC2/version.rb +9 -0
- data/setup.rb +1585 -0
- data/test/EC2_test.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +75 -0
data/CHANGELOG.txt
ADDED
data/History.txt
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
=History.txt : Amazon Elastic Compute Cloud (EC2) Ruby Gem
|
2
|
+
|
3
|
+
==Version History
|
4
|
+
|
5
|
+
===0.0.1 (12/23/2006)
|
6
|
+
* Initial release of the Ruby Gem. This includes the version of the library exactly as provided by
|
7
|
+
Amazon Web Services as example code. No changes or enhancements to that code were made other than
|
8
|
+
packaging it as a Ruby Gem.
|
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
=README.txt : Amazon Elastic Compute Cloud (EC2) Ruby Gem
|
2
|
+
|
3
|
+
AWS EC2 is an interface library that can be used to interact
|
4
|
+
with the Amazon EC2 system. The library exposes one main interface class,
|
5
|
+
'AWSAuthConnection'. This class performs all the operations for using using
|
6
|
+
the EC2 service including header signing.
|
7
|
+
|
8
|
+
==Important note about this project:
|
9
|
+
Please note that I am packaging this sample code up as a service to the
|
10
|
+
Ruby community and do not plan to be actively maintaining this code
|
11
|
+
on a regular basis. If you can contribute documentation or additional tests as
|
12
|
+
Subversion patch files I will be happy to incorporate those directly into the library.
|
13
|
+
Alternatively, if you are interested in becoming a contributing developer with checkin
|
14
|
+
privileges on this source code please feel free to contact me.
|
15
|
+
|
16
|
+
==RubyForge Project Info
|
17
|
+
This project is hosted on the RubyForge project server. You can find the project at:
|
18
|
+
|
19
|
+
http://amazon-ec2.rubyforge.org/
|
20
|
+
http://rubyforge.org/projects/amazon-ec2/
|
21
|
+
|
22
|
+
Please actively report any bugs that you find using the bug tracker found on the RubyForge site. Please submit any patches as well through that mechanism. If you feel you want to help contribute to the project please contact me at:
|
23
|
+
|
24
|
+
grempe @no spam@ rubyforge.org
|
25
|
+
|
26
|
+
==Prerequisites:
|
27
|
+
|
28
|
+
An Amazon Web Services Developer account which is also signed up for Amazon EC2.
|
29
|
+
You will need the AWS Access Key ID and Secret Access Key that they provide when you
|
30
|
+
sign up.
|
31
|
+
|
32
|
+
|
33
|
+
==Installation:
|
34
|
+
|
35
|
+
The specific installation may vary according to your operation system. Some examples are below.
|
36
|
+
|
37
|
+
Linux/Mac OS X:
|
38
|
+
|
39
|
+
sudo gem install amazon-ec2
|
40
|
+
|
41
|
+
Windows:
|
42
|
+
|
43
|
+
gem install amazon-ec2
|
44
|
+
|
45
|
+
|
46
|
+
==Usage:
|
47
|
+
|
48
|
+
The public methods on AWSAuthConnection closely mirror the EC2 Query API, and
|
49
|
+
as such the Query API Reference in the EC2 Developer Guide should be consulted.
|
50
|
+
|
51
|
+
===Example Code Usage (Stand-alone Ruby Application):
|
52
|
+
|
53
|
+
#!/usr/bin/env ruby
|
54
|
+
require 'rubygems'
|
55
|
+
require 'ec2'
|
56
|
+
AWS_ACCESS_KEY_ID = '--YOUR AWS ACCESS KEY ID--'
|
57
|
+
AWS_SECRET_ACCESS_KEY = '--YOUR AWS SECRET ACCESS KEY--'
|
58
|
+
conn = EC2::AWSAuthConnection.new(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
|
59
|
+
puts "----- listing images -----"
|
60
|
+
puts conn.describe_images()
|
61
|
+
|
62
|
+
|
63
|
+
An example client is provided as a starting point in this Gem installation which
|
64
|
+
you may consult for a few more detailed usage examples.
|
65
|
+
|
66
|
+
examples/ec2-example.rb
|
67
|
+
|
68
|
+
|
69
|
+
===Example Code Usage (Ruby on Rails Application):
|
70
|
+
|
71
|
+
config/environment.rb:
|
72
|
+
...
|
73
|
+
# Include Amazon Web Services EC2 library gem
|
74
|
+
require 'ec2'
|
75
|
+
|
76
|
+
app/controllers/your_controller.rb:
|
77
|
+
conn = EC2::AWSAuthConnection.new(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
|
78
|
+
|
79
|
+
# The .parse method gives you back an array of values you can use in your view
|
80
|
+
@ec2_images = conn.describe_images().parse
|
81
|
+
|
82
|
+
-- OR with some params (in this case specific owner ID's) --
|
83
|
+
|
84
|
+
@ec2_images_mine = ec2.describe_images([],["522821470517"],[]).parse
|
85
|
+
|
86
|
+
|
87
|
+
app/views/your_view.rhtml:
|
88
|
+
|
89
|
+
<%= debug(@ec2_images) %>
|
90
|
+
|
91
|
+
-- OR --
|
92
|
+
|
93
|
+
<% @ec2_images.each do |image| %>
|
94
|
+
<% image.each_with_index do |value, index| %>
|
95
|
+
<%= "#{index} => #{value}" %><br />
|
96
|
+
<% end %>
|
97
|
+
<% end %>
|
98
|
+
|
99
|
+
-- OR --
|
100
|
+
|
101
|
+
<table>
|
102
|
+
<tr>
|
103
|
+
<th>Id</th>
|
104
|
+
<th>Location</th>
|
105
|
+
<th>Owner</th>
|
106
|
+
<th>State</th>
|
107
|
+
<th>Public?</th>
|
108
|
+
</tr>
|
109
|
+
|
110
|
+
<% for ec2_image in @ec2_images %>
|
111
|
+
<tr>
|
112
|
+
<td><%=h ec2_image[1] %></td>
|
113
|
+
<td><%=h ec2_image[2] %></td>
|
114
|
+
<td><%=h ec2_image[3] %></td>
|
115
|
+
<td><%=h ec2_image[4] %></td>
|
116
|
+
<td><%=h ec2_image[5] %></td>
|
117
|
+
</tr>
|
118
|
+
<% end %>
|
119
|
+
</table>
|
120
|
+
|
121
|
+
|
122
|
+
==To Do:
|
123
|
+
|
124
|
+
* As provided by Amazon, this library has nearly non-existent error handling.
|
125
|
+
All errors from lower libraries are simply passed up. The response code in
|
126
|
+
the returned object needs to be checked after each request to verify
|
127
|
+
whether the request succeeded.
|
128
|
+
* Documentation - The code is almost devoid of documentation. RDoc comments in
|
129
|
+
the code would be very useful.
|
130
|
+
* Automated Unit Tests - There are currently no unit tests for this code.
|
131
|
+
A suite of tests to help exercise the code would greatly improve our confidence.
|
132
|
+
|
133
|
+
|
134
|
+
==Credits:
|
135
|
+
|
136
|
+
* The original sample code for this library was provided by Amazon Web Services, LLC.
|
137
|
+
Thanks to them for providing the samples that got this started. They took the wind out
|
138
|
+
of the sails of my own version of this library (which was maybe 75% complete), but they
|
139
|
+
probably saved me some hair that otherwise would have suffered self-inflicted removal.
|
140
|
+
|
141
|
+
* Thanks to Dr. Nic Williams and his great 'newgem' Ruby Gem Generator which can be found
|
142
|
+
at http://drnicwilliams.com/2006/10/11/generating-new-gems. This helped me package up
|
143
|
+
this code for distribution in a flash.
|
144
|
+
|
145
|
+
|
146
|
+
=Original AWS README for this code (12/13/2006)
|
147
|
+
|
148
|
+
http://developer.amazonwebservices.com/connect/entry.jspa?externalID=553
|
149
|
+
|
150
|
+
This is one of a collection of interface libraries that can be used to interact
|
151
|
+
with the Amazon EC2 system in a number of different languages. They each
|
152
|
+
expose one main interface class, AWSAuthConnection. This performs all the
|
153
|
+
operations using the appropriate libraries for the language, including header
|
154
|
+
signing.
|
155
|
+
|
156
|
+
|
157
|
+
==Usage:
|
158
|
+
|
159
|
+
The public methods on AWSAuthConnection closely mirror the EC2 Query API, and
|
160
|
+
as such the Query API Reference in the EC2 Developer Guide should be consulted.
|
161
|
+
|
162
|
+
An example client is provided as a starting point.
|
163
|
+
|
164
|
+
|
165
|
+
==Prerequisites:
|
166
|
+
|
167
|
+
An Amazon Web Services Developer account signed up for Amazon EC2.
|
168
|
+
|
169
|
+
|
170
|
+
==Limitations:
|
171
|
+
|
172
|
+
These libraries have nearly non-existent error handling. All errors from lower
|
173
|
+
libraries are simply passed up. The response code in the returned object needs
|
174
|
+
to be checked after each request to verify whether the request succeeded.
|
175
|
+
|
176
|
+
It is our intention that these libraries act as a starting point for future
|
177
|
+
development. They are meant to show off the various operations and provide an
|
178
|
+
example of how to negotiate the authentication process.
|
179
|
+
|
180
|
+
This software code is made available "AS IS" without warranties of any kind.
|
181
|
+
You may copy, display, modify and redistribute the software code either by
|
182
|
+
itself or as incorporated into your code; provided that you do not remove any
|
183
|
+
proprietary notices. Your use of this software code is at your own risk and
|
184
|
+
you waive any claim against Amazon Web Services LLC or its affiliates with
|
185
|
+
respect to your use of this software code. (c) 2006 Amazon Web Services LLC or
|
186
|
+
its affiliates. All rights reserved.
|
data/Rakefile
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'EC2', 'version')
|
13
|
+
|
14
|
+
AUTHOR = ["Amazon Web Services LLC", "Glenn Rempe"] # can also be an array of Authors
|
15
|
+
EMAIL = "grempe@rubyforge.org"
|
16
|
+
DESCRIPTION = "An interface library that allows Ruby or Ruby on Rails applications to easily connect to the HTTP 'Query API' for the Amazon Web Services Elastic Compute Cloud (EC2) and manipulate server instances."
|
17
|
+
GEM_NAME = "amazon-ec2" # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = "amazon-ec2" # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
RELEASE_TYPES = %w( gem ) # can use: gem, tar, zip
|
21
|
+
|
22
|
+
|
23
|
+
NAME = "amazon-ec2"
|
24
|
+
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
25
|
+
VERS = ENV['VERSION'] || (EC2::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
26
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
27
|
+
RDOC_OPTS = ['--quiet', '--title', "AWS EC2 documentation",
|
28
|
+
"--opname", "index.html",
|
29
|
+
"--line-numbers",
|
30
|
+
"--main", "README.txt",
|
31
|
+
"--inline-source"]
|
32
|
+
|
33
|
+
# Generate all the Rake tasks
|
34
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
35
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
36
|
+
p.name = NAME
|
37
|
+
p.version = VERS
|
38
|
+
p.author = AUTHOR
|
39
|
+
p.description = DESCRIPTION
|
40
|
+
p.email = EMAIL
|
41
|
+
p.summary = DESCRIPTION
|
42
|
+
p.url = HOMEPATH
|
43
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
44
|
+
p.test_globs = ["test/**/*_test.rb"]
|
45
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
46
|
+
|
47
|
+
p.spec_extras = {
|
48
|
+
:extra_rdoc_files => ["README.txt", "History.txt", "CHANGELOG.txt"],
|
49
|
+
:rdoc_options => RDOC_OPTS,
|
50
|
+
:autorequire => "EC2"
|
51
|
+
}
|
52
|
+
|
53
|
+
# == Optional
|
54
|
+
#p.changes - A description of the release's latest changes.
|
55
|
+
#p.extra_deps - An array of rubygem dependencies.
|
56
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
57
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This software code is made available "AS IS" without warranties of any
|
4
|
+
# kind. You may copy, display, modify and redistribute the software
|
5
|
+
# code either by itself or as incorporated into your code; provided that
|
6
|
+
# you do not remove any proprietary notices. Your use of this software
|
7
|
+
# code is at your own risk and you waive any claim against Amazon Web
|
8
|
+
# Services LLC or its affiliates with respect to your use of this software
|
9
|
+
# code. (c) 2006 Amazon Web Services LLC or its affiliates. All rights
|
10
|
+
# reserved.
|
11
|
+
|
12
|
+
require 'rubygems'
|
13
|
+
require 'ec2'
|
14
|
+
|
15
|
+
AWS_ACCESS_KEY_ID = '--YOUR AWS ACCESS KEY ID--'
|
16
|
+
AWS_SECRET_ACCESS_KEY = '--YOUR AWS SECRET ACCESS KEY--'
|
17
|
+
|
18
|
+
# remove these next two lines as well, when you've updated your credentials.
|
19
|
+
puts "update #{$0} with your AWS credentials"
|
20
|
+
exit
|
21
|
+
|
22
|
+
SECURITY_GROUP_NAME = "ec2-example-rb-test-group"
|
23
|
+
|
24
|
+
conn = EC2::AWSAuthConnection.new(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
|
25
|
+
|
26
|
+
puts "----- listing images -----"
|
27
|
+
puts conn.describe_images()
|
28
|
+
|
29
|
+
puts "----- listing instances -----"
|
30
|
+
puts conn.describe_instances()
|
31
|
+
|
32
|
+
puts "----- creating a security group -----"
|
33
|
+
puts conn.create_securitygroup(SECURITY_GROUP_NAME, "ec-example.rb test group")
|
34
|
+
|
35
|
+
puts "----- listing security groups -----"
|
36
|
+
puts conn.describe_securitygroups()
|
37
|
+
|
38
|
+
puts "----- deleting a security group -----"
|
39
|
+
puts conn.delete_securitygroup(SECURITY_GROUP_NAME)
|
40
|
+
|
41
|
+
puts "----- listing keypairs (verbose mode) -----"
|
42
|
+
conn.verbose = true
|
43
|
+
puts conn.describe_keypairs()
|
44
|
+
|
45
|
+
|
data/lib/EC2.rb
ADDED
@@ -0,0 +1,555 @@
|
|
1
|
+
# This software code is made available "AS IS" without warranties of any
|
2
|
+
# kind. You may copy, display, modify and redistribute the software
|
3
|
+
# code either by itself or as incorporated into your code; provided that
|
4
|
+
# you do not remove any proprietary notices. Your use of this software
|
5
|
+
# code is at your own risk and you waive any claim against Amazon Web
|
6
|
+
# Services LLC or its affiliates with respect to your use of this software
|
7
|
+
# code. (c) 2006 Amazon Web Services LLC or its affiliates. All rights
|
8
|
+
# reserved.
|
9
|
+
|
10
|
+
require 'base64'
|
11
|
+
require 'cgi'
|
12
|
+
require 'openssl'
|
13
|
+
require 'digest/sha1'
|
14
|
+
require 'net/https'
|
15
|
+
require 'rexml/document'
|
16
|
+
require 'time'
|
17
|
+
|
18
|
+
# Require any lib files that we have bundled with this Ruby Gem
|
19
|
+
Dir[File.join(File.dirname(__FILE__), 'EC2/**/*.rb')].sort.each { |lib| require lib }
|
20
|
+
|
21
|
+
include REXML
|
22
|
+
|
23
|
+
module EC2
|
24
|
+
DEFAULT_HOST = 'ec2.amazonaws.com'
|
25
|
+
PORTS_BY_SECURITY = { true => 443, false => 80 }
|
26
|
+
API_VERSION = '2006-10-01'
|
27
|
+
RELEASE_VERSION = "7813"
|
28
|
+
|
29
|
+
# Builds the canonical string for signing.
|
30
|
+
# Note: The parameters in the path passed in must already be sorted in
|
31
|
+
# case-insensitive alphabetical order and must not be url encoded.
|
32
|
+
def EC2.canonical_string(path)
|
33
|
+
buf = path.gsub(/\&|\?|=/,"")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Encodes the given string with the aws_secret_access_key, by taking the
|
37
|
+
# hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
|
38
|
+
# url encode the result of that to protect the string if it's going to
|
39
|
+
# be used as a query string parameter.
|
40
|
+
def EC2.encode(aws_secret_access_key, str, urlencode=true)
|
41
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
42
|
+
b64_hmac =
|
43
|
+
Base64.encode64(
|
44
|
+
OpenSSL::HMAC.digest(digest, aws_secret_access_key, str)).strip
|
45
|
+
|
46
|
+
if urlencode
|
47
|
+
return CGI::escape(b64_hmac)
|
48
|
+
else
|
49
|
+
return b64_hmac
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# uses Net::HTTP to interface with EC2.
|
55
|
+
class AWSAuthConnection
|
56
|
+
|
57
|
+
attr_accessor :verbose
|
58
|
+
|
59
|
+
def initialize(aws_access_key_id, aws_secret_access_key, is_secure=true,
|
60
|
+
server=DEFAULT_HOST, port=PORTS_BY_SECURITY[is_secure])
|
61
|
+
@aws_access_key_id = aws_access_key_id
|
62
|
+
@aws_secret_access_key = aws_secret_access_key
|
63
|
+
@http = Net::HTTP.new(server, port)
|
64
|
+
@http.use_ssl = is_secure
|
65
|
+
@verbose = false
|
66
|
+
end
|
67
|
+
|
68
|
+
def pathlist(key, arr)
|
69
|
+
params = {}
|
70
|
+
arr.each_with_index do |value, i|
|
71
|
+
params["#{key}.#{i+1}"] = value
|
72
|
+
end
|
73
|
+
params
|
74
|
+
end
|
75
|
+
|
76
|
+
def register_image(imageLocation)
|
77
|
+
params = { "ImageLocation" => imageLocation }
|
78
|
+
RegisterImageResponse.new(make_request("RegisterImage", params))
|
79
|
+
end
|
80
|
+
|
81
|
+
def describe_images(imageIds=[], owners=[], executableBy=[])
|
82
|
+
params = pathlist("ImageId", imageIds)
|
83
|
+
params.merge!(pathlist("Owner", owners))
|
84
|
+
params.merge!(pathlist("ExecutableBy", executableBy))
|
85
|
+
DescribeImagesResponse.new(make_request("DescribeImages", params))
|
86
|
+
end
|
87
|
+
|
88
|
+
def deregister_image(imageId)
|
89
|
+
params = { "ImageId" => imageId }
|
90
|
+
DeregisterImageResponse.new(make_request("DeregisterImage", params))
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_keypair(keyName)
|
94
|
+
params = { "KeyName" => keyName }
|
95
|
+
CreateKeyPairResponse.new(make_request("CreateKeyPair", params))
|
96
|
+
end
|
97
|
+
|
98
|
+
def describe_keypairs(keyNames=[])
|
99
|
+
params = pathlist("KeyName", keyNames)
|
100
|
+
DescribeKeyPairsResponse.new(make_request("DescribeKeyPairs", params))
|
101
|
+
end
|
102
|
+
|
103
|
+
def delete_keypair(keyName)
|
104
|
+
params = { "KeyName" => keyName }
|
105
|
+
DeleteKeyPairResponse.new(make_request("DeleteKeyPair", params))
|
106
|
+
end
|
107
|
+
|
108
|
+
def run_instances(imageId, kwargs={})
|
109
|
+
in_params = { :minCount=>1, :maxCount=>1, :keyname=>nil, :groupIds=>[], :userData=>nil, :base64Encoded=>false }
|
110
|
+
in_params.merge!(kwargs)
|
111
|
+
userData = Base64.encode64(userData) if userData and not base64Encoded
|
112
|
+
|
113
|
+
params = {
|
114
|
+
"ImageId" => imageId,
|
115
|
+
"MinCount" => in_params[:minCount].to_s,
|
116
|
+
"MaxCount" => in_params[:maxCount].to_s,
|
117
|
+
}.merge(pathlist("SecurityGroup", groupIds))
|
118
|
+
|
119
|
+
params["KeyName"] = keyName unless keyName.nil?
|
120
|
+
|
121
|
+
RunInstancesResponse.new(make_request("RunInstances", params))
|
122
|
+
end
|
123
|
+
|
124
|
+
def describe_instances(instanceIds=[])
|
125
|
+
params = pathlist("InstanceId", instanceIds)
|
126
|
+
DescribeInstancesResponse.new(make_request("DescribeInstances", params))
|
127
|
+
end
|
128
|
+
|
129
|
+
def terminate_instances(instanceIds)
|
130
|
+
params = pathlist("InstanceId", instanceIds)
|
131
|
+
TerminateInstancesResponse.new(make_request("TerminateInstances", params))
|
132
|
+
end
|
133
|
+
|
134
|
+
def create_securitygroup(groupName, groupDescription)
|
135
|
+
params = {
|
136
|
+
"GroupName" => groupName,
|
137
|
+
"GroupDescription" => groupDescription
|
138
|
+
}
|
139
|
+
CreateSecurityGroupResponse.new(make_request("CreateSecurityGroup", params))
|
140
|
+
end
|
141
|
+
|
142
|
+
def describe_securitygroups(groupNames=[])
|
143
|
+
params = pathlist("GroupName", groupNames)
|
144
|
+
DescribeSecurityGroupsResponse.new(make_request("DescribeSecurityGroups", params))
|
145
|
+
end
|
146
|
+
|
147
|
+
def delete_securitygroup(groupName)
|
148
|
+
params = { "GroupName" => groupName }
|
149
|
+
DeleteSecurityGroupResponse.new(make_request("DeleteSecurityGroup", params))
|
150
|
+
end
|
151
|
+
|
152
|
+
def authorize(*args)
|
153
|
+
params = auth_revoke_impl(*args)
|
154
|
+
AuthorizeSecurityGroupIngressResponse.new(make_request("AuthorizeSecurityGroupIngress", params))
|
155
|
+
end
|
156
|
+
|
157
|
+
def revoke(*args)
|
158
|
+
params = auth_revoke_impl(*args)
|
159
|
+
RevokeSecurityGroupIngressResponse.new(make_request("RevokeSecurityGroupIngress", params))
|
160
|
+
end
|
161
|
+
|
162
|
+
def modify_image_attribute(imageId, attribute, operationType, attributeValueHash)
|
163
|
+
params = {
|
164
|
+
"ImageId" => imageId,
|
165
|
+
"Attribute" => attribute,
|
166
|
+
"OperationType" => operationType
|
167
|
+
}
|
168
|
+
if attribute == "launchPermission"
|
169
|
+
params.merge!(pathlist("UserGroup", attributeValueHash[:userGroups])) if attributeValueHash.has_key? :userGroups
|
170
|
+
params.merge!(pathlist("UserId", attributeValueHash[:userIds])) if attributeValueHash.has_key? :userIds
|
171
|
+
end
|
172
|
+
ModifyImageAttributeResponse.new(make_request("ModifyImageAttribute", params))
|
173
|
+
end
|
174
|
+
|
175
|
+
def reset_image_attribute(imageId, attribute)
|
176
|
+
params = { "ImageId" => imageId, "Attribute" => attribute }
|
177
|
+
ResetImageAttributeResponse.new(make_request("ResetImageAttribute", params))
|
178
|
+
end
|
179
|
+
|
180
|
+
def describe_image_attribute(imageId, attribute)
|
181
|
+
params = { "ImageId" => imageId, "Attribute" => attribute }
|
182
|
+
DescribeImageAttributeResponse.new(make_request("DescribeImageAttribute", params))
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def auth_revoke_impl(groupName, kwargs={})
|
188
|
+
in_params = { :ipProtocol=>nil, :fromPort=>nil, :toPort=>nil, :cidrIp=>nil, :sourceSecurityGroupName=>nil,
|
189
|
+
:sourceSecurityGroupOwnerId=>nil}
|
190
|
+
in_params.merge! kwargs
|
191
|
+
|
192
|
+
{ "GroupName" => in_params[:groupName] ,
|
193
|
+
"IpProtocol" => in_params[:ipProtocol],
|
194
|
+
"FromPort" => in_params[:fromPort].to_s,
|
195
|
+
"ToPort" => in_params[:toPort].to_s,
|
196
|
+
"CidrIp" => in_params[:cidrIp],
|
197
|
+
"SourceSecurityGroupName" => in_params[:sourceSecurityGroupName],
|
198
|
+
"SourceSecurityGroupOwnerId" => in_params[:sourceSecurityGroupOwnerId],
|
199
|
+
}.reject { |key, value| value.nil? or value.empty?}
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
private
|
204
|
+
|
205
|
+
|
206
|
+
def make_request(action, params, data='')
|
207
|
+
|
208
|
+
@http.start do
|
209
|
+
|
210
|
+
params.merge!( {"Action"=>action, "SignatureVersion"=>"1", "AWSAccessKeyId"=>@aws_access_key_id,
|
211
|
+
"Version"=>API_VERSION, "Timestamp"=>Time.now.getutc.iso8601,} )
|
212
|
+
p params if @verbose
|
213
|
+
|
214
|
+
sigpath = "?" + params.sort_by { |param| param[0].downcase }.collect { |param| param.join("=") }.join("&")
|
215
|
+
|
216
|
+
sig = get_aws_auth_param(sigpath, @aws_secret_access_key)
|
217
|
+
|
218
|
+
path = "?" + params.sort.collect do |param|
|
219
|
+
CGI::escape(param[0]) + "=" + CGI::escape(param[1])
|
220
|
+
end.join("&") + "&Signature=" + sig
|
221
|
+
|
222
|
+
puts path if @verbose
|
223
|
+
|
224
|
+
req = Net::HTTP::Get.new("/#{path}")
|
225
|
+
|
226
|
+
# ruby will automatically add a random content-type on some verbs, so
|
227
|
+
# here we add a dummy one to 'supress' it. change this logic if having
|
228
|
+
# an empty content-type header becomes semantically meaningful for any
|
229
|
+
# other verb.
|
230
|
+
req['Content-Type'] ||= ''
|
231
|
+
req['User-Agent'] = 'ec2-ruby-query 1.2-#{RELEASE_VERSION}'
|
232
|
+
|
233
|
+
data = nil unless req.request_body_permitted?
|
234
|
+
@http.request(req, data)
|
235
|
+
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# set the Authorization header using AWS signed header authentication
|
240
|
+
def get_aws_auth_param(path, aws_secret_access_key)
|
241
|
+
canonical_string = EC2.canonical_string(path)
|
242
|
+
encoded_canonical = EC2.encode(aws_secret_access_key, canonical_string)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
class Response
|
247
|
+
attr_reader :http_response
|
248
|
+
attr_reader :http_xml
|
249
|
+
attr_reader :structure
|
250
|
+
|
251
|
+
ERROR_XPATH = "Response/Errors/Error"
|
252
|
+
|
253
|
+
def initialize(http_response)
|
254
|
+
@http_response = http_response
|
255
|
+
@http_xml = http_response.body
|
256
|
+
@is_error = false
|
257
|
+
if http_response.is_a? Net::HTTPSuccess
|
258
|
+
@structure = parse
|
259
|
+
else
|
260
|
+
@is_error = true
|
261
|
+
@structure = parse_error
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def is_error?
|
266
|
+
@is_error
|
267
|
+
end
|
268
|
+
|
269
|
+
def parse_error
|
270
|
+
doc = Document.new(@http_xml)
|
271
|
+
element = XPath.first(doc, ERROR_XPATH)
|
272
|
+
|
273
|
+
errorCode = XPath.first(element, "Code").text
|
274
|
+
errorMessage = XPath.first(element, "Message").text
|
275
|
+
|
276
|
+
[["#{errorCode}: #{errorMessage}"]]
|
277
|
+
end
|
278
|
+
|
279
|
+
def parse
|
280
|
+
# Placeholder -- this method should be overridden in child classes.
|
281
|
+
nil
|
282
|
+
end
|
283
|
+
|
284
|
+
def to_s
|
285
|
+
@structure.collect do |line|
|
286
|
+
line.join("\t")
|
287
|
+
end.join("\n")
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class DescribeImagesResponse < Response
|
292
|
+
ELEMENT_XPATH = "DescribeImagesResponse/imagesSet/item"
|
293
|
+
def parse
|
294
|
+
doc = Document.new(@http_xml)
|
295
|
+
lines = []
|
296
|
+
|
297
|
+
doc.elements.each(ELEMENT_XPATH) do |element|
|
298
|
+
imageId = XPath.first(element, "imageId").text
|
299
|
+
imageLocation = XPath.first(element, "imageLocation").text
|
300
|
+
imageOwnerId = XPath.first(element, "imageOwnerId").text
|
301
|
+
imageState = XPath.first(element, "imageState").text
|
302
|
+
isPublic = XPath.first(element, "isPublic").text
|
303
|
+
lines << ["IMAGE", imageId, imageLocation, imageOwnerId, imageState, isPublic]
|
304
|
+
end
|
305
|
+
lines
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
class RegisterImageResponse < Response
|
310
|
+
ELEMENT_XPATH = "RegisterImageResponse/imageId"
|
311
|
+
def parse
|
312
|
+
doc = Document.new(@http_xml)
|
313
|
+
lines = [["IMAGE", XPath.first(doc, ELEMENT_XPATH).text]]
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class DeregisterImageResponse < Response
|
318
|
+
def parse
|
319
|
+
# If we don't get an error, the deregistration succeeded.
|
320
|
+
[["Image deregistered."]]
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
class CreateKeyPairResponse < Response
|
325
|
+
ELEMENT_XPATH = "CreateKeyPairResponse"
|
326
|
+
def parse
|
327
|
+
doc = Document.new(@http_xml)
|
328
|
+
element = XPath.first(doc, ELEMENT_XPATH)
|
329
|
+
|
330
|
+
keyName = XPath.first(element, "keyName").text
|
331
|
+
keyFingerprint = XPath.first(element, "keyFingerprint").text
|
332
|
+
keyMaterial = XPath.first(element, "keyMaterial").text
|
333
|
+
|
334
|
+
line = [["KEYPAIR", keyName, keyFingerprint], [keyMaterial]]
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
class DescribeKeyPairsResponse < Response
|
339
|
+
ELEMENT_XPATH = "DescribeKeyPairsResponse/keySet/item"
|
340
|
+
def parse
|
341
|
+
doc = Document.new(@http_xml)
|
342
|
+
lines = []
|
343
|
+
|
344
|
+
doc.elements.each(ELEMENT_XPATH) do |element|
|
345
|
+
keyName = XPath.first(element, "keyName").text
|
346
|
+
keyFingerprint = XPath.first(element, "keyFingerprint").text
|
347
|
+
lines << ["KEYPAIR", keyName, keyFingerprint]
|
348
|
+
end
|
349
|
+
lines
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
class DeleteKeyPairResponse < Response
|
354
|
+
def parse
|
355
|
+
# If we don't get an error, the deletion succeeded.
|
356
|
+
[["Keypair deleted."]]
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
class RunInstancesResponse < Response
|
361
|
+
ELEMENT_XPATH = "RunInstancesResponse"
|
362
|
+
def parse
|
363
|
+
doc = Document.new(@http_xml)
|
364
|
+
lines = []
|
365
|
+
|
366
|
+
rootelement = XPath.first(doc, ELEMENT_XPATH)
|
367
|
+
|
368
|
+
reservationId = XPath.first(rootelement, "reservationId").text
|
369
|
+
ownerId = XPath.first(rootelement, "ownerId").text
|
370
|
+
groups = nil
|
371
|
+
rootelement.elements.each("groupSet/item/groupId") do |element|
|
372
|
+
if not groups
|
373
|
+
groups = element.text
|
374
|
+
else
|
375
|
+
groups += "," + element.text
|
376
|
+
end
|
377
|
+
end
|
378
|
+
lines << ["RESERVATION", reservationId, ownerId, groups]
|
379
|
+
|
380
|
+
# rootelement = XPath.first(doc, ELEMENT_XPATH)
|
381
|
+
rootelement.elements.each("instancesSet/item") do |element|
|
382
|
+
instanceId = XPath.first(element, "instanceId").text
|
383
|
+
imageId = XPath.first(element, "imageId").text
|
384
|
+
instanceState = XPath.first(element, "instanceState/name").text
|
385
|
+
# Only for debug mode, which we don't support yet:
|
386
|
+
instanceStateCode = XPath.first(element, "instanceState/code").text
|
387
|
+
dnsName = XPath.first(element, "dnsName").text
|
388
|
+
# We don't return this, but still:
|
389
|
+
reason = XPath.first(element, "reason").text
|
390
|
+
lines << ["INSTANCE", instanceId, imageId, dnsName, instanceState]
|
391
|
+
end
|
392
|
+
lines
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
class DescribeInstancesResponse < Response
|
397
|
+
ELEMENT_XPATH = "DescribeInstancesResponse/reservationSet/item"
|
398
|
+
def parse
|
399
|
+
doc = Document.new(@http_xml)
|
400
|
+
lines = []
|
401
|
+
|
402
|
+
doc.elements.each(ELEMENT_XPATH) do |rootelement|
|
403
|
+
reservationId = XPath.first(rootelement, "reservationId").text
|
404
|
+
ownerId = XPath.first(rootelement, "ownerId").text
|
405
|
+
groups = nil
|
406
|
+
rootelement.elements.each("groupSet/item/groupId") do |element|
|
407
|
+
if not groups
|
408
|
+
groups = element.text
|
409
|
+
else
|
410
|
+
groups += "," + element.text
|
411
|
+
end
|
412
|
+
end
|
413
|
+
lines << ["RESERVATION", reservationId, ownerId, groups]
|
414
|
+
|
415
|
+
rootelement.elements.each("instancesSet/item") do |element|
|
416
|
+
instanceId = XPath.first(element, "instanceId").text
|
417
|
+
imageId = XPath.first(element, "imageId").text
|
418
|
+
instanceState = XPath.first(element, "instanceState/name").text
|
419
|
+
# Only for debug mode, which we don't support yet:
|
420
|
+
instanceStateCode = XPath.first(element, "instanceState/code").text
|
421
|
+
dnsName = XPath.first(element, "dnsName").text
|
422
|
+
# We don't return this, but still:
|
423
|
+
reason = XPath.first(element, "reason").text
|
424
|
+
lines << ["INSTANCE", instanceId, imageId, dnsName, instanceState]
|
425
|
+
end
|
426
|
+
end
|
427
|
+
lines
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
class TerminateInstancesResponse < Response
|
432
|
+
ELEMENT_XPATH = "TerminateInstancesResponse/instancesSet/item"
|
433
|
+
def parse
|
434
|
+
doc = Document.new(@http_xml)
|
435
|
+
lines = []
|
436
|
+
|
437
|
+
doc.elements.each(ELEMENT_XPATH) do |element|
|
438
|
+
instanceId = XPath.first(element, "instanceId").text
|
439
|
+
shutdownState = XPath.first(element, "shutdownState/name").text
|
440
|
+
# Only for debug mode, which we don't support yet:
|
441
|
+
shutdownStateCode = XPath.first(element, "shutdownState/code").text
|
442
|
+
previousState = XPath.first(element, "previousState/name").text
|
443
|
+
# Only for debug mode, which we don't support yet:
|
444
|
+
previousStateCode = XPath.first(element, "previousState/code").text
|
445
|
+
lines << ["INSTANCE", instanceId, previousState, shutdownState]
|
446
|
+
end
|
447
|
+
lines
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
class CreateSecurityGroupResponse < Response
|
452
|
+
def parse
|
453
|
+
# If we don't get an error, the creation succeeded.
|
454
|
+
[["Security Group created."]]
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
class DescribeSecurityGroupsResponse < Response
|
459
|
+
ELEMENT_XPATH = "DescribeSecurityGroupsResponse/securityGroupInfo/item"
|
460
|
+
def parse
|
461
|
+
doc = Document.new(@http_xml)
|
462
|
+
lines = []
|
463
|
+
|
464
|
+
doc.elements.each(ELEMENT_XPATH) do |rootelement|
|
465
|
+
groupName = XPath.first(rootelement, "groupName").text
|
466
|
+
ownerId = XPath.first(rootelement, "ownerId").text
|
467
|
+
groupDescription = XPath.first(rootelement, "groupDescription").text
|
468
|
+
lines << ["GROUP", ownerId, groupName, groupDescription]
|
469
|
+
rootelement.elements.each("ipPermissions/item") do |element|
|
470
|
+
ipProtocol = XPath.first(element, "ipProtocol").text
|
471
|
+
fromPort = XPath.first(element, "fromPort").text
|
472
|
+
toPort = XPath.first(element, "toPort").text
|
473
|
+
permArr = [
|
474
|
+
"PERMISSION",
|
475
|
+
ownerId,
|
476
|
+
groupName,
|
477
|
+
"ALLOWS",
|
478
|
+
ipProtocol,
|
479
|
+
fromPort,
|
480
|
+
toPort,
|
481
|
+
"FROM"
|
482
|
+
]
|
483
|
+
element.elements.each("groups/item") do |subelement|
|
484
|
+
userId = XPath.first(subelement, "userId").text
|
485
|
+
targetGroupName = XPath.first(subelement, "groupName").text
|
486
|
+
lines << permArr + ["USER", userId, "GRPNAME", targetGroupName]
|
487
|
+
end
|
488
|
+
element.elements.each("ipRanges/item") do |subelement|
|
489
|
+
cidrIp = XPath.first(subelement, "cidrIp").text
|
490
|
+
lines << permArr + ["CIDR", cidrIp]
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
lines
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
class DeleteSecurityGroupResponse < Response
|
499
|
+
def parse
|
500
|
+
# If we don't get an error, the deletion succeeded.
|
501
|
+
[["Security Group deleted."]]
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
class AuthorizeSecurityGroupIngressResponse < Response
|
506
|
+
def parse
|
507
|
+
# If we don't get an error, the authorization succeeded.
|
508
|
+
[["Ingress authorized."]]
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
class RevokeSecurityGroupIngressResponse < Response
|
513
|
+
def parse
|
514
|
+
# If we don't get an error, the revocation succeeded.
|
515
|
+
[["Ingress revoked."]]
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
class ModifyImageAttributeResponse < Response
|
520
|
+
def parse
|
521
|
+
# If we don't get an error, modification succeeded.
|
522
|
+
[["Image attribute modified."]]
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
class ResetImageAttributeResponse < Response
|
527
|
+
def parse
|
528
|
+
# If we don't get an error, reset succeeded.
|
529
|
+
[["Image attribute reset."]]
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
class DescribeImageAttributeResponse < Response
|
534
|
+
ELEMENT_XPATH = "DescribeImageAttributeResponse"
|
535
|
+
def parse
|
536
|
+
doc = Document.new(@http_xml)
|
537
|
+
lines = []
|
538
|
+
|
539
|
+
rootelement = XPath.first(doc, ELEMENT_XPATH)
|
540
|
+
imageId = XPath.first(rootelement, "imageId").text
|
541
|
+
|
542
|
+
# Handle launchPermission attributes:
|
543
|
+
rootelement.elements.each("launchPermission/item/*") do |element|
|
544
|
+
lines << [
|
545
|
+
"launchPermission",
|
546
|
+
imageId,
|
547
|
+
element.name,
|
548
|
+
element.text
|
549
|
+
]
|
550
|
+
end
|
551
|
+
lines
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
end
|