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.
- 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 [](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
|