ec2ex 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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6fd690807cff2f0f5be2b942621f1efe62ff96de
4
+ data.tar.gz: 7ef5ca48952371e67172a80caa92c372b1751144
5
+ SHA512:
6
+ metadata.gz: 81c2592526c7d51688a431c99167abe994b3f1a940c22b38ec7ad56909238efc4b841e78df317ecf27d2a66692cfec228115994d44c6f0e32e0d498f56ff8cde
7
+ data.tar.gz: 5ba6cbfc97e284c35de7f77e9125f4c48c0fabe740dc91a2b076803095865425d3c071d98df36ec73cd8b94666ed08d7274ed03dbf698692ff2c65768537d4ac
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
@@ -0,0 +1,8 @@
1
+ Gemfile.lock
2
+ doc/
3
+ pkg/
4
+ **/*.gem
5
+ .yardoc/
6
+ .bundle/
7
+ vendor/bundle/
8
+ .tags
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour --format documentation
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1
7
+ - ruby-head
8
+
9
+ os:
10
+ - linux
11
+ - osx
12
+
13
+ gemfile:
14
+ - Gemfile
15
+
16
+ script: 'bundle exec rake spec'
@@ -0,0 +1 @@
1
+ --markup markdown --title "ec2ex Documentation" --protected
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2014-12-15
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'kramdown'
7
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Hiroshi Toyama
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,45 @@
1
+ # ec2ex [![Build Status](https://secure.travis-ci.org/toyama0919/ec2ex.png?branch=master)](http://travis-ci.org/toyama0919/ec2ex)
2
+
3
+ TODO: Summary
4
+
5
+ TODO: Description
6
+
7
+ ## Examples
8
+
9
+ $ ec2ex sample
10
+ #=> hoge
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ gem 'ec2ex'
17
+
18
+ And then execute:
19
+
20
+ $ bundle
21
+
22
+ Or install it yourself as:
23
+
24
+ $ gem install ec2ex
25
+
26
+ ## Contributing
27
+
28
+ 1. Fork it
29
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
30
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
31
+ 4. Push to the branch (`git push origin my-new-feature`)
32
+ 5. Create new [Pull Request](../../pull/new/master)
33
+
34
+ ## Information
35
+
36
+ * [Homepage](https://github.com/toyama0919/ec2ex)
37
+ * [Issues](https://github.com/toyama0919/ec2ex/issues)
38
+ * [Documentation](http://rubydoc.info/gems/ec2ex/frames)
39
+ * [Email](mailto:toyama0919@gmail.com)
40
+
41
+ ## Copyright
42
+
43
+ Copyright (c) 2014 Hiroshi Toyama
44
+
45
+ See [LICENSE.txt](../LICENSE.txt) for details.
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+
6
+ begin
7
+ gem 'rubygems-tasks', '~> 0.2'
8
+ require 'rubygems/tasks'
9
+
10
+ Gem::Tasks.new
11
+ rescue LoadError => e
12
+ warn e.message
13
+ warn "Run `gem install rubygems-tasks` to install Gem::Tasks."
14
+ end
15
+
16
+ begin
17
+ gem 'rspec', '~> 2.4'
18
+ require 'rspec/core/rake_task'
19
+
20
+ RSpec::Core::RakeTask.new
21
+ rescue LoadError => e
22
+ task :spec do
23
+ abort "Please run `gem install rspec` to install RSpec."
24
+ end
25
+ end
26
+
27
+ task :test => :spec
28
+ task :default => :spec
29
+
30
+ begin
31
+ gem 'yard', '~> 0.8'
32
+ require 'yard'
33
+
34
+ YARD::Rake::YardocTask.new
35
+ rescue LoadError => e
36
+ task :yard do
37
+ abort "Please run `gem install yard` to install YARD."
38
+ end
39
+ end
40
+ task :doc => :yard
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'ec2ex'
5
+
6
+ Ec2ex::CLI.start
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require File.expand_path('../lib/ec2ex/version', __FILE__)
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.name = "ec2ex"
7
+ gem.version = Ec2ex::VERSION
8
+ gem.summary = %q{ec2 expand command line}
9
+ gem.description = %q{ec2 expand command line}
10
+ gem.license = "MIT"
11
+ gem.authors = ["Hiroshi Toyama"]
12
+ gem.email = "toyama0919@gmail.com"
13
+ gem.homepage = "https://github.com/toyama0919/ec2ex"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency 'thor'
21
+ gem.add_dependency 'hashie'
22
+ gem.add_dependency 'ipaddress'
23
+ gem.add_dependency 'aws-sdk', '~> 2.0.11.pre'
24
+ gem.add_dependency 'activesupport'
25
+
26
+ gem.add_development_dependency 'bundler', '~> 1.7.2'
27
+ gem.add_development_dependency 'pry', '~> 0.10.1'
28
+ gem.add_development_dependency 'rake', '~> 10.3.2'
29
+ gem.add_development_dependency 'rspec', '~> 2.4'
30
+ gem.add_development_dependency 'rubocop', '~> 0.24.1'
31
+ gem.add_development_dependency 'rubygems-tasks', '~> 0.2'
32
+ gem.add_development_dependency 'yard', '~> 0.8'
33
+ end
@@ -0,0 +1,3 @@
1
+ require 'ec2ex/version'
2
+ require 'ec2ex/core'
3
+ require 'ec2ex/cli'
@@ -0,0 +1,476 @@
1
+ require 'thor'
2
+ require 'json'
3
+ require 'pp'
4
+ require 'hashie'
5
+ require 'aws-sdk'
6
+ require 'active_support/core_ext/hash'
7
+
8
+ module Ec2ex
9
+ class CLI < Thor
10
+ include Thor::Actions
11
+ map '-s' => :search
12
+ map '-t' => :search_by_tags
13
+ map '-i' => :search_images
14
+ map '-a' => :aggregate
15
+
16
+ class_option :profile, type: :string, default: 'default', required: true, desc: 'name tag'
17
+ class_option :fields, type: :array, default: nil, desc: 'fields'
18
+ def initialize(args = [], options = {}, config = {})
19
+ super(args, options, config)
20
+ @global_options = config[:shell].base.options
21
+ @core = Core.new
22
+ @ec2 = Aws::EC2::Client.new
23
+ @elb = Aws::ElasticLoadBalancing::Client.new
24
+ end
25
+
26
+ desc 'search', 'search instance'
27
+ option :name, aliases: '-n', type: :string, default: '', required: true, desc: 'name tag'
28
+ option :running_only, aliases: '--ro', type: :boolean, default: true, desc: 'grouping key'
29
+ def search(name = options['name'])
30
+ results = @core.instances_hash({ Name: name }, options['running_only'])
31
+ puts_json results
32
+ end
33
+
34
+ desc 'search_by_tags', 'search by tags instance'
35
+ option :condition, aliases: '-c', type: :hash, default: {}, desc: 'grouping key'
36
+ option :running_only, aliases: '--ro', type: :boolean, default: true, desc: 'grouping key'
37
+ def search_by_tags
38
+ puts_json @core.instances_hash(options['condition'], options['running_only'])
39
+ end
40
+
41
+ desc 'reserved', 'reserved instance'
42
+ def reserved
43
+ filter = []
44
+ filter << { name: 'state', values: ['active'] }
45
+ reserved_hash = {}
46
+ @ec2.describe_reserved_instances(filters: filter)[:reserved_instances].each{ |reserved|
47
+ sum = reserved_hash[reserved[:instance_type] + '_' + reserved[:availability_zone]] || 0
48
+ reserved_hash[reserved[:instance_type] + '_' + reserved[:availability_zone]] = sum + reserved[:instance_count]
49
+ }
50
+ list = @core.instances_hash({}, true).select { |instance| instance[:instance_lifecycle].nil? }
51
+ list = list.map{ |_instance|
52
+ ['instance_type', 'placement.availability_zone'].map do |key|
53
+ eval("instance.#{key} ")
54
+ end.join('_')
55
+ }
56
+ result = {}
57
+ @core.group_count(list).each do |k, v|
58
+ result[k] = { instance_count: v, reserved_count: 0 }
59
+ end
60
+ reserved_hash.each do |k, v|
61
+ hash = result[k] || { instance_count: 0 }
62
+ hash[:reserved_count] = v
63
+ result[k] = hash
64
+ end
65
+ puts_json(result)
66
+ end
67
+
68
+ desc 'create_image', 'create image'
69
+ option :name, aliases: '-n', type: :string, default: '', required: true, desc: 'name tag'
70
+ def create_image
71
+ results = @core.instances_hash({ Name: options['name'] }, false)
72
+ results.each do |instance|
73
+ @core.create_image_with_instance(instance)
74
+ end
75
+ end
76
+
77
+ desc 'copy', 'copy instance'
78
+ option :name, aliases: '-n', type: :string, default: '', required: true, desc: 'name tag'
79
+ option :params, aliases: '-p', type: :string, default: '{}', desc: 'params'
80
+ option :tag, aliases: '-t', type: :hash, default: {}, desc: 'name tag'
81
+ def copy
82
+ results = @core.instances_hash({ Name: options['name'] }, true)
83
+ results.each do |instance|
84
+ image_id = @core.create_image_with_instance(instance)
85
+ security_group_ids = instance.security_groups.map { |security_group| security_group.group_id }
86
+ request = {
87
+ image_id: image_id,
88
+ min_count: 1,
89
+ max_count: 1,
90
+ security_group_ids: security_group_ids,
91
+ instance_type: instance.instance_type,
92
+ placement: instance.placement.to_hash,
93
+ subnet_id: instance.subnet_id,
94
+ private_ip_address: instance.private_ip_address
95
+ }
96
+ unless instance.iam_instance_profile.nil?
97
+ request[:iam_instance_profile] = { name: instance.iam_instance_profile.arn.split('/').last }
98
+ end
99
+ request.merge!(eval(options['params']))
100
+ request[:subnet_id] = @core.get_subnet(request[:private_ip_address]).subnet_id
101
+
102
+ response = @ec2.run_instances(request)
103
+ instance_id = response.instances.first.instance_id
104
+ @ec2.wait_until(:instance_running, instance_ids: [instance_id])
105
+ @ec2.create_tags(resources: [instance_id], tags: instance.tags)
106
+ unless options['tag'].nil?
107
+ @ec2.create_tags(resources: [instance_id], tags: @core.format_tag(options['tag']))
108
+ end
109
+ end
110
+ end
111
+
112
+ desc 'renew', 'renew instance'
113
+ option :name, aliases: '-n', type: :string, default: '', required: true, desc: 'name tag'
114
+ option :stop, type: :boolean, default: true, desc: 'stop'
115
+ option :params, aliases: '-p', type: :string, default: nil, desc: 'params'
116
+ def renew
117
+ params = eval(options['params'])
118
+ results = @core.instances_hash({ Name: options['name'] }, false)
119
+ results.each do |instance|
120
+ tags = instance.tags
121
+ tag_hash = @core.get_tag_hash(tags)
122
+ if options['stop']
123
+ @core.stop_instance(instance.instance_id)
124
+ end
125
+
126
+ image_id = @core.create_image_with_instance(instance)
127
+
128
+ @core.terminate_instance(instance.instance_id)
129
+ security_group_ids = instance.security_groups.map { |security_group| security_group.group_id }
130
+ request = {
131
+ image_id: image_id,
132
+ min_count: 1,
133
+ max_count: 1,
134
+ security_group_ids: security_group_ids,
135
+ instance_type: instance.instance_type,
136
+ placement: instance.placement.to_hash,
137
+ private_ip_address: instance.private_ip_address
138
+ }
139
+ unless instance.iam_instance_profile.nil?
140
+ request[:iam_instance_profile] = { name: instance.iam_instance_profile.arn.split('/').last }
141
+ end
142
+ request.merge!(params)
143
+ request[:subnet_id] = @core.get_subnet(request[:private_ip_address]).subnet_id
144
+
145
+ response = @ec2.run_instances(request)
146
+ instance_id = response.instances.first.instance_id
147
+ sleep 5
148
+ @ec2.wait_until(:instance_running, instance_ids: [instance_id])
149
+ @ec2.create_tags(resources: [instance_id], tags: instance.tags)
150
+
151
+ @core.associate_address(instance_id, instance.public_ip_address)
152
+ end
153
+ end
154
+
155
+ desc 'spot', 'request spot instances'
156
+ option :name, aliases: '-n', type: :string, required: true, desc: 'name tag'
157
+ option :price, type: :string, required: true, desc: 'price'
158
+ option :private_ip_address, type: :string, default: nil, desc: 'private_ip_address'
159
+ option :public_ip_address, type: :string, default: nil, desc: 'public_ip_address'
160
+ option :params, aliases: '-p', type: :string, default: '{}', desc: 'params'
161
+ option :tag, aliases: '-t', type: :hash, default: {}, desc: 'name tag'
162
+ option :renew, aliases: '-r', type: :boolean, default: false, desc: 'renew instance'
163
+ option :persistent, type: :boolean, default: false, desc: 'persistent request'
164
+ def spot
165
+ results = @core.instances_hash({ Name: options['name'] }, true)
166
+ results.each do |instance|
167
+ image_id = @core.create_image_with_instance(instance)
168
+ security_group_ids = instance.security_groups.map { |security_group| security_group.group_id }
169
+ option = {
170
+ instance_count: 1,
171
+ spot_price: options['price'],
172
+ launch_specification: {
173
+ image_id: image_id,
174
+ instance_type: instance.instance_type,
175
+ security_group_ids: security_group_ids,
176
+ subnet_id: instance.subnet_id
177
+ },
178
+ }
179
+ option[:type] = 'persistent' if options['persistent']
180
+
181
+ unless instance.iam_instance_profile.nil?
182
+ option[:launch_specification][:iam_instance_profile] = { name: instance.iam_instance_profile.arn.split('/').last }
183
+ end
184
+
185
+ option[:launch_specification].merge!(eval(options['params']))
186
+
187
+ private_ip_address = nil
188
+ if options['private_ip_address'].nil?
189
+ private_ip_address = instance.private_ip_address if options['renew']
190
+ else
191
+ private_ip_address = options['private_ip_address']
192
+ end
193
+
194
+ unless private_ip_address.nil?
195
+ network_interface = {
196
+ device_index: 0,
197
+ subnet_id: @core.get_subnet(private_ip_address).subnet_id,
198
+ groups: security_group_ids,
199
+ private_ip_addresses: [{ private_ip_address: private_ip_address, primary: true }]
200
+ }
201
+ option[:launch_specification][:network_interfaces] = [network_interface]
202
+ option[:launch_specification].delete(:security_group_ids)
203
+ option[:launch_specification].delete(:subnet_id)
204
+ end
205
+ @core.terminate_instance(instance.instance_id) if options['renew']
206
+
207
+ response = @ec2.request_spot_instances(option)
208
+ spot_instance_request_id = response.spot_instance_requests.first.spot_instance_request_id
209
+ sleep 5
210
+ instance_id = @core.wait_spot_running(spot_instance_request_id)
211
+
212
+ @ec2.create_tags(resources: [instance_id], tags: instance.tags)
213
+ @ec2.create_tags(resources: [instance_id], tags: [{ key: 'Spot', value: 'true' }])
214
+
215
+ unless options['tag'].empty?
216
+ @ec2.create_tags(resources: [instance_id], tags: @core.format_tag(options['tag']))
217
+ end
218
+
219
+ public_ip_address = nil
220
+ if options['public_ip_address'].nil?
221
+ public_ip_address = instance.public_ip_address if options['renew']
222
+ else
223
+ public_ip_address = options['public_ip_address']
224
+ end
225
+ @core.associate_address(instance_id, public_ip_address)
226
+ end
227
+ end
228
+
229
+ desc 'run_spot', 'run_spot latest image'
230
+ option :name, aliases: '-n', type: :string, required: true, desc: 'name tag'
231
+ option :price, type: :string, required: true, desc: 'price'
232
+ def run_spot
233
+ image = @core.latest_image_with_name(options['name'])
234
+ tag_hash = @core.get_tag_hash(image[:tags])
235
+ option = {
236
+ instance_count: 1,
237
+ spot_price: options['price'],
238
+ launch_specification: {
239
+ image_id: image[:image_id],
240
+ instance_type: tag_hash.instance_type
241
+ },
242
+ }
243
+
244
+ if tag_hash.iam_instance_profile
245
+ option[:launch_specification][:iam_instance_profile] = { name: tag_hash.iam_instance_profile }
246
+ end
247
+
248
+ network_interface = {
249
+ device_index: 0,
250
+ subnet_id: @core.get_subnet(tag_hash.private_ip_address).subnet_id,
251
+ groups: JSON.parse(tag_hash.security_groups),
252
+ private_ip_addresses: [{ private_ip_address: tag_hash.private_ip_address, primary: true }]
253
+ }
254
+ option[:launch_specification][:network_interfaces] = [network_interface]
255
+
256
+ response = @ec2.request_spot_instances(option)
257
+ spot_instance_request_id = response.spot_instance_requests.first.spot_instance_request_id
258
+ sleep 5
259
+ instance_id = @core.wait_spot_running(spot_instance_request_id)
260
+ @ec2.create_tags(resources: [instance_id], tags: JSON.parse(tag_hash[:tags]))
261
+
262
+ if tag_hash.public_ip_address
263
+ @core.associate_address(instance_id, tag_hash.public_ip_address)
264
+ end
265
+ end
266
+
267
+ desc 'regist_deny_acl', 'regist deny acl'
268
+ option :acl_id, type: :string, default: '', required: true, desc: 'name tag'
269
+ option :ip_address, type: :string, default: '', required: true, desc: 'name tag'
270
+ def regist_deny_acl
271
+ acls = @ec2.describe_network_acls(network_acl_ids: [options['acl_id']])
272
+
273
+ allow_any_rule_number = acls.network_acls.first.entries.select {|r|
274
+ !r.egress && r.cidr_block == '0.0.0.0/0' && r.rule_action == 'allow'
275
+ }.first.rule_number
276
+
277
+ deny_rules = acls.network_acls.first.entries.select {|r|
278
+ !r.egress && r.rule_number < allow_any_rule_number
279
+ }.sort_by { |r| r.rule_number }
280
+
281
+ next_rule_number = deny_rules.empty? ? 1 : deny_rules.last.rule_number + 1
282
+
283
+ unless deny_rules.any? { |r| r.cidr_block == "#{options['ip_address']}/32" }
284
+ option = {
285
+ network_acl_id: options['acl_id'],
286
+ rule_number: next_rule_number,
287
+ rule_action: 'deny',
288
+ protocol: '-1',
289
+ cidr_block: "#{options['ip_address']}/32",
290
+ egress: false
291
+ }
292
+ @ec2.create_network_acl_entry(option)
293
+ end
294
+ end
295
+
296
+ desc 'delete_deny_acl_all', 'delete deny acl'
297
+ option :acl_id, type: :string, required: true, desc: 'name tag'
298
+ def delete_deny_acl_all
299
+ acls = @ec2.describe_network_acls(network_acl_ids: [options['acl_id']])
300
+
301
+ allow_any_rule_number = acls.network_acl_set.first.entries.select {|r|
302
+ !r.egress && r.cidr_block == '0.0.0.0/0' && r.rule_action == 'allow'
303
+ }.first.rule_number
304
+
305
+ deny_rules = acls.network_acls.first.entries.select {|r|
306
+ !r.egress && r.rule_number < allow_any_rule_number
307
+ }.sort_by { |r| r.rule_number }
308
+
309
+ deny_rules.each do |deny_rule|
310
+ option = {
311
+ network_acl_id: options['acl_id'],
312
+ rule_number: deny_rule.rule_number,
313
+ egress: false
314
+ }
315
+ @ec2.delete_network_acl_entry(option)
316
+ end
317
+ end
318
+
319
+ desc 'acls', 'show acls'
320
+ def acls
321
+ puts_json(@ec2.describe_network_acls.data.to_hash[:network_acls])
322
+ end
323
+
324
+ desc 'subnets', 'show subnets'
325
+ def subnets
326
+ puts_json(@ec2.describe_subnets.data.to_hash[:subnets])
327
+ end
328
+
329
+ desc 'sg', 'show security groups'
330
+ def sg
331
+ puts_json(@ec2.describe_security_groups.data.to_hash[:security_groups])
332
+ end
333
+
334
+ desc 'copy_tag', 'request spot instances'
335
+ option :source, aliases: '--src', type: :string, default: nil, required: true, desc: 'name tag'
336
+ option :dest, aliases: '--dest', type: :string, default: nil, required: true, desc: 'name tag'
337
+ def copy_tag(_name = options['name'])
338
+ source = @core.instances_hash({ Name: options['source'] }, true)
339
+ dest = @core.instances_hash({ Name: options['dest'] }, true)
340
+ @ec2.create_tags(resources: dest.map { |instance| instance.instance_id }, tags: source.first.tags)
341
+ @ec2.create_tags(resources: dest.map { |instance| instance.instance_id }, tags: [{ key: 'Name', value: options['dest'] }])
342
+ end
343
+
344
+ desc 'set_tag', 'set tag'
345
+ option :name, aliases: '-n', type: :string, required: true, desc: 'name tag'
346
+ option :tag, aliases: '-t', type: :hash, required: true, desc: 'name tag'
347
+ def set_tag
348
+ instances = @core.instances_hash({ Name: options['name'] }, true)
349
+ tags = @core.format_tag(options['tag'])
350
+ @ec2.create_tags(resources: instances.map { |instance| instance.instance_id }, tags: tags)
351
+ end
352
+
353
+ desc 'search_images', 'search images'
354
+ option :name, aliases: '-n', type: :string, default: '', required: true, desc: 'name tag'
355
+ def search_images(name = options['name'])
356
+ puts_json @core.images(name)
357
+ end
358
+
359
+ desc 'aggregate', 'say hello to NAME'
360
+ option :condition, aliases: '-c', type: :hash, default: {}, desc: 'grouping key'
361
+ option :key, aliases: '-k', type: :array, required: true, desc: 'grouping key'
362
+ option :running_only, aliases: '--ro', type: :boolean, default: true, desc: 'grouping key'
363
+ def aggregate
364
+ list = @core.instances_hash(options['condition'], options['running_only']).map do |_instance|
365
+ options['key'].map do |key|
366
+ eval("instance.#{key} ")
367
+ end.join('_')
368
+ end
369
+ puts @core.group_count(list).to_json
370
+ end
371
+
372
+ desc 'reboot', 'reboot instance'
373
+ option :name, aliases: '-n', type: :string, default: '', required: true, desc: 'name tag'
374
+ def reboot
375
+ @core.instances_hash({ Name: options['name'] }, true).each do |instance|
376
+ @ec2.reboot_instances(instance_ids: [instance.instance_id])
377
+ sleep 5
378
+ @ec2.wait_until(:instance_running, instance_ids: [instance.instance_id])
379
+ end
380
+ end
381
+
382
+ desc 'stop_start', 'stop after start instance'
383
+ option :names, aliases: '-n', type: :array, default: [], required: true, desc: 'name tag'
384
+ def stop_start
385
+ options['names'].each do |name|
386
+ @core.instances_hash({ Name: name }, true).each do |instance|
387
+ instance.stop
388
+ @ec2.wait_until(:instance_stopped, instance_ids: [instance.instance_id])
389
+ instance.start
390
+ @ec2.wait_until(:instance_running, instance_ids: [instance.instance_id])
391
+ puts "#{instance.tags['Name']} restart complete!"
392
+ end
393
+ end
394
+ end
395
+
396
+ desc 'terminate', 'terminate instance'
397
+ option :name, aliases: '-n', type: :string, required: true, desc: 'name tag'
398
+ def terminate
399
+ @core.instances_hash({ Name: options['name'] }, false).each do |instance|
400
+ @core.terminate_instance(instance.instance_id)
401
+ end
402
+ end
403
+
404
+ desc 'terminate', 'terminate instance'
405
+ option :name, aliases: '-n', type: :string, required: true, desc: 'name tag'
406
+ def start
407
+ @core.instances_hash({ Name: options['name'] }, false).each do |instance|
408
+ @core.start_instance(instance.instance_id)
409
+ end
410
+ end
411
+
412
+ desc 'connect elb', 'connect elb'
413
+ option :name, aliases: '-n', type: :string, default: '', required: true, desc: 'name tag'
414
+ option :load_balancer_name, aliases: '-l', type: :string, default: '', required: true, desc: 'name tag'
415
+ def connect_elb(_name = options['name'])
416
+ @core.instances_hash({ Name: options['name'] }, true).each do |instance|
417
+ option = { load_balancer_name: options['load_balancer_name'], instances: [instance_id: instance.instance_id] }
418
+ @elb.deregister_instances_from_load_balancer(option)
419
+ @elb.register_instances_with_load_balancer(option)
420
+ print 'connecting ELB...'
421
+ loop do
422
+ break if 'InService' == @elb.describe_instance_health(option).instance_states.first.state
423
+ sleep 10
424
+ print '.'
425
+ end
426
+ end
427
+ end
428
+
429
+ desc 'elbs', 'show elbs'
430
+ def elbs
431
+ puts_json @elb.describe_load_balancers.data.to_h[:load_balancer_descriptions]
432
+ end
433
+
434
+ desc 'events', 'show events'
435
+ def events
436
+ results = []
437
+ @core.instances_hash({}, true).each do |i|
438
+ status = @ec2.describe_instance_status(instance_ids: [i.instance_id])
439
+ events = status.data[:instance_status_set][0][:events] rescue nil
440
+ next if events.nil? or events.empty?
441
+ events.each do |event|
442
+ next if event[:description] =~ /^\[Completed\]/
443
+ event[:id] = i.id
444
+ event[:name] = i.tags['Name']
445
+ event[:availability_zone] = i.availability_zone
446
+ results << event
447
+ end
448
+ end
449
+ puts_json results
450
+ end
451
+
452
+ desc 'latest_image', 'show elbs'
453
+ option :name, aliases: '-n', type: :string, required: true, desc: 'name tag'
454
+ def latest_image
455
+ puts_json @core.latest_image_with_name(options['name'])
456
+ end
457
+
458
+ desc 'allocate_address', 'allocate address'
459
+ def allocate_address
460
+ response = @ec2.allocate_address(domain: 'vpc')
461
+ puts response
462
+ end
463
+
464
+ private
465
+ def instances(name, _running_only = true)
466
+ @ec2.instances.with_tag('Name', "#{name}")
467
+ end
468
+
469
+ def puts_json(data)
470
+ unless @global_options['fields'].nil?
471
+ data = @core.extract_fields(data, @global_options['fields'])
472
+ end
473
+ puts JSON.pretty_generate(data)
474
+ end
475
+ end
476
+ end
@@ -0,0 +1,176 @@
1
+ require 'aws-sdk'
2
+ require 'ipaddress'
3
+
4
+ module Ec2ex
5
+ class Core
6
+ def initialize
7
+ @ec2 = Aws::EC2::Client.new
8
+ end
9
+
10
+ def extract_fields(data, fields)
11
+ results = []
12
+ data.each do |row|
13
+ row = Hashie::Mash.new(row) if row.class == Hash
14
+ result = {}
15
+ fields.map { |key|
16
+ result[key] = eval("row.#{key}")
17
+ }
18
+ results << result
19
+ end
20
+ results
21
+ end
22
+
23
+ def group_count(list)
24
+ Hash[list.group_by { |e| e }.map { |k, v| [k, v.length] }]
25
+ end
26
+
27
+ def format_tag(tag)
28
+ tags = []
29
+ tag.each do |k, v|
30
+ tags << { key: k, value: v }
31
+ end
32
+ tags
33
+ end
34
+
35
+ def get_tag_hash(tags)
36
+ result = {}
37
+ tags.each {|hash|
38
+ result[hash['key'] || hash[:key]] = hash['value'] || hash[:value]
39
+ }
40
+ Hashie::Mash.new(result)
41
+ end
42
+
43
+ def instances_hash(condition, running_only = true)
44
+ filter = []
45
+ condition.each do |key, value|
46
+ filter << { name: "tag:#{key}", values: ["#{value}"] }
47
+ end
48
+ if running_only
49
+ filter << { name: 'instance-state-name', values: ['running'] }
50
+ else
51
+ filter << { name: 'instance-state-name', values: %w(running stopped) }
52
+ end
53
+ @ec2.describe_instances(
54
+ filters: filter
55
+ ).data.to_h[:reservations].map { |instance| Hashie::Mash.new(instance[:instances].first) }
56
+ end
57
+
58
+ def images(name)
59
+ filter = [{ name: 'is-public', values: ['false'] }]
60
+ filter << { name: 'name', values: ["#{name}"] }
61
+ @ec2.describe_images(
62
+ filters: filter
63
+ ).data.to_h[:images]
64
+ end
65
+
66
+ def create_image_with_instance(instance)
67
+ puts 'image creating...'
68
+ tags = get_tag_hash(instance.tags)
69
+ snapshot = {
70
+ 'created' => Time.now.strftime('%Y%m%d%H%M%S'),
71
+ 'tags' => instance.tags.map(&:to_hash).to_json,
72
+ 'Name' => tags['Name']
73
+ }
74
+ snapshot['security_groups'] = instance.security_groups.map(&:group_id).to_json
75
+ snapshot['private_ip_address'] = instance.private_ip_address
76
+ unless instance.public_ip_address.nil?
77
+ snapshot['public_ip_address'] = instance.public_ip_address
78
+ end
79
+ snapshot['instance_type'] = instance.instance_type
80
+ snapshot['placement'] = instance.placement.to_hash.to_json
81
+ unless instance.iam_instance_profile.nil?
82
+ snapshot['iam_instance_profile'] = instance.iam_instance_profile.arn.split('/').last
83
+ end
84
+
85
+ image_response = @ec2.create_image(
86
+ instance_id: instance.instance_id,
87
+ name: tags['Name'] + ".#{Time.now.strftime('%Y%m%d%H%M%S')}",
88
+ no_reboot: true
89
+ )
90
+ sleep 10
91
+ @ec2.wait_until(:image_available, image_ids: [image_response.image_id]) do |w|
92
+ w.interval = 15
93
+ w.max_attempts = 720
94
+ end
95
+ puts "image create complete! image_id => [#{image_response.image_id}]"
96
+
97
+ ami_tag = format_tag(snapshot)
98
+ @ec2.create_tags(resources: [image_response.image_id], tags: ami_tag)
99
+ image_response.image_id
100
+ end
101
+
102
+ def wait_spot_running(spot_instance_request_id)
103
+ puts 'spot instance creating...'
104
+ instance_id = nil
105
+ while true
106
+ spot_instance_request = @ec2.describe_spot_instance_requests(spot_instance_request_ids: [spot_instance_request_id]).spot_instance_requests.first
107
+ if spot_instance_request.state == 'active'
108
+ instance_id = spot_instance_request.instance_id
109
+ break
110
+ end
111
+ sleep 10
112
+ end
113
+ @ec2.wait_until(:instance_running, instance_ids: [instance_id])
114
+ puts "spot instance create complete! instance_id => [#{instance_id}]"
115
+ instance_id
116
+ end
117
+
118
+ def get_allocation(public_ip_address)
119
+ @ec2.describe_addresses(public_ips: [public_ip_address]).addresses.first
120
+ end
121
+
122
+ def get_subnet(private_ip_address)
123
+ subnets = @ec2.describe_subnets.subnets.select{ |subnet|
124
+ ip = IPAddress(subnet.cidr_block)
125
+ ip.to_a.map { |ipv4| ipv4.address }.include?(private_ip_address)
126
+ }
127
+ subnets.first
128
+ end
129
+
130
+ def stop_instance(instance_id)
131
+ puts 'stopping...'
132
+ @ec2.stop_instances(
133
+ instance_ids: [instance_id],
134
+ force: true
135
+ )
136
+ @ec2.wait_until(:instance_stopped, instance_ids: [instance_id])
137
+ puts "stop instance complete! instance_id => [#{instance_id}]"
138
+ end
139
+
140
+ def start_instance(instance_id)
141
+ puts 'starting...'
142
+ @ec2.start_instances(
143
+ instance_ids: [instance_id]
144
+ )
145
+ @ec2.wait_until(:instance_running, instance_ids: [instance_id])
146
+ puts "start instance complete! instance_id => [#{instance_id}]"
147
+ end
148
+
149
+ def terminate_instance(instance_id)
150
+ puts 'terminating...'
151
+ @ec2.terminate_instances(instance_ids: [instance_id])
152
+ @ec2.wait_until(:instance_terminated, instance_ids: [instance_id])
153
+ puts "terminate instance complete! instance_id => [#{instance_id}]"
154
+ end
155
+
156
+ def associate_address(instance_id, public_ip_address)
157
+ unless public_ip_address.nil?
158
+ allocation_id = get_allocation(public_ip_address).allocation_id
159
+ resp = @ec2.associate_address(instance_id: instance_id, allocation_id: allocation_id)
160
+ end
161
+ end
162
+
163
+ def latest_image_with_name(name)
164
+ filter = [{ name: 'is-public', values: ['false'] }]
165
+ filter << { name: 'tag:Name', values: [name] }
166
+ result = @ec2.describe_images(
167
+ filters: filter
168
+ ).data.to_h[:images]
169
+ result = result.sort_by{ |image|
170
+ tag_hash = get_tag_hash(image[:tags])
171
+ tag_hash['created'].nil? ? '' : tag_hash['created']
172
+ }
173
+ result.empty? ? {} : result.last
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,4 @@
1
+ module Ec2ex
2
+ # ec2ex version
3
+ VERSION = '0.1.0'
4
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'ec2ex'
3
+
4
+ describe Ec2ex::CLI do
5
+ before do
6
+ end
7
+
8
+ it "should stdout sample" do
9
+ output = capture_stdout do
10
+ Ec2ex::CLI.start(['sample'])
11
+ end
12
+ output.should == "This is your new task\n"
13
+ end
14
+
15
+ after do
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+ require 'ec2ex'
3
+
4
+ describe Ec2ex::Core do
5
+ before do
6
+ @core = Core.new
7
+ end
8
+
9
+ it "core not nil" do
10
+ @core.should_not nil
11
+ end
12
+
13
+ after do
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ require 'ec2ex'
3
+
4
+ describe Ec2ex do
5
+ it "should have a VERSION constant" do
6
+ subject.const_get('VERSION').should_not be_empty
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ gem 'rspec', '~> 2.4'
2
+ require 'rspec'
3
+ require 'ec2ex/version'
4
+
5
+ include Ec2ex
6
+
7
+ def capture_stdout
8
+ out = StringIO.new
9
+ $stdout = out
10
+ yield
11
+ return out.string
12
+ ensure
13
+ $stdout = STDOUT
14
+ end
15
+
16
+ def capture_stderr
17
+ out = StringIO.new
18
+ $stderr = out
19
+ yield
20
+ return out.string
21
+ ensure
22
+ $stderr = STDERR
23
+ end
24
+
metadata ADDED
@@ -0,0 +1,236 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ec2ex
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi Toyama
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hashie
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ipaddress
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: aws-sdk
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.11.pre
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.11.pre
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.7.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.7.2
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.10.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.10.1
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 10.3.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 10.3.2
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.4'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.4'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.24.1
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.24.1
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubygems-tasks
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.2'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.2'
167
+ - !ruby/object:Gem::Dependency
168
+ name: yard
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '0.8'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '0.8'
181
+ description: ec2 expand command line
182
+ email: toyama0919@gmail.com
183
+ executables:
184
+ - ec2ex
185
+ extensions: []
186
+ extra_rdoc_files: []
187
+ files:
188
+ - ".document"
189
+ - ".gitignore"
190
+ - ".rspec"
191
+ - ".travis.yml"
192
+ - ".yardopts"
193
+ - ChangeLog.md
194
+ - Gemfile
195
+ - LICENSE.txt
196
+ - README.md
197
+ - Rakefile
198
+ - bin/ec2ex
199
+ - ec2ex.gemspec
200
+ - lib/ec2ex.rb
201
+ - lib/ec2ex/cli.rb
202
+ - lib/ec2ex/core.rb
203
+ - lib/ec2ex/version.rb
204
+ - spec/cli_spec.rb
205
+ - spec/core_spec.rb
206
+ - spec/ec2ex_spec.rb
207
+ - spec/spec_helper.rb
208
+ homepage: https://github.com/toyama0919/ec2ex
209
+ licenses:
210
+ - MIT
211
+ metadata: {}
212
+ post_install_message:
213
+ rdoc_options: []
214
+ require_paths:
215
+ - lib
216
+ required_ruby_version: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ required_rubygems_version: !ruby/object:Gem::Requirement
222
+ requirements:
223
+ - - ">="
224
+ - !ruby/object:Gem::Version
225
+ version: '0'
226
+ requirements: []
227
+ rubyforge_project:
228
+ rubygems_version: 2.4.2
229
+ signing_key:
230
+ specification_version: 4
231
+ summary: ec2 expand command line
232
+ test_files:
233
+ - spec/cli_spec.rb
234
+ - spec/core_spec.rb
235
+ - spec/ec2ex_spec.rb
236
+ - spec/spec_helper.rb