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/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