ec2ex 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/.travis.yml +16 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +45 -0
- data/Rakefile +40 -0
- data/bin/ec2ex +6 -0
- data/ec2ex.gemspec +33 -0
- data/lib/ec2ex.rb +3 -0
- data/lib/ec2ex/cli.rb +476 -0
- data/lib/ec2ex/core.rb +176 -0
- data/lib/ec2ex/version.rb +4 -0
- data/spec/cli_spec.rb +17 -0
- data/spec/core_spec.rb +15 -0
- data/spec/ec2ex_spec.rb +8 -0
- data/spec/spec_helper.rb +24 -0
- metadata +236 -0
checksums.yaml
ADDED
@@ -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
|
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title "ec2ex Documentation" --protected
|
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/bin/ec2ex
ADDED
data/ec2ex.gemspec
ADDED
@@ -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
|
data/lib/ec2ex.rb
ADDED
data/lib/ec2ex/cli.rb
ADDED
@@ -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
|
data/lib/ec2ex/core.rb
ADDED
@@ -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
|
data/spec/cli_spec.rb
ADDED
@@ -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
|
data/spec/core_spec.rb
ADDED
data/spec/ec2ex_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -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
|