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.
- data/README +71 -0
- data/bin/ec2cli +67 -0
- data/lib/ec2cli/cli/evaluate.rb +53 -0
- data/lib/ec2cli/cli/functions.rb +104 -0
- data/lib/ec2cli/cli/help.rb +40 -0
- data/lib/ec2cli/cli/options.rb +35 -0
- data/lib/ec2cli/ec2-client.rb +108 -0
- data/lib/ec2cli/ec2-driver.rb +322 -0
- data/lib/ec2cli/ec2-endpoint.rb +32 -0
- data/lib/ec2cli/ec2-error.rb +10 -0
- data/lib/ec2cli/ec2-parser.tab.rb +519 -0
- data/lib/ec2cli/ec2-parser.y +233 -0
- data/lib/ec2cli.rb +11 -0
- metadata +74 -0
@@ -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
|