amazon-ec2 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +166 -95
- data/License.txt +67 -20
- data/Manifest.txt +17 -6
- data/README.txt +112 -2
- data/Rakefile +15 -5
- data/bin/ec2-gem-example.rb +61 -0
- data/bin/ec2sh +73 -0
- data/bin/setup.rb +19 -0
- data/lib/EC2.rb +142 -61
- data/lib/EC2/console.rb +44 -0
- data/lib/EC2/exceptions.rb +136 -0
- data/lib/EC2/image_attributes.rb +137 -29
- data/lib/EC2/images.rb +120 -73
- data/lib/EC2/instances.rb +168 -98
- data/lib/EC2/keypairs.rb +79 -23
- data/lib/EC2/responses.rb +142 -321
- data/lib/EC2/security_groups.rb +209 -117
- data/lib/EC2/version.rb +11 -2
- data/test/test_EC2.rb +44 -13
- data/test/test_EC2_console.rb +54 -0
- data/test/test_EC2_image_attributes.rb +188 -0
- data/test/test_EC2_images.rb +191 -0
- data/test/test_EC2_instances.rb +303 -0
- data/test/test_EC2_keypairs.rb +123 -0
- data/test/test_EC2_responses.rb +102 -0
- data/test/test_EC2_security_groups.rb +205 -0
- data/test/test_EC2_version.rb +44 -0
- data/test/test_helper.rb +16 -8
- data/website/index.html +378 -86
- data/website/index.txt +339 -88
- data/website/stylesheets/screen.css +8 -8
- metadata +89 -16
- data/examples/ec2-example.rb +0 -48
- data/test/test_responses.rb +0 -17
data/lib/EC2/instances.rb
CHANGED
@@ -1,121 +1,191 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
1
|
+
#--
|
2
|
+
# Amazon Web Services EC2 Query API Ruby library
|
3
|
+
#
|
4
|
+
# Ruby Gem Name:: amazon-ec2
|
5
|
+
# Author:: Glenn Rempe (mailto:glenn@elasticworkbench.com)
|
6
|
+
# Copyright:: Copyright (c) 2007 Glenn Rempe
|
7
|
+
# License:: Distributes under the same terms as Ruby
|
8
|
+
# Home:: http://amazon-ec2.rubyforge.org
|
9
|
+
#++
|
8
10
|
|
9
11
|
module EC2
|
10
12
|
|
11
|
-
class
|
13
|
+
class Base
|
12
14
|
|
13
|
-
#
|
15
|
+
#Amazon Developer Guide Docs:
|
14
16
|
#
|
15
|
-
#
|
16
|
-
# The Query version of RunInstances only allows instances of a
|
17
|
-
# single AMI to be launched in one call. This is different
|
18
|
-
# from the SOAP API call of the same name but similar to the
|
19
|
-
# ec2-run-instances command line tool.
|
17
|
+
# The RunInstances operation launches a specified number of instances.
|
20
18
|
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
# request. All instances that are
|
32
|
-
# is provided will have access to the associated public key at boot
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
19
|
+
# Note : The Query version of RunInstances only allows instances of a single AMI to be launched in
|
20
|
+
# one call. This is different from the SOAP API call of the same name but similar to the
|
21
|
+
# ec2-run-instances command line tool.
|
22
|
+
#
|
23
|
+
# A call to RunInstances is guaranteed to start no fewer than the requested minimum. If there is
|
24
|
+
# insufficient capacity available then no instances will be started. Amazon EC2 will make a best effort
|
25
|
+
# attempt to satisfy the requested maximum values.
|
26
|
+
#
|
27
|
+
# Every instance is launched in a security group. This may be specified as part of the launch request. If a
|
28
|
+
# security group is not indicated then instances are started in a the default security group.
|
29
|
+
# An optional keypair ID may be provided for each image in the launch request. All instances that are
|
30
|
+
# created from images for which this is provided will have access to the associated public key at boot time
|
31
|
+
# (detailed below). This key may be used to provide secure access to an instance of an image on a
|
32
|
+
# per-instance basis. Amazon EC2 public images make use of this functionality to provide secure
|
33
|
+
# passwordless access to instances (and launching those images without a keypair ID will leave them
|
34
|
+
# inaccessible).
|
35
|
+
#
|
36
|
+
# The public key material is made available to the instance at boot time by placing it in a file named
|
37
|
+
# openssh_id.pub on a logical device that is exposed to the instance as /dev/sda2 (the ephemeral
|
38
|
+
# store). The format of this file is suitable for use as an entry within ~/.ssh/authorized_keys (the
|
39
|
+
# OpenSSH format). This can be done at boot time (as part of rclocal, for example) allowing for secure
|
40
|
+
# password-less access. As the need arises, other formats will also be considered.
|
41
|
+
#
|
42
|
+
#Required Arguments:
|
43
|
+
#
|
44
|
+
# :image_id => String (Default : "")
|
45
|
+
# :min_count => Integer (default : 1 )
|
46
|
+
# :max_count => Integer (default : 1 )
|
47
|
+
#
|
48
|
+
#Optional Arguments:
|
49
|
+
#
|
50
|
+
# :key_name => String (default : nil)
|
51
|
+
# :group_id => Array (default : [])
|
52
|
+
# :user_data => String (default : nil)
|
53
|
+
# :addressing_type => String (default : "public")
|
54
|
+
# :base64_encoded => Boolean (default : false)
|
55
|
+
#
|
56
|
+
def run_instances( options = {} )
|
57
|
+
|
58
|
+
options = { :image_id => "",
|
59
|
+
:min_count => 1,
|
60
|
+
:max_count => 1,
|
61
|
+
:key_name => nil,
|
62
|
+
:group_id => [],
|
63
|
+
:user_data => nil,
|
64
|
+
:addressing_type => "public",
|
65
|
+
:base64_encoded => false }.merge(options)
|
66
|
+
|
67
|
+
# Do some validation on the arguments provided
|
68
|
+
raise ArgumentError, ":image_id must be provided" if options[:image_id].nil? || options[:image_id].empty?
|
69
|
+
raise ArgumentError, ":min_count is not valid" unless options[:min_count].to_i > 0
|
70
|
+
raise ArgumentError, ":max_count is not valid" unless options[:max_count].to_i > 0
|
71
|
+
raise ArgumentError, ":addressing_type must be 'direct' or 'public'" unless options[:addressing_type] == "public" || options[:addressing_type] == "direct"
|
72
|
+
raise ArgumentError, ":base64_encoded must be 'true' or 'false'" unless options[:base64_encoded] == true || options[:base64_encoded] == false
|
73
|
+
|
74
|
+
# If :user_data is passed in then URL escape and Base64 encode it
|
56
75
|
# as needed. Need for URL Escape + Base64 encoding is determined
|
57
|
-
# by
|
58
|
-
if
|
59
|
-
if
|
60
|
-
|
76
|
+
# by :base64_encoded param.
|
77
|
+
if options[:user_data]
|
78
|
+
if options[:base64_encoded]
|
79
|
+
user_data = options[:user_data]
|
61
80
|
else
|
62
|
-
|
81
|
+
user_data = Base64.encode64(options[:user_data]).gsub(/\n/,"").strip()
|
63
82
|
end
|
64
83
|
else
|
65
|
-
|
84
|
+
user_data = nil
|
66
85
|
end
|
67
|
-
|
86
|
+
|
68
87
|
params = {
|
69
|
-
"ImageId" =>
|
70
|
-
"MinCount" =>
|
71
|
-
"MaxCount" =>
|
72
|
-
}.merge(pathlist("SecurityGroup",
|
88
|
+
"ImageId" => options[:image_id],
|
89
|
+
"MinCount" => options[:min_count].to_s,
|
90
|
+
"MaxCount" => options[:max_count].to_s,
|
91
|
+
}.merge(pathlist("SecurityGroup", options[:group_id]))
|
92
|
+
|
93
|
+
params["KeyName"] = options[:key_name] unless options[:key_name].nil?
|
94
|
+
params["UserData"] = user_data unless user_data.nil?
|
95
|
+
params["AddressingType"] = options[:addressing_type]
|
96
|
+
|
97
|
+
return response_generator(:action => "RunInstances", :params => params)
|
73
98
|
|
74
|
-
params["KeyName"] = in_params[:keyname] unless in_params[:keyname].nil?
|
75
|
-
params["UserData"] = userData unless userData.nil?
|
76
|
-
|
77
|
-
RunInstancesResponse.new(make_request("RunInstances", params))
|
78
99
|
end
|
79
100
|
|
80
|
-
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
88
|
-
# instance
|
89
|
-
#
|
90
|
-
#
|
91
|
-
# for a small interval subsequent to
|
92
|
-
# is typically of the order of one hour
|
93
|
-
|
94
|
-
|
95
|
-
|
101
|
+
|
102
|
+
#Amazon Developer Guide Docs:
|
103
|
+
#
|
104
|
+
# The DescribeInstances operation returns information about instances owned by the user
|
105
|
+
# making the request.
|
106
|
+
#
|
107
|
+
# An optional list of instance IDs may be provided to request information for those instances only. If no
|
108
|
+
# instance IDs are provided, information of all relevant instances information will be returned. If an
|
109
|
+
# instance is specified that does not exist a fault is returned. If an instance is specified that exists but is not
|
110
|
+
# owned by the user making the request, then that instance will not be included in the returned results.
|
111
|
+
#
|
112
|
+
# Recently terminated instances will be included in the returned results for a small interval subsequent to
|
113
|
+
# their termination. This interval is typically of the order of one hour
|
114
|
+
#
|
115
|
+
#Required Arguments:
|
116
|
+
#
|
117
|
+
# none
|
118
|
+
#
|
119
|
+
#Optional Arguments:
|
120
|
+
#
|
121
|
+
# :instance_id => Array (default : [])
|
122
|
+
#
|
123
|
+
def describe_instances( options = {} )
|
124
|
+
|
125
|
+
options = { :instance_id => [] }.merge(options)
|
126
|
+
|
127
|
+
params = pathlist("InstanceId", options[:instance_id])
|
128
|
+
|
129
|
+
return response_generator(:action => "DescribeInstances", :params => params)
|
130
|
+
|
96
131
|
end
|
97
132
|
|
98
|
-
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
133
|
+
|
134
|
+
#Amazon Developer Guide Docs:
|
135
|
+
#
|
136
|
+
# The RebootInstances operation requests a reboot of one or more instances. This operation is
|
137
|
+
# asynchronous; it only queues a request to reboot the specified instance(s). The operation will succeed
|
138
|
+
# provided the instances are valid and belong to the user. Terminated instances will be ignored.
|
139
|
+
#
|
140
|
+
#Required Arguments:
|
141
|
+
#
|
142
|
+
# :instance_id => Array (default : [])
|
143
|
+
#
|
144
|
+
#Optional Arguments:
|
145
|
+
#
|
146
|
+
# none
|
147
|
+
#
|
148
|
+
def reboot_instances( options = {} )
|
149
|
+
|
150
|
+
# defaults
|
151
|
+
options = { :instance_id => [] }.merge(options)
|
152
|
+
|
153
|
+
raise ArgumentError, "No instance IDs provided" if options[:instance_id].nil? || options[:instance_id].empty?
|
154
|
+
|
155
|
+
params = pathlist("InstanceId", options[:instance_id])
|
156
|
+
|
157
|
+
return response_generator(:action => "RebootInstances", :params => params)
|
158
|
+
|
108
159
|
end
|
109
160
|
|
110
|
-
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
161
|
+
|
162
|
+
#Amazon Developer Guide Docs:
|
163
|
+
#
|
164
|
+
# The TerminateInstances operation shuts down one or more instances. This operation is idempotent
|
165
|
+
# and terminating an instance that is in the process of shutting down (or already terminated) will succeed.
|
166
|
+
# Terminated instances remain visible for a short period of time (approximately one hour) after
|
167
|
+
# termination, after which their instance ID is invalidated.
|
168
|
+
#
|
169
|
+
#Required Arguments:
|
170
|
+
#
|
171
|
+
# :instance_id => Array (default : [])
|
172
|
+
#
|
173
|
+
#Optional Arguments:
|
174
|
+
#
|
175
|
+
# none
|
176
|
+
#
|
177
|
+
def terminate_instances( options = {} )
|
178
|
+
|
179
|
+
options = { :instance_id => [] }.merge(options)
|
180
|
+
|
181
|
+
raise ArgumentError, "No :instance_id provided" if options[:instance_id].nil? || options[:instance_id].empty?
|
182
|
+
|
183
|
+
params = pathlist("InstanceId", options[:instance_id])
|
184
|
+
|
185
|
+
return response_generator(:action => "TerminateInstances", :params => params)
|
186
|
+
|
118
187
|
end
|
188
|
+
|
119
189
|
end
|
120
190
|
|
121
191
|
end
|
data/lib/EC2/keypairs.rb
CHANGED
@@ -1,36 +1,92 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
1
|
+
#--
|
2
|
+
# Amazon Web Services EC2 Query API Ruby library
|
3
|
+
#
|
4
|
+
# Ruby Gem Name:: amazon-ec2
|
5
|
+
# Author:: Glenn Rempe (mailto:glenn@elasticworkbench.com)
|
6
|
+
# Copyright:: Copyright (c) 2007 Glenn Rempe
|
7
|
+
# License:: Distributes under the same terms as Ruby
|
8
|
+
# Home:: http://amazon-ec2.rubyforge.org
|
9
|
+
#++
|
8
10
|
|
9
11
|
module EC2
|
10
12
|
|
11
|
-
class
|
13
|
+
class Base
|
12
14
|
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
|
16
|
+
#Amazon Developer Guide Docs:
|
17
|
+
#
|
18
|
+
# The CreateKeyPair operation creates a new 2048 bit RSA keypair and returns a unique ID that can be
|
19
|
+
# used to reference this keypair when launching new instances.
|
20
|
+
#
|
21
|
+
#Required Arguments:
|
22
|
+
#
|
23
|
+
# :key_name => String (default : "")
|
24
|
+
#
|
25
|
+
#Optional Arguments:
|
26
|
+
#
|
27
|
+
# none
|
28
|
+
#
|
29
|
+
def create_keypair( options = {} )
|
30
|
+
|
31
|
+
# defaults
|
32
|
+
options = { :key_name => "" }.merge(options)
|
33
|
+
|
34
|
+
raise ArgumentError, "No :key_name provided" if options[:key_name].nil? || options[:key_name].empty?
|
35
|
+
|
36
|
+
params = { "KeyName" => options[:key_name] }
|
37
|
+
|
38
|
+
return response_generator(:action => "CreateKeyPair", :params => params)
|
39
|
+
|
19
40
|
end
|
20
41
|
|
21
|
-
|
22
|
-
#
|
23
|
-
#
|
42
|
+
|
43
|
+
#Amazon Developer Guide Docs:
|
44
|
+
#
|
45
|
+
# The DescribeKeyPairs operation returns information about keypairs available for use by the user
|
46
|
+
# making the request. Selected keypairs may be specified or the list may be left empty if information for
|
24
47
|
# all registered keypairs is required.
|
25
|
-
|
26
|
-
|
27
|
-
|
48
|
+
#
|
49
|
+
#Required Arguments:
|
50
|
+
#
|
51
|
+
# :key_name => Array (default : [])
|
52
|
+
#
|
53
|
+
#Optional Arguments:
|
54
|
+
#
|
55
|
+
# none
|
56
|
+
#
|
57
|
+
def describe_keypairs( options = {} )
|
58
|
+
|
59
|
+
options = { :key_name => [] }.merge(options)
|
60
|
+
|
61
|
+
params = pathlist("KeyName", options[:key_name] )
|
62
|
+
|
63
|
+
return response_generator(:action => "DescribeKeyPairs", :params => params)
|
64
|
+
|
28
65
|
end
|
29
66
|
|
67
|
+
|
68
|
+
#Amazon Developer Guide Docs:
|
69
|
+
#
|
30
70
|
# The DeleteKeyPair operation deletes a keypair.
|
31
|
-
|
32
|
-
|
33
|
-
|
71
|
+
#
|
72
|
+
#Required Arguments:
|
73
|
+
#
|
74
|
+
# :key_name => String (default : "")
|
75
|
+
#
|
76
|
+
#Optional Arguments:
|
77
|
+
#
|
78
|
+
# none
|
79
|
+
#
|
80
|
+
def delete_keypair( options = {} )
|
81
|
+
|
82
|
+
options = { :key_name => "" }.merge(options)
|
83
|
+
|
84
|
+
raise ArgumentError, "No :key_name provided" if options[:key_name].nil? || options[:key_name].empty?
|
85
|
+
|
86
|
+
params = { "KeyName" => options[:key_name] }
|
87
|
+
|
88
|
+
return response_generator(:action => "DeleteKeyPair", :params => params)
|
89
|
+
|
34
90
|
end
|
35
91
|
|
36
92
|
end
|
data/lib/EC2/responses.rb
CHANGED
@@ -1,348 +1,169 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
1
|
+
#--
|
2
|
+
# Amazon Web Services EC2 Query API Ruby library
|
3
|
+
#
|
4
|
+
# Ruby Gem Name:: amazon-ec2
|
5
|
+
# Author:: Glenn Rempe (mailto:glenn@elasticworkbench.com)
|
6
|
+
# Copyright:: Copyright (c) 2007 Glenn Rempe
|
7
|
+
# License:: Distributes under the same terms as Ruby
|
8
|
+
# Home:: http://amazon-ec2.rubyforge.org
|
9
|
+
#++
|
8
10
|
|
9
11
|
module EC2
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
# The make_request() and ec2_error? methods, which are shared by all, will raise any
|
14
|
+
# exceptions encountered along the way as it converses with EC2.
|
15
|
+
#
|
16
|
+
# Exception Handling: If for some reason an error occurrs when executing a method
|
17
|
+
# (e.g. its arguments were incorrect, or it simply failed) then an exception will
|
18
|
+
# be thrown. The exceptions are defined in exceptions.rb as individual classes and should
|
19
|
+
# match the exceptions that Amazon has defined for EC2. If the exception raised cannot be
|
20
|
+
# identified then a more generic exception class will be thrown.
|
21
|
+
#
|
22
|
+
# The implication of this is that you need to be prepared to handle any exceptions that
|
23
|
+
# may be raised by this library in YOUR code with a 'rescue' clauses. It is up to you
|
24
|
+
# how gracefully you want to handle these exceptions that are raised.
|
25
|
+
|
26
|
+
# Credits :
|
27
|
+
# I learned the magic of making an OpenStruct object able to respond as a fully Enumerable
|
28
|
+
# object (responds to .each, etc.) thanks to a great blog article on Struct and OpenStruct
|
29
|
+
# at http://errtheblog.com/post/30
|
30
|
+
#
|
31
|
+
# Thanks to Sean Knapp for the XmlSimple response patch which greatly simplified the response
|
32
|
+
# mechanism for the whole library while making it more accurate and much less brittle to boot!
|
33
|
+
#
|
34
|
+
|
35
|
+
require 'xmlsimple'
|
36
|
+
|
37
|
+
class Response < OpenStruct
|
15
38
|
|
16
|
-
|
39
|
+
include Enumerable
|
17
40
|
|
18
|
-
def initialize(http_response)
|
19
|
-
@http_response = http_response
|
20
|
-
@http_xml = http_response.body
|
21
|
-
@is_error = false
|
22
|
-
if http_response.is_a? Net::HTTPSuccess
|
23
|
-
@structure = parse
|
24
|
-
else
|
25
|
-
@is_error = true
|
26
|
-
@structure = parse_error
|
27
|
-
end
|
28
|
-
end
|
29
41
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
element = REXML::XPath.first(doc, ERROR_XPATH)
|
37
|
-
|
38
|
-
errorCode = REXML::XPath.first(element, "Code").text
|
39
|
-
errorMessage = REXML::XPath.first(element, "Message").text
|
42
|
+
def self.parse(options = {})
|
43
|
+
options = {
|
44
|
+
:xml => "",
|
45
|
+
:parse_options => { 'ForceArray' => ['item'], 'SuppressEmpty' => nil }
|
46
|
+
}.merge(options)
|
47
|
+
response = Response.new(XmlSimple.xml_in(options[:xml], options[:parse_options]))
|
40
48
|
|
41
|
-
|
49
|
+
# set the xml attribute of the response object to contain the original XML that was
|
50
|
+
# returned by amazon. This allows anyone to bypass our parsing if desired and just
|
51
|
+
# get right at the raw XML response.
|
52
|
+
response.xml = options[:xml]
|
53
|
+
return response
|
42
54
|
end
|
43
55
|
|
44
|
-
def parse
|
45
|
-
# Placeholder -- this method should be overridden in child classes.
|
46
|
-
nil
|
47
|
-
end
|
48
56
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
57
|
+
# Every member of an OpenStruct object has getters and setters, the latter of which
|
58
|
+
# has a method ending in "=". Find all of these methods, excluding those defined on
|
59
|
+
# parent classes.
|
60
|
+
def members
|
61
|
+
methods(false).sort.grep(/=/).map { |m| m[0...-1] }
|
53
62
|
end
|
54
63
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
doc = REXML::Document.new(@http_xml)
|
62
|
-
lines = []
|
63
|
-
|
64
|
-
doc.elements.each(ELEMENT_XPATH) do |element|
|
65
|
-
imageId = REXML::XPath.first(element, "imageId").text
|
66
|
-
imageLocation = REXML::XPath.first(element, "imageLocation").text
|
67
|
-
imageOwnerId = REXML::XPath.first(element, "imageOwnerId").text
|
68
|
-
imageState = REXML::XPath.first(element, "imageState").text
|
69
|
-
isPublic = REXML::XPath.first(element, "isPublic").text
|
70
|
-
lines << ["IMAGE", imageId, imageLocation, imageOwnerId, imageState, isPublic]
|
64
|
+
|
65
|
+
# Required by the Enumerable module. Iterate over each item in the members array
|
66
|
+
# and pass as a value the block passed to each using yield.
|
67
|
+
def each
|
68
|
+
members.each do |method|
|
69
|
+
yield send(method)
|
71
70
|
end
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
|
77
|
-
class RegisterImageResponse < Response
|
78
|
-
ELEMENT_XPATH = "RegisterImageResponse/imageId"
|
79
|
-
def parse
|
80
|
-
doc = REXML::Document.new(@http_xml)
|
81
|
-
lines = [["IMAGE", REXML::XPath.first(doc, ELEMENT_XPATH).text]]
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
|
86
|
-
class DeregisterImageResponse < Response
|
87
|
-
def parse
|
88
|
-
# If we don't get an error, the deregistration succeeded.
|
89
|
-
[["Image deregistered."]]
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
class CreateKeyPairResponse < Response
|
95
|
-
ELEMENT_XPATH = "CreateKeyPairResponse"
|
96
|
-
def parse
|
97
|
-
doc = REXML::Document.new(@http_xml)
|
98
|
-
element = REXML::XPath.first(doc, ELEMENT_XPATH)
|
99
|
-
|
100
|
-
keyName = REXML::XPath.first(element, "keyName").text
|
101
|
-
keyFingerprint = REXML::XPath.first(element, "keyFingerprint").text
|
102
|
-
keyMaterial = REXML::XPath.first(element, "keyMaterial").text
|
103
|
-
|
104
|
-
line = [["KEYPAIR", keyName, keyFingerprint], [keyMaterial]]
|
71
|
+
self
|
105
72
|
end
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
doc.elements.each(ELEMENT_XPATH) do |element|
|
116
|
-
keyName = REXML::XPath.first(element, "keyName").text
|
117
|
-
keyFingerprint = REXML::XPath.first(element, "keyFingerprint").text
|
118
|
-
lines << ["KEYPAIR", keyName, keyFingerprint]
|
73
|
+
|
74
|
+
|
75
|
+
# Same as the each method, but with both key and value.
|
76
|
+
#
|
77
|
+
#Sample Use:
|
78
|
+
# obj.each_pair { |k,v| puts "key: #{k}, value: #{v}" }
|
79
|
+
def each_pair
|
80
|
+
members.each do |method|
|
81
|
+
yield method, send(method)
|
119
82
|
end
|
120
|
-
|
83
|
+
self
|
121
84
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
# If we don't get an error, the deletion succeeded.
|
128
|
-
[["Keypair deleted."]]
|
85
|
+
|
86
|
+
|
87
|
+
# Alternative method for getting members.
|
88
|
+
def [](member)
|
89
|
+
send(member)
|
129
90
|
end
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
def parse
|
136
|
-
doc = REXML::Document.new(@http_xml)
|
137
|
-
lines = []
|
138
|
-
|
139
|
-
rootelement = REXML::XPath.first(doc, ELEMENT_XPATH)
|
140
|
-
|
141
|
-
reservationId = REXML::XPath.first(rootelement, "reservationId").text
|
142
|
-
ownerId = REXML::XPath.first(rootelement, "ownerId").text
|
143
|
-
groups = nil
|
144
|
-
rootelement.elements.each("groupSet/item/groupId") do |element|
|
145
|
-
if not groups
|
146
|
-
groups = element.text
|
147
|
-
else
|
148
|
-
groups += "," + element.text
|
149
|
-
end
|
150
|
-
end
|
151
|
-
lines << ["RESERVATION", reservationId, ownerId, groups]
|
152
|
-
|
153
|
-
# rootelement = REXML::XPath.first(doc, ELEMENT_XPATH)
|
154
|
-
rootelement.elements.each("instancesSet/item") do |element|
|
155
|
-
instanceId = REXML::XPath.first(element, "instanceId").text
|
156
|
-
imageId = REXML::XPath.first(element, "imageId").text
|
157
|
-
instanceState = REXML::XPath.first(element, "instanceState/name").text
|
158
|
-
# Only for debug mode, which we don't support yet:
|
159
|
-
instanceStateCode = REXML::XPath.first(element, "instanceState/code").text
|
160
|
-
dnsName = REXML::XPath.first(element, "dnsName").text
|
161
|
-
# We don't return this, but still:
|
162
|
-
reason = REXML::XPath.first(element, "reason").text
|
163
|
-
lines << ["INSTANCE", instanceId, imageId, dnsName, instanceState]
|
164
|
-
end
|
165
|
-
lines
|
91
|
+
|
92
|
+
|
93
|
+
# Alternative method for setting members.
|
94
|
+
def []=(member, value)
|
95
|
+
send("#{member}=", value)
|
166
96
|
end
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
else
|
184
|
-
groups += "," + element.text
|
97
|
+
|
98
|
+
|
99
|
+
# Helper for converting to string which support a long and short version
|
100
|
+
# to avoid recursion problems with parents.
|
101
|
+
def to_string(short=false)
|
102
|
+
s = "#<#{self.class}:0x#{(2 ** 32 + object_id).to_s(16).upcase}"
|
103
|
+
if (short)
|
104
|
+
s += " ..."
|
105
|
+
else
|
106
|
+
each_pair { |k,v|
|
107
|
+
if (v == self.parent && v.kind_of?(Response))
|
108
|
+
v = v.to_string(true)
|
109
|
+
elsif (v.kind_of?(String))
|
110
|
+
v = "\"#{v.gsub("\"", "\\\"")}\""
|
111
|
+
elsif (v.kind_of?(NilClass))
|
112
|
+
v = "nil"
|
185
113
|
end
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
rootelement.elements.each("instancesSet/item") do |element|
|
190
|
-
instanceId = REXML::XPath.first(element, "instanceId").text
|
191
|
-
imageId = REXML::XPath.first(element, "imageId").text
|
192
|
-
instanceState = REXML::XPath.first(element, "instanceState/name").text
|
193
|
-
# Only for debug mode, which we don't support yet:
|
194
|
-
instanceStateCode = REXML::XPath.first(element, "instanceState/code").text
|
195
|
-
dnsName = REXML::XPath.first(element, "dnsName").text
|
196
|
-
# We don't return this, but still:
|
197
|
-
reason = REXML::XPath.first(element, "reason").text
|
198
|
-
lines << ["INSTANCE", instanceId, imageId, dnsName, instanceState]
|
199
|
-
end
|
200
|
-
end
|
201
|
-
lines
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
|
206
|
-
class TerminateInstancesResponse < Response
|
207
|
-
ELEMENT_XPATH = "TerminateInstancesResponse/instancesSet/item"
|
208
|
-
def parse
|
209
|
-
doc = REXML::Document.new(@http_xml)
|
210
|
-
lines = []
|
211
|
-
|
212
|
-
doc.elements.each(ELEMENT_XPATH) do |element|
|
213
|
-
instanceId = REXML::XPath.first(element, "instanceId").text
|
214
|
-
shutdownState = REXML::XPath.first(element, "shutdownState/name").text
|
215
|
-
# Only for debug mode, which we don't support yet:
|
216
|
-
shutdownStateCode = REXML::XPath.first(element, "shutdownState/code").text
|
217
|
-
previousState = REXML::XPath.first(element, "previousState/name").text
|
218
|
-
# Only for debug mode, which we don't support yet:
|
219
|
-
previousStateCode = REXML::XPath.first(element, "previousState/code").text
|
220
|
-
lines << ["INSTANCE", instanceId, previousState, shutdownState]
|
114
|
+
s += " #{k}=#{v}"
|
115
|
+
}
|
221
116
|
end
|
222
|
-
|
117
|
+
s += ">"
|
118
|
+
return s
|
223
119
|
end
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
def
|
228
|
-
|
229
|
-
# Let's use the tag they're actually returning since it doesn't match the docs -- Kevin Clark 2/26/07
|
230
|
-
REXML::XPath.match( doc, "//return").first.text == "true" ? true : false
|
120
|
+
|
121
|
+
|
122
|
+
# Override of to string method.
|
123
|
+
def to_s
|
124
|
+
return to_string
|
231
125
|
end
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
126
|
+
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# Initialize the object by passing data to the OpenStruct initialize method
|
131
|
+
# and then converting ourself to guarantee we have top-to-bottom data
|
132
|
+
# representation as a Response object.
|
133
|
+
def initialize(data, parent=nil)
|
134
|
+
super(data)
|
135
|
+
self.parent = parent
|
136
|
+
Response.convert(self, parent)
|
239
137
|
end
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
groupName = REXML::XPath.first(rootelement, "groupName").text
|
251
|
-
ownerId = REXML::XPath.first(rootelement, "ownerId").text
|
252
|
-
groupDescription = REXML::XPath.first(rootelement, "groupDescription").text
|
253
|
-
lines << ["GROUP", ownerId, groupName, groupDescription]
|
254
|
-
rootelement.elements.each("ipPermissions/item") do |element|
|
255
|
-
ipProtocol = REXML::XPath.first(element, "ipProtocol").text
|
256
|
-
fromPort = REXML::XPath.first(element, "fromPort").text
|
257
|
-
toPort = REXML::XPath.first(element, "toPort").text
|
258
|
-
permArr = [
|
259
|
-
"PERMISSION",
|
260
|
-
ownerId,
|
261
|
-
groupName,
|
262
|
-
"ALLOWS",
|
263
|
-
ipProtocol,
|
264
|
-
fromPort,
|
265
|
-
toPort,
|
266
|
-
"FROM"
|
267
|
-
]
|
268
|
-
element.elements.each("groups/item") do |subelement|
|
269
|
-
userId = REXML::XPath.first(subelement, "userId").text
|
270
|
-
targetGroupName = REXML::XPath.first(subelement, "groupName").text
|
271
|
-
lines << permArr + ["USER", userId, "GRPNAME", targetGroupName]
|
272
|
-
end
|
273
|
-
element.elements.each("ipRanges/item") do |subelement|
|
274
|
-
cidrIp = REXML::XPath.first(subelement, "cidrIp").text
|
275
|
-
lines << permArr + ["CIDR", cidrIp]
|
138
|
+
|
139
|
+
|
140
|
+
# The "brains" of our Response class. This method takes an arbitray object and
|
141
|
+
# depending on its class attempts to convert it.
|
142
|
+
def self.convert(obj, parent)
|
143
|
+
if (obj.kind_of?(Response))
|
144
|
+
# Recursively convert the object.
|
145
|
+
obj.each_pair { |k,v|
|
146
|
+
if (v != obj.parent)
|
147
|
+
obj[k] = convert(v, obj)
|
276
148
|
end
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
class AuthorizeSecurityGroupIngressResponse < Response
|
293
|
-
def parse
|
294
|
-
# If we don't get an error, the authorization succeeded.
|
295
|
-
[["Ingress authorized."]]
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
|
300
|
-
class RevokeSecurityGroupIngressResponse < Response
|
301
|
-
def parse
|
302
|
-
# If we don't get an error, the revocation succeeded.
|
303
|
-
[["Ingress revoked."]]
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
|
308
|
-
class ModifyImageAttributeResponse < Response
|
309
|
-
def parse
|
310
|
-
# If we don't get an error, modification succeeded.
|
311
|
-
[["Image attribute modified."]]
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
|
316
|
-
class ResetImageAttributeResponse < Response
|
317
|
-
def parse
|
318
|
-
# If we don't get an error, reset succeeded.
|
319
|
-
[["Image attribute reset."]]
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
|
324
|
-
class DescribeImageAttributeResponse < Response
|
325
|
-
ELEMENT_XPATH = "DescribeImageAttributeResponse"
|
326
|
-
def parse
|
327
|
-
doc = REXML::Document.new(@http_xml)
|
328
|
-
lines = []
|
329
|
-
|
330
|
-
rootelement = REXML::XPath.first(doc, ELEMENT_XPATH)
|
331
|
-
imageId = REXML::XPath.first(rootelement, "imageId").text
|
332
|
-
|
333
|
-
# Handle launchPermission attributes:
|
334
|
-
rootelement.elements.each("launchPermission/item/*") do |element|
|
335
|
-
lines << [
|
336
|
-
"launchPermission",
|
337
|
-
imageId,
|
338
|
-
element.name,
|
339
|
-
element.text
|
340
|
-
]
|
149
|
+
}
|
150
|
+
return obj
|
151
|
+
elsif (obj.kind_of?(Hash))
|
152
|
+
# Hashes make good Responses already thanks to OpenStruct.
|
153
|
+
return Response.new(obj, parent)
|
154
|
+
elsif (obj.kind_of?(Array))
|
155
|
+
# With arrays, make sure each element is appropriately converted.
|
156
|
+
new_arr = []
|
157
|
+
obj.each { |elem|
|
158
|
+
new_arr << convert(elem, parent)
|
159
|
+
}
|
160
|
+
return new_arr
|
161
|
+
else
|
162
|
+
# At this point we're out of ideas, so let's hope it is a string.
|
163
|
+
return obj
|
341
164
|
end
|
342
|
-
lines
|
343
165
|
end
|
344
|
-
end
|
345
|
-
|
346
|
-
# end EC2 Module
|
347
|
-
end
|
348
166
|
|
167
|
+
end # class Response < OpenStruct
|
168
|
+
|
169
|
+
end # module EC2
|