amazon-ec2 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/EC2/instances.rb CHANGED
@@ -1,121 +1,191 @@
1
- # Amazon Web Services EC2 Query API Ruby Library
2
- # This library has been packaged as a Ruby Gem
3
- # by Glenn Rempe ( grempe @nospam@ rubyforge.org ).
4
- #
5
- # Source code and gem hosted on RubyForge
6
- # under the Ruby License as of 12/14/2006:
7
- # http://amazon-ec2.rubyforge.org
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 AWSAuthConnection
13
+ class Base
12
14
 
13
- # The RunInstances operation launches a specified number of instances.
15
+ #Amazon Developer Guide Docs:
14
16
  #
15
- # Note:
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
- # A call to RunInstances is guaranteed to start no fewer than the
22
- # requested minimum. If there is insufficient capacity available
23
- # then no instances will be started. Amazon EC2 will make a best
24
- # effort attempt to satisfy the requested maximum values.
25
- #
26
- # Every instance is launched in a security group. This may be
27
- # specified as part of the launch request. If a security group is
28
- # not indicated then instances are started in a the default security group.
29
- #
30
- # An optional keypair ID may be provided for each image in the launch
31
- # request. All instances that are created from images for which this
32
- # is provided will have access to the associated public key at boot
33
- # time (detailed below). This key may be used to provide secure access
34
- # to an instance of an image on a per-instance basis. Amazon EC2 public
35
- # images make use of this functionality to provide secure passwordless
36
- # access to instances (and launching those images without a keypair ID
37
- # will leave them inaccessible).
38
- #
39
- # The public key material is made available to the instance at boot
40
- # time by placing it in a file named openssh_id.pub on a logical
41
- # device that is exposed to the instance as /dev/sda2 (the ephemeral store).
42
- # The format of this file is suitable for use as an entry within
43
- # ~/.ssh/authorized_keys (the OpenSSH format). This can be done at boot
44
- # time (as part of rclocal, for example) allowing for secure
45
- # password-less access. As the need arises, other formats will
46
- # also be considered.
47
- def run_instances(imageId, kwargs={})
48
-
49
- # setup the default input parameters hash
50
- in_params = { :minCount=>1, :maxCount=>1, :keyname=>nil, :groupIds=>[], :userData=>nil, :base64Encoded=>false }
51
-
52
- # Override or extend the default input params with any passed in kwargs
53
- in_params.merge!(kwargs)
54
-
55
- # If userData is passed in then URL escape and Base64 encode it
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 base64Encoded param.
58
- if in_params[:userData]
59
- if in_params[:base64Encoded]
60
- userData = in_params[:userData]
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
- userData = Base64.encode64(in_params[:userData]).gsub(/\n/,"").strip()
81
+ user_data = Base64.encode64(options[:user_data]).gsub(/\n/,"").strip()
63
82
  end
64
83
  else
65
- userData = nil
84
+ user_data = nil
66
85
  end
67
-
86
+
68
87
  params = {
69
- "ImageId" => imageId,
70
- "MinCount" => in_params[:minCount].to_s,
71
- "MaxCount" => in_params[:maxCount].to_s,
72
- }.merge(pathlist("SecurityGroup", in_params[:groupIds]))
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
- # The DescribeInstances operation returns information about instances owned
81
- # by the user making the request.
82
- #
83
- # An optional list of instance IDs may be provided to request information
84
- # for those instances only. If no instance IDs are provided, information of
85
- # all relevant instances information will be returned. If an instance is
86
- # specified that does not exist a fault is returned. If an instance is specified
87
- # that exists but is not owned by the user making the request, then that
88
- # instance will not be included in the returned results.
89
- #
90
- # Recently terminated instances will be included in the returned results
91
- # for a small interval subsequent to their termination. This interval
92
- # is typically of the order of one hour.
93
- def describe_instances(instanceIds=[])
94
- params = pathlist("InstanceId", instanceIds)
95
- DescribeInstancesResponse.new(make_request("DescribeInstances", params))
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
- # The TerminateInstances operation shuts down one or more instances.
99
- # This operation is idempotent and terminating an instance that is
100
- # in the process of shutting down (or already terminated) will succeed.
101
- #
102
- # Terminated instances remain visible for a short period of time
103
- # (approximately one hour) after termination, after which their
104
- # instance ID is invalidated.
105
- def terminate_instances(instanceIds)
106
- params = pathlist("InstanceId", instanceIds)
107
- TerminateInstancesResponse.new(make_request("TerminateInstances", params))
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
- # The RebootInstances operation requests a reboot of one or more instances.
111
- # This operation is asynchronous; it only queues a request to reboot the specified
112
- # instance(s). The operation will succeed provided the instances are valid and
113
- # belong to the user. Terminated instances will be ignored.
114
- def reboot_instances(*instance_ids)
115
- raise "No instance ids given" if instance_ids.empty?
116
- params = pathlist("InstanceId", instance_ids)
117
- ResetInstancesResponse.new(make_request("RebootInstances", params))
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
- # Amazon Web Services EC2 Query API Ruby Library
2
- # This library has been packaged as a Ruby Gem
3
- # by Glenn Rempe ( grempe @nospam@ rubyforge.org ).
4
- #
5
- # Source code and gem hosted on RubyForge
6
- # under the Ruby License as of 12/14/2006:
7
- # http://amazon-ec2.rubyforge.org
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 AWSAuthConnection
13
+ class Base
12
14
 
