amazon-ec2 0.0.2 → 0.0.3

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.
@@ -0,0 +1,47 @@
1
+ # Amazon Web Services EC2 Query API Ruby Library
2
+ # This library has been packaged as a Ruby Gem
3
+ # by Glenn Rempe ( glenn @nospam@ elasticworkbench.com ).
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
8
+
9
+ module EC2
10
+
11
+ class AWSAuthConnection
12
+
13
+ # The ModifyImageAttribute operation modifies an attribute of an AMI.
14
+ #
15
+ # Currently the only attribute supported is launchPermission. By
16
+ # modifying this attribute it is possible to make an AMI public or
17
+ # to grant specific users launch permissions for the AMI. To make the
18
+ # AMI public add the group=all attribute item. To grant launch permissions
19
+ # for a specific user add a userId=<userid> attribute item.
20
+ def modify_image_attribute(imageId, attribute, operationType, attributeValueHash)
21
+ params = {
22
+ "ImageId" => imageId,
23
+ "Attribute" => attribute,
24
+ "OperationType" => operationType
25
+ }
26
+ if attribute == "launchPermission"
27
+ params.merge!(pathlist("UserGroup", attributeValueHash[:userGroups])) if attributeValueHash.has_key? :userGroups
28
+ params.merge!(pathlist("UserId", attributeValueHash[:userIds])) if attributeValueHash.has_key? :userIds
29
+ end
30
+ ModifyImageAttributeResponse.new(make_request("ModifyImageAttribute", params))
31
+ end
32
+
33
+ # The DescribeImageAttribute operation returns information about an attribute of an AMI.
34
+ def describe_image_attribute(imageId, attribute)
35
+ params = { "ImageId" => imageId, "Attribute" => attribute }
36
+ DescribeImageAttributeResponse.new(make_request("DescribeImageAttribute", params))
37
+ end
38
+
39
+ # The ResetImageAttribute operation resets an attribute of an AMI to its default value.
40
+ def reset_image_attribute(imageId, attribute)
41
+ params = { "ImageId" => imageId, "Attribute" => attribute }
42
+ ResetImageAttributeResponse.new(make_request("ResetImageAttribute", params))
43
+ end
44
+
45
+ end
46
+
47
+ end
data/lib/EC2/images.rb ADDED
@@ -0,0 +1,87 @@
1
+ # Amazon Web Services EC2 Query API Ruby Library
2
+ # This library has been packaged as a Ruby Gem
3
+ # by Glenn Rempe ( glenn @nospam@ elasticworkbench.com ).
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
8
+
9
+ module EC2
10
+
11
+ class AWSAuthConnection
12
+
13
+ # The RegisterImage operation registers an AMI with Amazon EC2.
14
+ # Images must be registered before they can be launched. Each
15
+ # AMI is associated with an unique ID which is provided by the
16
+ # EC2 service via the Registerimage operation. As part of the
17
+ # registration process, Amazon EC2 will retrieve the specified
18
+ # image manifest from Amazon S3 and verify that the image is
19
+ # owned by the user requesting image registration. The image
20
+ # manifest is retrieved once and stored within the Amazon EC2
21
+ # network. Any modifications to an image in Amazon S3 invalidate
22
+ # this registration. If you do have to make changes and upload
23
+ # a new image deregister the previous image and register the new image.
24
+ def register_image(imageLocation)
25
+ params = { "ImageLocation" => imageLocation }
26
+ RegisterImageResponse.new(make_request("RegisterImage", params))
27
+ end
28
+
29
+ # The DescribeImages operation returns information about AMIs available
30
+ # for use by the user. This includes both public AMIs (those available
31
+ # for any user to launch) and private AMIs (those owned by the user
32
+ # making the request and those owned by other users that the user
33
+ # making the request has explicit launch permissions for).
34
+ #
35
+ # The list of AMIs returned can be modified via optional lists of AMI IDs,
36
+ # owners or users with launch permissions. If all three optional lists
37
+ # are empty all AMIs the user has launch permissions for are returned.
38
+ # Launch permissions fall into three categories:
39
+ #
40
+ # Launch Permission Description
41
+ # * public The all group has launch permissions for the AMI. All users have
42
+ # launch permissions for these AMIs.
43
+ # * explicit The owner of the AMIs has granted a specific user launch permissions for the AMI.
44
+ # * implicit A user has implicit launch permissions for all AMIs he or she owns.
45
+ #
46
+ # If one or more of the lists are specified the result set is the intersection
47
+ # of AMIs matching the criteria of the individual lists.
48
+ #
49
+ # Providing the list of AMI IDs requests information for those AMIs only.
50
+ # If no AMI IDs are provided, information of all relevant AMIs will be
51
+ # returned. If an AMI is specified that does not exist a fault is returned.
52
+ # If an AMI is specified that exists but the user making the request does
53
+ # not have launch permissions for, then that AMI will not be included in
54
+ # the returned results.
55
+ #
56
+ # Providing the list of owners requests information for AMIs owned
57
+ # by the specified owners only. Only AMIs the user has launch permissions
58
+ # for are returned. The items of the list may be account ids for AMIs
59
+ # owned by users with those account ids, amazon for AMIs owned by Amazon
60
+ # or self for AMIs owned by the user making the request.
61
+ #
62
+ # The executable list may be provided to request information for AMIs
63
+ # that only the specified users have launch permissions for. The items of
64
+ # the list may be account ids for AMIs owned by the user making the request
65
+ # that the users with the specified account ids have explicit launch permissions
66
+ # for, self for AMIs the user making the request has explicit launch permissions
67
+ # for or all for public AMIs.
68
+ #
69
+ # Deregistered images will be included in the returned results for an
70
+ # unspecified interval subsequent to deregistration.
71
+ def describe_images(imageIds=[], owners=[], executableBy=[])
72
+ params = pathlist("ImageId", imageIds)
73
+ params.merge!(pathlist("Owner", owners))
74
+ params.merge!(pathlist("ExecutableBy", executableBy))
75
+ DescribeImagesResponse.new(make_request("DescribeImages", params))
76
+ end
77
+
78
+ # The DeregisterImage operation deregisters an AMI. Once deregistered,
79
+ # instances of the AMI may no longer be launched.
80
+ def deregister_image(imageId)
81
+ params = { "ImageId" => imageId }
82
+ DeregisterImageResponse.new(make_request("DeregisterImage", params))
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -0,0 +1,95 @@
1
+ # Amazon Web Services EC2 Query API Ruby Library
2
+ # This library has been packaged as a Ruby Gem
3
+ # by Glenn Rempe ( glenn @nospam@ elasticworkbench.com ).
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
8
+
9
+ module EC2
10
+
11
+ class AWSAuthConnection
12
+
13
+ # The RunInstances operation launches a specified number of instances.
14
+ #
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.
20
+ #
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
+ in_params = { :minCount=>1, :maxCount=>1, :keyname=>nil, :groupIds=>[], :userData=>nil, :base64Encoded=>false }
49
+ in_params.merge!(kwargs)
50
+ userData = Base64.encode64(userData) if userData and not base64Encoded
51
+
52
+ params = {
53
+ "ImageId" => imageId,
54
+ "MinCount" => in_params[:minCount].to_s,
55
+ "MaxCount" => in_params[:maxCount].to_s,
56
+ }.merge(pathlist("SecurityGroup", in_params[:groupIds]))
57
+
58
+ params["KeyName"] = in_params[:keyname] unless in_params[:keyname].nil?
59
+
60
+ RunInstancesResponse.new(make_request("RunInstances", params))
61
+ end
62
+
63
+ # The DescribeInstances operation returns information about instances owned
64
+ # by the user making the request.
65
+ #
66
+ # An optional list of instance IDs may be provided to request information
67
+ # for those instances only. If no instance IDs are provided, information of
68
+ # all relevant instances information will be returned. If an instance is
69
+ # specified that does not exist a fault is returned. If an instance is specified
70
+ # that exists but is not owned by the user making the request, then that
71
+ # instance will not be included in the returned results.
72
+ #
73
+ # Recently terminated instances will be included in the returned results
74
+ # for a small interval subsequent to their termination. This interval
75
+ # is typically of the order of one hour.
76
+ def describe_instances(instanceIds=[])
77
+ params = pathlist("InstanceId", instanceIds)
78
+ DescribeInstancesResponse.new(make_request("DescribeInstances", params))
79
+ end
80
+
81
+ # The TerminateInstances operation shuts down one or more instances.
82
+ # This operation is idempotent and terminating an instance that is
83
+ # in the process of shutting down (or already terminated) will succeed.
84
+ #
85
+ # Terminated instances remain visible for a short period of time
86
+ # (approximately one hour) after termination, after which their
87
+ # instance ID is invalidated.
88
+ def terminate_instances(instanceIds)
89
+ params = pathlist("InstanceId", instanceIds)
90
+ TerminateInstancesResponse.new(make_request("TerminateInstances", params))
91
+ end
92
+
93
+ end
94
+
95
+ end
@@ -0,0 +1,38 @@
1
+ # Amazon Web Services EC2 Query API Ruby Library
2
+ # This library has been packaged as a Ruby Gem
3
+ # by Glenn Rempe ( glenn @nospam@ elasticworkbench.com ).
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
8
+
9
+ module EC2
10
+
11
+ class AWSAuthConnection
12
+
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))
19
+ end
20
+
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
24
+ # all registered keypairs is required.
25
+ def describe_keypairs(keyNames=[])
26
+ params = pathlist("KeyName", keyNames)
27
+ DescribeKeyPairsResponse.new(make_request("DescribeKeyPairs", params))
28
+ end
29
+
30
+ # The DeleteKeyPair operation deletes a keypair.
31
+ def delete_keypair(keyName)
32
+ params = { "KeyName" => keyName }
33
+ DeleteKeyPairResponse.new(make_request("DeleteKeyPair", params))
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,340 @@
1
+ # Amazon Web Services EC2 Query API Ruby Library
2
+ # This library has been packaged as a Ruby Gem
3
+ # by Glenn Rempe ( glenn @nospam@ elasticworkbench.com ).
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
8
+
9
+ module EC2
10
+
11
+ class Response
12
+ attr_reader :http_response
13
+ attr_reader :http_xml
14
+ attr_reader :structure
15
+
16
+ ERROR_XPATH = "Response/Errors/Error"
17
+
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
+
30
+ def is_error?
31
+ @is_error
32
+ end
33
+
34
+ def parse_error
35
+ doc = Document.new(@http_xml)
36
+ element = XPath.first(doc, ERROR_XPATH)
37
+
38
+ errorCode = XPath.first(element, "Code").text
39
+ errorMessage = XPath.first(element, "Message").text
40
+
41
+ [["#{errorCode}: #{errorMessage}"]]
42
+ end
43
+
44
+ def parse
45
+ # Placeholder -- this method should be overridden in child classes.
46
+ nil
47
+ end
48
+
49
+ def to_s
50
+ @structure.collect do |line|
51
+ line.join("\t")
52
+ end.join("\n")
53
+ end
54
+
55
+ end
56
+
57
+
58
+ class DescribeImagesResponse < Response
59
+ ELEMENT_XPATH = "DescribeImagesResponse/imagesSet/item"
60
+ def parse
61
+ doc = Document.new(@http_xml)
62
+ lines = []
63
+
64
+ doc.elements.each(ELEMENT_XPATH) do |element|
65
+ imageId = XPath.first(element, "imageId").text
66
+ imageLocation = XPath.first(element, "imageLocation").text
67
+ imageOwnerId = XPath.first(element, "imageOwnerId").text
68
+ imageState = XPath.first(element, "imageState").text
69
+ isPublic = XPath.first(element, "isPublic").text
70
+ lines << ["IMAGE", imageId, imageLocation, imageOwnerId, imageState, isPublic]
71
+ end
72
+ lines
73
+ end
74
+ end
75
+
76
+
77
+ class RegisterImageResponse < Response
78
+ ELEMENT_XPATH = "RegisterImageResponse/imageId"
79
+ def parse
80
+ doc = Document.new(@http_xml)
81
+ lines = [["IMAGE", 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 = Document.new(@http_xml)
98
+ element = XPath.first(doc, ELEMENT_XPATH)
99
+
100
+ keyName = XPath.first(element, "keyName").text
101
+ keyFingerprint = XPath.first(element, "keyFingerprint").text
102
+ keyMaterial = XPath.first(element, "keyMaterial").text
103
+
104
+ line = [["KEYPAIR", keyName, keyFingerprint], [keyMaterial]]
105
+ end
106
+ end
107
+
108
+
109
+ class DescribeKeyPairsResponse < Response
110
+ ELEMENT_XPATH = "DescribeKeyPairsResponse/keySet/item"
111
+ def parse
112
+ doc = Document.new(@http_xml)
113
+ lines = []
114
+
115
+ doc.elements.each(ELEMENT_XPATH) do |element|
116
+ keyName = XPath.first(element, "keyName").text
117
+ keyFingerprint = XPath.first(element, "keyFingerprint").text
118
+ lines << ["KEYPAIR", keyName, keyFingerprint]
119
+ end
120
+ lines
121
+ 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."]]
129
+ end
130
+ end
131
+
132
+
133
+ class RunInstancesResponse < Response
134
+ ELEMENT_XPATH = "RunInstancesResponse"
135
+ def parse
136
+ doc = Document.new(@http_xml)
137
+ lines = []
138
+
139
+ rootelement = XPath.first(doc, ELEMENT_XPATH)
140
+
141
+ reservationId = XPath.first(rootelement, "reservationId").text
142
+ ownerId = 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 = XPath.first(doc, ELEMENT_XPATH)
154
+ rootelement.elements.each("instancesSet/item") do |element|
155
+ instanceId = XPath.first(element, "instanceId").text
156
+ imageId = XPath.first(element, "imageId").text
157
+ instanceState = XPath.first(element, "instanceState/name").text
158
+ # Only for debug mode, which we don't support yet:
159
+ instanceStateCode = XPath.first(element, "instanceState/code").text
160
+ dnsName = XPath.first(element, "dnsName").text
161
+ # We don't return this, but still:
162
+ reason = XPath.first(element, "reason").text
163
+ lines << ["INSTANCE", instanceId, imageId, dnsName, instanceState]
164
+ end
165
+ lines
166
+ end
167
+ end
168
+
169
+
170
+ class DescribeInstancesResponse < Response
171
+ ELEMENT_XPATH = "DescribeInstancesResponse/reservationSet/item"
172
+ def parse
173
+ doc = Document.new(@http_xml)
174
+ lines = []
175
+
176
+ doc.elements.each(ELEMENT_XPATH) do |rootelement|
177
+ reservationId = XPath.first(rootelement, "reservationId").text
178
+ ownerId = 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
185
+ end
186
+ end
187
+ lines << ["RESERVATION", reservationId, ownerId, groups]
188
+
189
+ rootelement.elements.each("instancesSet/item") do |element|
190
+ instanceId = XPath.first(element, "instanceId").text
191
+ imageId = XPath.first(element, "imageId").text
192
+ instanceState = XPath.first(element, "instanceState/name").text
193
+ # Only for debug mode, which we don't support yet:
194
+ instanceStateCode = XPath.first(element, "instanceState/code").text
195
+ dnsName = XPath.first(element, "dnsName").text
196
+ # We don't return this, but still:
197
+ reason = 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 = Document.new(@http_xml)
210
+ lines = []
211
+
212
+ doc.elements.each(ELEMENT_XPATH) do |element|
213
+ instanceId = XPath.first(element, "instanceId").text
214
+ shutdownState = XPath.first(element, "shutdownState/name").text
215
+ # Only for debug mode, which we don't support yet:
216
+ shutdownStateCode = XPath.first(element, "shutdownState/code").text
217
+ previousState = XPath.first(element, "previousState/name").text
218
+ # Only for debug mode, which we don't support yet:
219
+ previousStateCode = XPath.first(element, "previousState/code").text
220
+ lines << ["INSTANCE", instanceId, previousState, shutdownState]
221
+ end
222
+ lines
223
+ end
224
+ end
225
+
226
+
227
+ class CreateSecurityGroupResponse < Response
228
+ def parse
229
+ # If we don't get an error, the creation succeeded.
230
+ [["Security Group created."]]
231
+ end
232
+ end
233
+
234
+
235
+ class DescribeSecurityGroupsResponse < Response
236
+ ELEMENT_XPATH = "DescribeSecurityGroupsResponse/securityGroupInfo/item"
237
+ def parse
238
+ doc = Document.new(@http_xml)
239
+ lines = []
240
+
241
+ doc.elements.each(ELEMENT_XPATH) do |rootelement|
242
+ groupName = XPath.first(rootelement, "groupName").text
243
+ ownerId = XPath.first(rootelement, "ownerId").text
244
+ groupDescription = XPath.first(rootelement, "groupDescription").text
245
+ lines << ["GROUP", ownerId, groupName, groupDescription]
246
+ rootelement.elements.each("ipPermissions/item") do |element|
247
+ ipProtocol = XPath.first(element, "ipProtocol").text
248
+ fromPort = XPath.first(element, "fromPort").text
249
+ toPort = XPath.first(element, "toPort").text
250
+ permArr = [
251
+ "PERMISSION",
252
+ ownerId,
253
+ groupName,
254
+ "ALLOWS",
255
+ ipProtocol,
256
+ fromPort,
257
+ toPort,
258
+ "FROM"
259
+ ]
260
+ element.elements.each("groups/item") do |subelement|
261
+ userId = XPath.first(subelement, "userId").text
262
+ targetGroupName = XPath.first(subelement, "groupName").text
263
+ lines << permArr + ["USER", userId, "GRPNAME", targetGroupName]
264
+ end
265
+ element.elements.each("ipRanges/item") do |subelement|
266
+ cidrIp = XPath.first(subelement, "cidrIp").text
267
+ lines << permArr + ["CIDR", cidrIp]
268
+ end
269
+ end
270
+ end
271
+ lines
272
+ end
273
+ end
274
+
275
+
276
+ class DeleteSecurityGroupResponse < Response
277
+ def parse
278
+ # If we don't get an error, the deletion succeeded.
279
+ [["Security Group deleted."]]
280
+ end
281
+ end
282
+
283
+
284
+ class AuthorizeSecurityGroupIngressResponse < Response
285
+ def parse
286
+ # If we don't get an error, the authorization succeeded.
287
+ [["Ingress authorized."]]
288
+ end
289
+ end
290
+
291
+
292
+ class RevokeSecurityGroupIngressResponse < Response
293
+ def parse
294
+ # If we don't get an error, the revocation succeeded.
295
+ [["Ingress revoked."]]
296
+ end
297
+ end
298
+
299
+
300
+ class ModifyImageAttributeResponse < Response
301
+ def parse
302
+ # If we don't get an error, modification succeeded.
303
+ [["Image attribute modified."]]
304
+ end
305
+ end
306
+
307
+
308
+ class ResetImageAttributeResponse < Response
309
+ def parse
310
+ # If we don't get an error, reset succeeded.
311
+ [["Image attribute reset."]]
312
+ end
313
+ end
314
+
315
+
316
+ class DescribeImageAttributeResponse < Response
317
+ ELEMENT_XPATH = "DescribeImageAttributeResponse"
318
+ def parse
319
+ doc = Document.new(@http_xml)
320
+ lines = []
321
+
322
+ rootelement = XPath.first(doc, ELEMENT_XPATH)
323
+ imageId = XPath.first(rootelement, "imageId").text
324
+
325
+ # Handle launchPermission attributes:
326
+ rootelement.elements.each("launchPermission/item/*") do |element|
327
+ lines << [
328
+ "launchPermission",
329
+ imageId,
330
+ element.name,
331
+ element.text
332
+ ]
333
+ end
334
+ lines
335
+ end
336
+ end
337
+
338
+ # end EC2 Module
339
+ end
340
+