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 ADDED
@@ -0,0 +1,6 @@
1
+ =CHANGELOG.txt : Amazon Elastic Compute Cloud (EC2) Ruby Gem
2
+
3
+
4
+ ==2006.12.13
5
+ * Original sample library released by Amazon Web Services, LLC
6
+ * Library packaged as a gem and RubyForge project 'amazon-ec2' setup by Glenn Rempe
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
@@ -0,0 +1,12 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ CHANGELOG.txt
5
+ Rakefile
6
+ setup.rb
7
+ lib/EC2.rb
8
+ lib/EC2/version.rb
9
+ test/test_helper.rb
10
+ test/EC2_test.rb
11
+
12
+ examples/ec2-example.rb
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