ec2cli 0.1.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.
@@ -0,0 +1,322 @@
1
+ require 'forwardable'
2
+ require 'rexml/parsers/pullparser'
3
+
4
+ require 'ec2cli/ec2-client'
5
+ require 'ec2cli/ec2-parser.tab'
6
+
7
+ module EC2
8
+ class Driver
9
+ extend Forwardable
10
+
11
+ class Rownum
12
+ def initialize(rownum)
13
+ @rownum = rownum
14
+ end
15
+
16
+ def to_i
17
+ @rownum
18
+ end
19
+ end # Rownum
20
+
21
+ def initialize(accessKeyId, secretAccessKey, endpoint_or_region)
22
+ @client = EC2::Client.new(accessKeyId, secretAccessKey, endpoint_or_region)
23
+ end
24
+
25
+ def_delegators(
26
+ :@client,
27
+ :endpoint,
28
+ :region,
29
+ :set_endpoint_and_region,
30
+ :debug, :'debug=')
31
+
32
+ def execute(query, opts = {})
33
+ parsed, script_type, script = EC2::Parser.parse(query)
34
+ command = parsed.class.name.split('::').last.to_sym
35
+
36
+ case command
37
+ when :DESCRIBE_INSTANCES
38
+ describe_instances(parsed.filter)
39
+ when :DESCRIBE_IMAGES
40
+ describe_images(parsed.filter, :all => parsed.all)
41
+ when :RUN_INSTANCES
42
+ run_instainces(parsed.image_id)
43
+ when :START_INSTANCES
44
+ start_instainces(parsed.instances)
45
+ when :STOP_INSTANCES
46
+ stop_instainces(parsed.instances)
47
+ when :CREATE_TAGS
48
+ create_tags(parsed.filter, parsed.tags)
49
+ when :SHOW_REGIONS
50
+ EC2::Endpoint.regions
51
+ when :USE
52
+ set_endpoint_and_region(parsed.endpoint_or_region)
53
+ nil
54
+ else
55
+ raise 'must not happen'
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def create_filter_params(filter)
62
+ params = {}
63
+
64
+ (filter || {}).each_with_index do |name_values, i|
65
+ name, values, = name_values
66
+ params["Filter.#{i+1}.Name"] = name
67
+
68
+ values.each_with_index do |value, j|
69
+ params["Filter.#{i+1}.Value.#{j+1}"] = value
70
+ end
71
+ end
72
+
73
+ return params
74
+ end
75
+
76
+ def describe_instances(filter)
77
+ params = create_filter_params(filter)
78
+ source = @client.query('DescribeInstances', params)
79
+ parser = REXML::Parsers::PullParser.new(source)
80
+
81
+ rows = []
82
+ instance_id = nil
83
+ status = nil
84
+ ip_addr = nil
85
+ groups = nil
86
+ instance_type = nil
87
+ zone = nil
88
+ tags = {}
89
+
90
+ while parser.has_next?
91
+ event = parser.pull
92
+ next if event.event_type != :start_element
93
+
94
+ case event[0]
95
+ when 'instanceId'
96
+ ip_addr = nil
97
+ tags = {}
98
+ instance_id = parser.pull[0]
99
+ when 'instanceState'
100
+ until event.event_type == :start_element and event[0] == 'name'
101
+ event = parser.pull
102
+ end
103
+
104
+ status = parser.pull[0]
105
+ when 'instanceType'
106
+ instance_type = parser.pull[0]
107
+ when 'availabilityZone'
108
+ zone = parser.pull[0]
109
+ when 'privateIpAddress'
110
+ ip_addr = parser.pull[0] unless ip_addr
111
+ when 'groupSet'
112
+ groups = []
113
+
114
+ until event.event_type == :end_element and event[0] == 'groupSet'
115
+ event = parser.pull
116
+
117
+ if event.event_type == :start_element and event[0] == 'groupName'
118
+ groups << parser.pull[0]
119
+ end
120
+ end
121
+ when 'tagSet'
122
+ tag_key = nil
123
+
124
+ until event.event_type == :end_element and event[0] == 'tagSet'
125
+ event = parser.pull
126
+ next unless event.event_type == :start_element
127
+
128
+ case event[0]
129
+ when 'key'; tag_key = parser.pull[0]
130
+ when 'value'; tags[tag_key] = parser.pull[0]
131
+ end
132
+ end
133
+ when 'ebsOptimized'
134
+ row = [ tags['Name'],
135
+ instance_id,
136
+ status,
137
+ ip_addr,
138
+ instance_type,
139
+ groups,
140
+ tags,
141
+ zone,
142
+ ]
143
+
144
+ yield row if block_given?
145
+ rows << row
146
+ end
147
+ end
148
+
149
+ return rows
150
+ end # describe_instances
151
+
152
+ def describe_images(filter, opts = {})
153
+ filter ||= []
154
+ params = {}
155
+ owner = filter.find {|k, v| k =~ /\Aowner\Z/i }
156
+
157
+ if owner
158
+ filter.delete_if {|k, v| k == owner[0] }
159
+ params['Owner.1'] = owner[1].first
160
+ elsif not opts[:qll]
161
+ params['Owner.1'] = 'self'
162
+ end
163
+
164
+ unless filter.any? {|k, v| k == 'image-id' }
165
+ filter << ['image-type', ['machine']]
166
+ end
167
+
168
+ params.update(create_filter_params(filter))
169
+ source = @client.query('DescribeImages', params)
170
+ parser = REXML::Parsers::PullParser.new(source)
171
+
172
+ rows = []
173
+ image_id = nil
174
+ image_state = nil
175
+ architecture = nil
176
+ image_type = nil
177
+ name = nil
178
+ description = nil
179
+
180
+ while parser.has_next?
181
+ event = parser.pull
182
+ next if event.event_type != :start_element
183
+
184
+ case event[0]
185
+ when 'imageId'
186
+ image_id = parser.pull[0]
187
+ when 'imageState'
188
+ image_state = parser.pull[0]
189
+ when 'architecture'
190
+ architecture = parser.pull[0]
191
+ when 'imageType'
192
+ image_type = parser.pull[0]
193
+ when 'name'
194
+ name = parser.pull[0]
195
+ when 'description'
196
+ description = parser.pull[0]
197
+ when 'hypervisor'
198
+ row = [
199
+ image_id,
200
+ image_state,
201
+ architecture,
202
+ image_type,
203
+ name,
204
+ description,
205
+ ]
206
+
207
+ yield row if block_given?
208
+ rows << row
209
+ end
210
+ end
211
+
212
+ return rows
213
+ end # describe_images
214
+
215
+ def run_instainces(image_id)
216
+ # XXX:
217
+ params = {
218
+ 'ImageId' => image_id,
219
+ 'MinCount' => 1,
220
+ 'MaxCount' => 1,
221
+ }
222
+
223
+ @client.query('RunInstances', params)
224
+ Rownum.new(1)
225
+ end
226
+
227
+ def start_instainces(instances)
228
+ instances = get_instaince_ids_by_names(instances)
229
+ params = {}
230
+
231
+ instances.each_with_index do |instance_id, i|
232
+ params["InstanceId.#{i+1}"] = instance_id
233
+ end
234
+
235
+ @client.query('StartInstances', params)
236
+
237
+ Rownum.new(instances.length)
238
+ end
239
+
240
+ def stop_instainces(instances)
241
+ instances = get_instaince_ids_by_names(instances)
242
+ params = {}
243
+
244
+ instances.each_with_index do |instance_id, i|
245
+ params["InstanceId.#{i+1}"] = instance_id
246
+ end
247
+
248
+ @client.query('StopInstances', params)
249
+
250
+ Rownum.new(instances.length)
251
+ end
252
+
253
+ def create_tags(filter, tags)
254
+ instance_ids = get_instance_ids(create_filter_params(filter))
255
+
256
+ params = {}
257
+ delete_params = {}
258
+ i = j = 0
259
+
260
+ instance_ids.each do |instance_id|
261
+ tags.each do |key, value|
262
+ if value
263
+ params["ResourceId.#{i+1}"] = instance_id
264
+ params["Tag.#{i+1}.Key"] = key
265
+ params["Tag.#{i+1}.Value"] = value
266
+ i += 1
267
+ else
268
+ delete_params["ResourceId.#{j+1}"] = instance_id
269
+ delete_params["Tag.#{j+1}.Key"] = key
270
+ j += 1
271
+ end
272
+ end
273
+ end
274
+
275
+ @client.query('CreateTags', params) unless params.empty?
276
+ @client.query('DeleteTags', delete_params) unless delete_params.empty?
277
+
278
+ Rownum.new(instance_ids.length)
279
+ end
280
+
281
+ def get_instaince_ids_by_names(id_or_names)
282
+ id_or_names.map do |id_or_name|
283
+ if id_or_name =~ /\Ai-[0-9a-z]+\Z/
284
+ id_or_name
285
+ else
286
+ get_instance_id_by_name(id_or_name)
287
+ end
288
+ end
289
+ end
290
+
291
+ def get_instance_id_by_name(name)
292
+ params = {
293
+ 'Filter.1.Name' => 'tag:Name',
294
+ 'Filter.1.Value' => name
295
+ }
296
+
297
+ instance_ids = get_instance_ids(params)
298
+
299
+ return instance_ids.first
300
+ end
301
+
302
+
303
+ def get_instance_ids(params)
304
+ instance_ids = []
305
+
306
+ source = @client.query('DescribeInstances', params)
307
+ parser = REXML::Parsers::PullParser.new(source)
308
+
309
+ while parser.has_next?
310
+ event = parser.pull
311
+ next if event.event_type != :start_element
312
+
313
+ if event[0] == 'instanceId'
314
+ instance_ids << parser.pull[0]
315
+ end
316
+ end
317
+
318
+ return instance_ids
319
+ end
320
+
321
+ end # Driver
322
+ end # EC2
@@ -0,0 +1,32 @@
1
+ require 'ec2cli/ec2-error'
2
+
3
+ module EC2
4
+ class Endpoint
5
+
6
+ ENDPOINTS = {
7
+ 'ec2.us-east-1.amazonaws.com' => 'us-east-1',
8
+ 'ec2.us-west-2.amazonaws.com' => 'us-west-2',
9
+ 'ec2.us-west-1.amazonaws.com' => 'us-west-1',
10
+ 'ec2.eu-west-1.amazonaws.com' => 'eu-west-1',
11
+ 'ec2.ap-southeast-1.amazonaws.com' => 'ap-southeast-1',
12
+ 'ec2.ap-southeast-2.amazonaws.com' => 'ap-southeast-2',
13
+ 'ec2.ap-northeast-1.amazonaws.com' => 'ap-northeast-1',
14
+ 'ec2.sa-east-1.amazonaws.com' => 'sa-east-1',
15
+ }
16
+
17
+ def self.endpoint_and_region(endpoint_or_region)
18
+ if ENDPOINTS.key?(endpoint_or_region)
19
+ [endpoint_or_region, ENDPOINTS[endpoint_or_region]]
20
+ elsif ENDPOINTS.value?(endpoint_or_region)
21
+ [ENDPOINTS.key(endpoint_or_region), endpoint_or_region]
22
+ else
23
+ raise EC2::Error, "Unknown endpoint or region: #{endpoint_or_region}"
24
+ end
25
+ end
26
+
27
+ def self.regions
28
+ ENDPOINTS.values.dup
29
+ end
30
+
31
+ end # Endpoint
32
+ end # EC2
@@ -0,0 +1,10 @@
1
+ module EC2
2
+ class Error < StandardError
3
+ attr_reader :data
4
+
5
+ def initialize(error_message, data = {})
6
+ super(error_message)
7
+ @data = data
8
+ end
9
+ end
10
+ end