ec2ex 0.1.0

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