ec2cli 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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