13
- # The CreateKeyPair operation creates a new 2048 bit RSA keypair
14
- # and returns a unique ID that can be used to reference this
15
- # keypair when launching new instances.
16
- def create_keypair(keyName)
17
- params = { "KeyName" => keyName }
18
- CreateKeyPairResponse.new(make_request("CreateKeyPair", params))
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
- # The DescribeKeyPairs operation returns information about keypairs
22
- # available for use by the user making the request. Selected keypairs
23
- # may be specified or the list may be left empty if information for
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
- def describe_keypairs(keyNames=[])
26
- params = pathlist("KeyName", keyNames)
27
- DescribeKeyPairsResponse.new(make_request("DescribeKeyPairs", params))
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
- def delete_keypair(keyName)
32
- params = { "KeyName" => keyName }
33
- DeleteKeyPairResponse.new(make_request("DeleteKeyPair", params))
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
- # Amazon Web Services EC2 Query API Ruby Library
2
- # This library has been packaged as a Ruby Gem
3
- # by Glenn Rempe ( grempe @nospam@ rubyforge.org ).
4
- #
5
- # Source code and gem hosted on RubyForge
6
- # under the Ruby License as of 12/14/2006:
7
- # http://amazon-ec2.rubyforge.org
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 Response
12
- attr_reader :http_response
13
- attr_reader :http_xml
14
- attr_reader :structure
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
- ERROR_XPATH = "Response/Errors/Error"
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 is_error?
31
- @is_error
32
- end
33
-
34
- def parse_error
35
- doc = REXML::Document.new(@http_xml)
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
- [["#{errorCode}: #{errorMessage}"]]
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
- def to_s
50
- @structure.collect do |line|
51
- line.join("\t")
52
- end.join("\n")
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
- end
56
-
57
-
58
- class DescribeImagesResponse < Response
59
- ELEMENT_XPATH = "DescribeImagesResponse/imagesSet/item"
60
- def parse
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
- lines
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
- end
107
-
108
-
109
- class DescribeKeyPairsResponse < Response
110
- ELEMENT_XPATH = "DescribeKeyPairsResponse/keySet/item"
111
- def parse
112
- doc = REXML::Document.new(@http_xml)
113
- lines = []
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
- lines
83
+ self
121
84
  end
122
- end
123
-
124
-
125
- class DeleteKeyPairResponse < Response
126
- def parse
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
- end
131
-
132
-
133
- class RunInstancesResponse < Response
134
- ELEMENT_XPATH = "RunInstancesResponse"
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
- end
168
-
169
-
170
- class DescribeInstancesResponse < Response
171
- ELEMENT_XPATH = "DescribeInstancesResponse/reservationSet/item"
172
- def parse
173
- doc = REXML::Document.new(@http_xml)
174
- lines = []
175
-
176
- doc.elements.each(ELEMENT_XPATH) do |rootelement|
177
- reservationId = REXML::XPath.first(rootelement, "reservationId").text
178
- ownerId = REXML::XPath.first(rootelement, "ownerId").text
179
- groups = nil
180
- rootelement.elements.each("groupSet/item/groupId") do |element|
181
- if not groups
182
- groups = element.text
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
- end
187
- lines << ["RESERVATION", reservationId, ownerId, groups]
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
- lines
117
+ s += ">"
118
+ return s
223
119
  end
224
- end
225
-
226
- class ResetInstancesResponse < Response
227
- def parse
228
- doc = REXML::Document.new(@http_xml)
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
- end
233
-
234
-
235
- class CreateSecurityGroupResponse < Response
236
- def parse
237
- # If we don't get an error, the creation succeeded.
238
- [["Security Group created."]]
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
- end
241
-
242
-
243
- class DescribeSecurityGroupsResponse < Response
244
- ELEMENT_XPATH = "DescribeSecurityGroupsResponse/securityGroupInfo/item"
245
- def parse
246
- doc = REXML::Document.new(@http_xml)
247
- lines = []
248
-
249
- doc.elements.each(ELEMENT_XPATH) do |rootelement|
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
- end
278
- end
279
- lines
280
- end
281
- end
282
-
283
-
284
- class DeleteSecurityGroupResponse < Response
285
- def parse
286
- # If we don't get an error, the deletion succeeded.
287
- [["Security Group deleted."]]
288
- end
289
- end
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