awful 0.0.1
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/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/awful.gemspec +26 -0
- data/bin/asg +7 -0
- data/bin/ec2 +7 -0
- data/bin/elb +7 -0
- data/bin/lc +7 -0
- data/bin/subnet +7 -0
- data/bin/vpc +7 -0
- data/lib/awful.rb +87 -0
- data/lib/awful/auto_scaling.rb +135 -0
- data/lib/awful/ec2.rb +137 -0
- data/lib/awful/elb.rb +86 -0
- data/lib/awful/launch_config.rb +49 -0
- data/lib/awful/subnet.rb +32 -0
- data/lib/awful/version.rb +3 -0
- data/lib/awful/vpc.rb +32 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7bedcb9861ea2075c3ea043bd651ebf56edd2668
|
4
|
+
data.tar.gz: aa9e8729e5772fef666e9334ad6405bf8977c816
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b9af606fdddd26cb7455a485690da1e62b7bc02efdc5d2d2ee494c092957cfdd47b8adb5cae71e98188a0d3f16ecedb26fd44420734de7b4bc128f2fe4e0eec3
|
7
|
+
data.tar.gz: 6c6218d4692d44c435aad729be0346d166e6421cc4cf9f6d2ec686b6866179b6f11fbf4ccb2315c391db7740480b545533ddf78155bcf7aff5abc53b36f68ee3
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Awful
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'awful'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install awful
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TODO: Write usage instructions here
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it ( https://github.com/[my-github-username]/awful/fork )
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/awful.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'awful/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "awful"
|
8
|
+
spec.version = Awful::VERSION
|
9
|
+
spec.authors = ["Ric Lister"]
|
10
|
+
spec.email = ["rlister+gh@gmail.com"]
|
11
|
+
spec.summary = %q{Simple AWS command-line tool.}
|
12
|
+
spec.description = %q{AWS cmdline and yaml loader.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
|
24
|
+
spec.add_dependency('aws-sdk', '~> 2')
|
25
|
+
spec.add_dependency('thor')
|
26
|
+
end
|
data/bin/asg
ADDED
data/bin/ec2
ADDED
data/bin/elb
ADDED
data/bin/lc
ADDED
data/bin/subnet
ADDED
data/bin/vpc
ADDED
data/lib/awful.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require "awful/version"
|
2
|
+
|
3
|
+
require 'aws-sdk'
|
4
|
+
require 'thor'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module Awful
|
8
|
+
|
9
|
+
def ec2
|
10
|
+
@ec2 ||= Aws::EC2::Client.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def autoscaling
|
14
|
+
@autoscaling ||= Aws::AutoScaling::Client.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def elb
|
18
|
+
@elb ||= Aws::ElasticLoadBalancing::Client.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def symbolize_keys(thing)
|
22
|
+
if thing.is_a?(Hash)
|
23
|
+
Hash[ thing.map { |k,v| [ k.to_sym, symbolize_keys(v) ] } ]
|
24
|
+
elsif thing.respond_to?(:map)
|
25
|
+
thing.map { |v| symbolize_keys(v) }
|
26
|
+
else
|
27
|
+
thing
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def stringify_keys(thing)
|
32
|
+
if thing.is_a?(Hash)
|
33
|
+
Hash[ thing.map { |k,v| [ k.to_s, stringify_keys(v) ] } ]
|
34
|
+
elsif thing.respond_to?(:map)
|
35
|
+
thing.map { |v| stringify_keys(v) }
|
36
|
+
else
|
37
|
+
thing
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_cfg(options = {})
|
42
|
+
cfg = $stdin.tty? ? {} : symbolize_keys(YAML.load($stdin.read))
|
43
|
+
cfg.merge(symbolize_keys(options.reject{ |_,v| v.nil? }))
|
44
|
+
end
|
45
|
+
|
46
|
+
def only_keys_matching(hash, keylist)
|
47
|
+
hash.select do |key,_|
|
48
|
+
keylist.include?(key)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def remove_empty_strings(hash)
|
53
|
+
hash.reject do |_,value|
|
54
|
+
value.respond_to?(:empty?) and value.empty?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def tag_name(thing)
|
59
|
+
tn = thing.tags.find { |tag| tag.key == 'Name' }
|
60
|
+
tn && tn.value
|
61
|
+
end
|
62
|
+
|
63
|
+
## return id for instance by name
|
64
|
+
def find_instance(name)
|
65
|
+
if name .nil?
|
66
|
+
nil?
|
67
|
+
elsif name.match(/^i-[\d[a-f]]{8}$/)
|
68
|
+
name
|
69
|
+
else
|
70
|
+
ec2.describe_instances.map(&:reservations).flatten.map(&:instances).flatten.find do |instance|
|
71
|
+
tag_name(instance) == name
|
72
|
+
end.instance_id
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
## return id for subnet by name
|
77
|
+
def find_subnet(name)
|
78
|
+
if name.match(/^subnet-[\d[a-f]]{8}$/)
|
79
|
+
name
|
80
|
+
else
|
81
|
+
ec2.describe_subnets.map(&:subnets).flatten.find do |subnet|
|
82
|
+
tag_name(subnet) == name
|
83
|
+
end.subnet_id
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Awful
|
2
|
+
|
3
|
+
class AutoScaling < Thor
|
4
|
+
include Awful
|
5
|
+
|
6
|
+
desc 'ls [PATTERN]', 'list autoscaling groups with name matching PATTERN'
|
7
|
+
method_option :long, aliases: '-l', default: false, desc: 'Long listing'
|
8
|
+
def ls(name = /./)
|
9
|
+
fields = if options[:long]
|
10
|
+
->(a) { [
|
11
|
+
a.auto_scaling_group_name,
|
12
|
+
a.launch_configuration_name,
|
13
|
+
"#{a.instances.length}/#{a.desired_capacity}",
|
14
|
+
"#{a.min_size}-#{a.max_size}",
|
15
|
+
a.availability_zones.sort.join(','),
|
16
|
+
a.created_time
|
17
|
+
] }
|
18
|
+
else
|
19
|
+
->(a) { [ a.auto_scaling_group_name ] }
|
20
|
+
end
|
21
|
+
|
22
|
+
autoscaling.describe_auto_scaling_groups.map(&:auto_scaling_groups).flatten.select do |asg|
|
23
|
+
asg.auto_scaling_group_name.match(name)
|
24
|
+
end.map do |asg|
|
25
|
+
fields.call(asg)
|
26
|
+
end.tap do |list|
|
27
|
+
print_table list
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'delete NAME', 'delete autoscaling group'
|
32
|
+
def delete(name)
|
33
|
+
if yes? "Really delete auto-scaling group #{name}?", :yellow
|
34
|
+
autoscaling.delete_auto_scaling_group(auto_scaling_group_name: name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'instances', 'list instance IDs for instances in groups matching NAME'
|
39
|
+
method_option :long, aliases: '-l', default: false, desc: 'Long listing'
|
40
|
+
def instances(name)
|
41
|
+
fields = options[:long] ? %i[instance_id auto_scaling_group_name availability_zone lifecycle_state health_status launch_configuration_name] : %i[instance_id]
|
42
|
+
|
43
|
+
autoscaling.describe_auto_scaling_instances.map(&:auto_scaling_instances).flatten.select do |instance|
|
44
|
+
instance.auto_scaling_group_name.match(name)
|
45
|
+
end.map do |instance|
|
46
|
+
fields.map { |field| instance.send(field) }
|
47
|
+
end.tap do |list|
|
48
|
+
print_table list
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'ips NAME', 'list IPs for instances in groups matching NAME'
|
54
|
+
method_option :long, aliases: '-l', default: false, desc: 'Long listing'
|
55
|
+
def ips(name)
|
56
|
+
fields = options[:long] ? %i[public_ip_address private_ip_address instance_id image_id instance_type launch_time] : %i[ public_ip_address ]
|
57
|
+
|
58
|
+
instance_ids = autoscaling.describe_auto_scaling_instances.map(&:auto_scaling_instances).flatten.select do |instance|
|
59
|
+
instance.auto_scaling_group_name.match(name)
|
60
|
+
end.map(&:instance_id)
|
61
|
+
|
62
|
+
ec2 = Aws::EC2::Client.new
|
63
|
+
ec2.describe_instances(instance_ids: instance_ids).map(&:reservations).flatten.map(&:instances).flatten.map do |instance|
|
64
|
+
fields.map { |field| instance.send(field) }
|
65
|
+
end.tap do |list|
|
66
|
+
print_table list
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'dump NAME', 'dump existing autoscaling group as yaml'
|
71
|
+
def dump(name)
|
72
|
+
asg = autoscaling.describe_auto_scaling_groups(auto_scaling_group_names: Array(name)).map(&:auto_scaling_groups).flatten.first.to_hash
|
73
|
+
puts YAML.dump(stringify_keys(asg))
|
74
|
+
end
|
75
|
+
|
76
|
+
desc 'create NAME', 'create a new auto-scaling group'
|
77
|
+
def create(name)
|
78
|
+
opt = load_cfg
|
79
|
+
whitelist = %i[auto_scaling_group_name launch_configuration_name instance_id min_size max_size desired_capacity default_cooldown availability_zones
|
80
|
+
load_balancer_names health_check_type health_check_grace_period placement_group vpc_zone_identifier termination_policies tags ]
|
81
|
+
|
82
|
+
opt[:auto_scaling_group_name] = name
|
83
|
+
opt = remove_empty_strings(opt)
|
84
|
+
opt = only_keys_matching(opt, whitelist)
|
85
|
+
|
86
|
+
## scrub aws-provided keys from tags
|
87
|
+
opt[:tags] = opt.has_key?(:tags) ? opt[:tags].map { |tag| only_keys_matching(tag, %i[key value propagate_at_launch]) } : []
|
88
|
+
|
89
|
+
autoscaling.create_auto_scaling_group(opt)
|
90
|
+
end
|
91
|
+
|
92
|
+
desc 'update NAME', 'update existing auto-scaling group'
|
93
|
+
method_option :desired_capacity, aliases: '-d', default: nil, desc: 'Set desired capacity'
|
94
|
+
method_option :min_size, aliases: '-m', default: nil, desc: 'Set minimum capacity'
|
95
|
+
method_option :max_size, aliases: '-M', default: nil, desc: 'Set maximum capacity'
|
96
|
+
def update(name)
|
97
|
+
opt = load_cfg(options)
|
98
|
+
whitelist = %i[auto_scaling_group_name launch_configuration_name min_size max_size desired_capacity default_cooldown availability_zones
|
99
|
+
health_check_type health_check_grace_period placement_group vpc_zone_identifier termination_policies ]
|
100
|
+
|
101
|
+
## cleanup the group options
|
102
|
+
opt[:auto_scaling_group_name] = name
|
103
|
+
opt = remove_empty_strings(opt)
|
104
|
+
|
105
|
+
## update the group
|
106
|
+
autoscaling.update_auto_scaling_group(only_keys_matching(opt, whitelist))
|
107
|
+
|
108
|
+
## update any tags
|
109
|
+
if opt[:tags]
|
110
|
+
tags = opt[:tags].map { |tag| tag.merge(resource_id: name, resource_type: 'auto-scaling-group') }
|
111
|
+
autoscaling.create_or_update_tags(tags: tags)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
desc 'terminate NAME', 'terminate instances in group NAME'
|
116
|
+
method_option :decrement, aliases: '-d', default: false, desc: 'Decrement desired capacity for each terminated instance'
|
117
|
+
def terminate(name, num = 1)
|
118
|
+
instance_ids = autoscaling.describe_auto_scaling_instances.map(&:auto_scaling_instances).flatten.select do |instance|
|
119
|
+
instance.auto_scaling_group_name == name
|
120
|
+
end.map(&:instance_id)
|
121
|
+
|
122
|
+
instances = ec2.describe_instances(instance_ids: instance_ids).map(&:reservations).flatten.map(&:instances).flatten.sort_by(&:launch_time)
|
123
|
+
instances.first(num.to_i).map(&:instance_id).tap do |ids|
|
124
|
+
if yes? "Really terminate #{num} instances: #{ids.join(',')}?", :yellow
|
125
|
+
ids.each do |id|
|
126
|
+
autoscaling.terminate_instance_in_auto_scaling_group(instance_id: id, should_decrement_desired_capacity: options[:decrement] && true)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
data/lib/awful/ec2.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Awful
|
4
|
+
|
5
|
+
class Ec2 < Thor
|
6
|
+
include Awful
|
7
|
+
|
8
|
+
desc 'ls [PATTERN]', 'list EC2 instances [with id or tags matching PATTERN]'
|
9
|
+
method_option :long, aliases: '-l', default: false, desc: 'Long listing'
|
10
|
+
def ls(name = /./)
|
11
|
+
fields = options[:long] ?
|
12
|
+
->(i) { [ tag_name(i) || '-', i.instance_id, i.instance_type, i.virtualization_type, i.placement.availability_zone, i.state.name,
|
13
|
+
i.security_groups.map(&:group_name).join(','), i.private_ip_address, i.public_ip_address ] } :
|
14
|
+
->(i) { [ tag_name(i) || i.instance_id ] }
|
15
|
+
|
16
|
+
ec2.describe_instances.map(&:reservations).flatten.map(&:instances).flatten.select do |instance|
|
17
|
+
instance.instance_id.match(name) or instance.tags.any? { |tag| tag.value.match(name) }
|
18
|
+
end.map do |instance|
|
19
|
+
fields.call(instance)
|
20
|
+
end.tap do |list|
|
21
|
+
print_table list.sort
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'dump NAME', 'dump EC2 instance with id or tag NAME as yaml'
|
26
|
+
def dump(name)
|
27
|
+
ec2.describe_instances.map(&:reservations).flatten.map(&:instances).flatten.find do |instance|
|
28
|
+
instance.instance_id == name or tag_name(instance) == name
|
29
|
+
end.tap do |instance|
|
30
|
+
puts YAML.dump(stringify_keys(instance.to_hash))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'create NAME', 'run new EC2 instance'
|
35
|
+
method_option :subnet, :aliases => '-s', :default => nil, :desc => 'VPC subnet to use; default nil (classic)'
|
36
|
+
method_option :public_ip, :aliases => '-p', :default => true, :desc => 'Assign public IP to VPC instances'
|
37
|
+
method_option :elastic_ip, :aliases => '-e', :default => true, :desc => 'Assign new elastic IP to instances'
|
38
|
+
def create(name)
|
39
|
+
opt = load_cfg.merge(symbolize_keys(options))
|
40
|
+
whitelist = %i[image_id min_count max_count key_name security_group_ids user_data instance_type kernel_id
|
41
|
+
ramdisk_id monitoring subnet_id disable_api_termination instance_initiated_shutdown_behavior
|
42
|
+
additional_info iam_instance_profile ebs_optimized network_interfaces]
|
43
|
+
|
44
|
+
opt[:min_count] ||= 1
|
45
|
+
opt[:max_count] ||= 1
|
46
|
+
opt[:monitoring] = {enabled: opt.fetch(:monitoring, {}).fetch(:state, '') == 'enabled'}
|
47
|
+
|
48
|
+
## set subnet from human-readable name, either for network interface, or instance-level
|
49
|
+
if opt[:subnet]
|
50
|
+
subnet = find_subnet(opt[:subnet])
|
51
|
+
opt[:network_interfaces] ? (opt[:network_interfaces][0][:subnet_id] = subnet) : (opt[:subnet_id] = subnet)
|
52
|
+
end
|
53
|
+
|
54
|
+
(opt[:tags] = opt.fetch(:tags, [])).find_index { |t| t[:key] == 'Name' }.tap do |index|
|
55
|
+
opt[:tags][index || 0] = {key: 'Name', value: name}
|
56
|
+
end
|
57
|
+
|
58
|
+
## TODO: block_device_mappings
|
59
|
+
## TODO: placement
|
60
|
+
|
61
|
+
opt[:security_group_ids] = opt.fetch(:security_groups, []).map { |sg| sg[:group_id] }
|
62
|
+
opt[:user_data] = Base64.strict_encode64(opt[:user_data]) if opt[:user_data]
|
63
|
+
|
64
|
+
# scrub unwanted fields from a copied instance dump
|
65
|
+
opt = remove_empty_strings(opt)
|
66
|
+
|
67
|
+
## start instance
|
68
|
+
response = ec2.run_instances(only_keys_matching(opt, whitelist))
|
69
|
+
ids = response.instances.map(&:instance_id)
|
70
|
+
ec2.create_tags(resources: ids, tags: opt[:tags]) # tag instances
|
71
|
+
puts ids # report new instance ids
|
72
|
+
|
73
|
+
## wait for instance to enter running state
|
74
|
+
puts 'running instance ...'
|
75
|
+
ec2.wait_until(:instance_running, instance_ids: ids)
|
76
|
+
|
77
|
+
## allocate and associate new elastic IPs
|
78
|
+
ids.map { |id| associate(id, allocate.allocation_id) } if opt[:elastic_ip]
|
79
|
+
|
80
|
+
## report DNS or IP for instance
|
81
|
+
ec2.describe_instances(instance_ids: ids).map(&:reservations).flatten.map(&:instances).flatten.map do |instance|
|
82
|
+
instance.public_dns_name or instance.public_ip_address or instance.private_ip_address
|
83
|
+
end.tap do |list|
|
84
|
+
puts list
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'allocate', 'allocate a new elastic IP address'
|
89
|
+
def allocate
|
90
|
+
ec2.allocate_address(domain: 'vpc').first.tap do |eip|
|
91
|
+
puts eip.allocation_id, eip.public_ip
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
desc 'associate NAME IP', 'associate a public ip with an instance'
|
96
|
+
def associate(name, eip)
|
97
|
+
ec2.associate_address(instance_id: find_instance(name), allocation_id: eip).map(&:association_id).tap do |id|
|
98
|
+
puts id
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
desc 'addresses', 'list elastic IP addresses'
|
103
|
+
def addresses
|
104
|
+
ec2.describe_addresses.map(&:addresses).flatten.map do |ip|
|
105
|
+
[ ip.allocation_id, ip.public_ip, ip.instance_id, ip.domain ]
|
106
|
+
end.tap do |list|
|
107
|
+
print_table list
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
desc 'dns NAME', 'get public DNS for named instance'
|
112
|
+
def dns(name)
|
113
|
+
ec2.describe_instances.map(&:reservations).flatten.map(&:instances).flatten.find do |instance|
|
114
|
+
instance.instance_id == name or (n = tag_name(instance) and n.match(name))
|
115
|
+
end.public_dns_name.tap do |dns|
|
116
|
+
puts dns
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
desc 'delete NAME', 'terminate a running instance'
|
121
|
+
def delete(name)
|
122
|
+
id =
|
123
|
+
if name.match(/^i-[\d[a-f]]{8}$/)
|
124
|
+
name
|
125
|
+
else
|
126
|
+
ec2.describe_instances.map(&:reservations).flatten.map(&:instances).flatten.find do |instance|
|
127
|
+
tag_name(instance) == name and not %w[terminated shutting-down].include?(instance.state.name)
|
128
|
+
end.instance_id
|
129
|
+
end
|
130
|
+
if yes? "Really terminate instance #{name} (#{id})?", :yellow
|
131
|
+
ec2.terminate_instances(instance_ids: Array(id))
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
data/lib/awful/elb.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module Awful
|
2
|
+
|
3
|
+
class Elb < Thor
|
4
|
+
include Awful
|
5
|
+
|
6
|
+
desc 'ls [PATTERN]', 'list vpcs [with any tags matching PATTERN]'
|
7
|
+
method_option :long, aliases: '-l', default: false, desc: 'Long listing'
|
8
|
+
def ls(name = /./)
|
9
|
+
fields = options[:long] ?
|
10
|
+
->(e) { [e.load_balancer_name, e.instances.length, e.availability_zones.join(','), e.dns_name] } :
|
11
|
+
->(e) { [e.load_balancer_name] }
|
12
|
+
|
13
|
+
elb.describe_load_balancers.map(&:load_balancer_descriptions).flatten.select do |elb|
|
14
|
+
elb.load_balancer_name.match(name)
|
15
|
+
end.map do |elb|
|
16
|
+
fields.call(elb)
|
17
|
+
end.tap do |list|
|
18
|
+
print_table list
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'instances NAME', 'list instances and states for elb NAME'
|
23
|
+
def instances(name)
|
24
|
+
instances = elb.describe_instance_health(load_balancer_name: name).map(&:instance_states).flatten
|
25
|
+
instances_by_id = instances.inject({}) { |hash,instance| hash[instance.instance_id] = instance; hash }
|
26
|
+
|
27
|
+
ec2.describe_instances(instance_ids: instances_by_id.keys).map(&:reservations).flatten.map(&:instances).flatten.map do |instance|
|
28
|
+
health = instances_by_id[instance.instance_id]
|
29
|
+
[ instance.tags.map(&:value).sort.join(','), instance.public_ip_address, health.state, health.reason_code, health.description ]
|
30
|
+
end.tap do |list|
|
31
|
+
print_table list
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'dump NAME', 'dump VPC with id or tag NAME as yaml'
|
36
|
+
def dump(name)
|
37
|
+
lb = elb.describe_load_balancers(load_balancer_names: Array(name)).map(&:load_balancer_descriptions).flatten.first.to_hash
|
38
|
+
puts YAML.dump(stringify_keys(lb))
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'dns NAME', 'get DNS name for load-balancers matching NAME'
|
42
|
+
def dns(name)
|
43
|
+
elb.describe_load_balancers.map(&:load_balancer_descriptions).flatten.select do |elb|
|
44
|
+
elb.load_balancer_name.match(name)
|
45
|
+
end.map(&:dns_name).tap do |dns|
|
46
|
+
puts dns
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc 'create NAME', 'create new load-balancer'
|
51
|
+
def create(name)
|
52
|
+
whitelist = %i[load_balancer_name listeners availability_zones subnets security_groups scheme tags]
|
53
|
+
opt = load_cfg
|
54
|
+
opt[:load_balancer_name] = name
|
55
|
+
opt[:listeners] = opt.fetch(:listener_descriptions, []).map { |l| l[:listener] }
|
56
|
+
opt.delete(:availability_zones) unless opt.fetch(:subnets, []).empty?
|
57
|
+
opt = remove_empty_strings(opt)
|
58
|
+
opt = only_keys_matching(opt, whitelist)
|
59
|
+
elb.create_load_balancer(opt).map(&:dns_name).flatten.tap { |dns| puts dns }
|
60
|
+
health_check(name) if opt[:health_check]
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'health_check NAME', 'set health-check'
|
64
|
+
method_option :target, aliases: '-t', default: nil, desc: 'Health check target'
|
65
|
+
method_option :interval, aliases: '-i', default: nil, desc: 'Check interval'
|
66
|
+
method_option :timeout, aliases: '-o', default: nil, desc: 'Check timeout'
|
67
|
+
method_option :unhealthy_threshold, aliases: '-u', default: nil, desc: 'Unhealthy threshold'
|
68
|
+
method_option :healthy_threshold, aliases: '-h', default: nil, desc: 'Healthy threshold'
|
69
|
+
def health_check(name)
|
70
|
+
opt = load_cfg.merge(options.reject(&:nil?))
|
71
|
+
hc = elb.configure_health_check(load_balancer_name: name, health_check: opt[:health_check])
|
72
|
+
hc.map(&:health_check).flatten.first.tap do |h|
|
73
|
+
print_table h.to_hash
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
desc 'delete NAME', 'delete load-balancer'
|
78
|
+
def delete(name)
|
79
|
+
if yes? "really delete ELB #{name}?", :yellow
|
80
|
+
elb.delete_load_balancer(load_balancer_name: name)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Awful
|
4
|
+
|
5
|
+
class LaunchConfig < Thor
|
6
|
+
include Awful
|
7
|
+
|
8
|
+
desc 'ls [PATTERN]', 'list launch configs with name matching PATTERN'
|
9
|
+
method_option :long, aliases: '-l', default: false, desc: 'Long listing'
|
10
|
+
def ls(name = /./)
|
11
|
+
fields = options[:long] ? %i[launch_configuration_name image_id instance_type created_time] : %i[launch_configuration_name]
|
12
|
+
autoscaling.describe_launch_configurations.map(&:launch_configurations).flatten.select do |lc|
|
13
|
+
lc.launch_configuration_name.match(name)
|
14
|
+
end.map do |lc|
|
15
|
+
fields.map { |field| lc.send(field) }
|
16
|
+
end.tap do |list|
|
17
|
+
print_table list
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'delete NAME', 'delete launch configuration'
|
22
|
+
def delete(name)
|
23
|
+
autoscaling.delete_launch_configuration(launch_configuration_name: name)
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'dump NAME', 'dump existing launch_configuration as yaml'
|
27
|
+
def dump(name)
|
28
|
+
lc = autoscaling.describe_launch_configurations(launch_configuration_names: Array(name)).map(&:launch_configurations).flatten.first.to_hash
|
29
|
+
lc[:user_data] = Base64.decode64(lc[:user_data])
|
30
|
+
puts YAML.dump(stringify_keys(lc))
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'create [NAME]', 'create a new launch configuration'
|
34
|
+
def create(name)
|
35
|
+
opt = load_cfg
|
36
|
+
whitelist = %i[launch_configuration_name image_id key_name security_groups classic_link_vpc_id classic_link_vpc_security_groups user_data
|
37
|
+
instance_id instance_type kernel_id ramdisk_id block_device_mappings instance_monitoring spot_price iam_instance_profile
|
38
|
+
ebs_optimized associate_public_ip_address placement_tenancy]
|
39
|
+
opt[:launch_configuration_name] = "#{name}-#{Time.now.utc.strftime('%Y%m%d%H%M%S')}"
|
40
|
+
opt[:user_data] = Base64.encode64(opt[:user_data]) # encode user data
|
41
|
+
opt = remove_empty_strings(opt)
|
42
|
+
opt = only_keys_matching(opt, whitelist)
|
43
|
+
autoscaling.create_launch_configuration(opt)
|
44
|
+
puts opt[:launch_configuration_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
data/lib/awful/subnet.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Awful
|
2
|
+
|
3
|
+
class Subnet < Thor
|
4
|
+
include Awful
|
5
|
+
|
6
|
+
desc 'ls [PATTERN]', 'list subnets [with any tags matching PATTERN]'
|
7
|
+
method_option :long, aliases: '-l', default: false, desc: 'Long listing'
|
8
|
+
def ls(name = /./)
|
9
|
+
fields = options[:long] ?
|
10
|
+
->(s) { [s.tags.map{ |t| t.value }.join(','), s.subnet_id, s.state, s.cidr_block, s.available_ip_address_count, s.availability_zone] } :
|
11
|
+
->(s) { [s.subnet_id] }
|
12
|
+
ec2.describe_subnets.map(&:subnets).flatten.select do |subnet|
|
13
|
+
subnet.tags.any? { |tag| tag.value.match(name) }
|
14
|
+
end.map do |subnet|
|
15
|
+
fields.call(subnet)
|
16
|
+
end.tap do |list|
|
17
|
+
print_table list.sort
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'dump NAME', 'dump subnet with id or tag NAME as yaml'
|
22
|
+
def dump(name)
|
23
|
+
ec2.describe_subnets.map(&:subnets).flatten.find do |subnet|
|
24
|
+
subnet.subnet_id == name or subnet.tags.any? { |tag| tag.value == name }
|
25
|
+
end.tap do |subnet|
|
26
|
+
puts YAML.dump(stringify_keys(subnet.to_hash))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/lib/awful/vpc.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Awful
|
2
|
+
|
3
|
+
class Vpc < Thor
|
4
|
+
include Awful
|
5
|
+
|
6
|
+
desc 'ls [PATTERN]', 'list vpcs [with any tags matching PATTERN]'
|
7
|
+
method_option :long, aliases: '-l', default: false, desc: 'Long listing'
|
8
|
+
def ls(name = /./)
|
9
|
+
fields = options[:long] ?
|
10
|
+
->(v) { [v.tags.map{ |t| t.value }.join(','), v.vpc_id, v.state, v.cidr_block] } :
|
11
|
+
->(v) { [v.vpc_id] }
|
12
|
+
ec2.describe_vpcs.map(&:vpcs).flatten.select do |vpc|
|
13
|
+
vpc.tags.any? { |tag| tag.value.match(name) }
|
14
|
+
end.map do |vpc|
|
15
|
+
fields.call(vpc)
|
16
|
+
end.tap do |list|
|
17
|
+
print_table list
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'dump NAME', 'dump VPC with id or tag NAME as yaml'
|
22
|
+
def dump(name)
|
23
|
+
ec2.describe_vpcs.map(&:vpcs).flatten.find do |vpc|
|
24
|
+
vpc.vpc_id == name or vpc.tags.any? { |tag| tag.value == name }
|
25
|
+
end.tap do |vpc|
|
26
|
+
puts YAML.dump(stringify_keys(vpc.to_hash))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: awful
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ric Lister
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: aws-sdk
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: thor
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: AWS cmdline and yaml loader.
|
70
|
+
email:
|
71
|
+
- rlister+gh@gmail.com
|
72
|
+
executables:
|
73
|
+
- asg
|
74
|
+
- ec2
|
75
|
+
- elb
|
76
|
+
- lc
|
77
|
+
- subnet
|
78
|
+
- vpc
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files: []
|
81
|
+
files:
|
82
|
+
- ".gitignore"
|
83
|
+
- Gemfile
|
84
|
+
- LICENSE.txt
|
85
|
+
- README.md
|
86
|
+
- Rakefile
|
87
|
+
- awful.gemspec
|
88
|
+
- bin/asg
|
89
|
+
- bin/ec2
|
90
|
+
- bin/elb
|
91
|
+
- bin/lc
|
92
|
+
- bin/subnet
|
93
|
+
- bin/vpc
|
94
|
+
- lib/awful.rb
|
95
|
+
- lib/awful/auto_scaling.rb
|
96
|
+
- lib/awful/ec2.rb
|
97
|
+
- lib/awful/elb.rb
|
98
|
+
- lib/awful/launch_config.rb
|
99
|
+
- lib/awful/subnet.rb
|
100
|
+
- lib/awful/version.rb
|
101
|
+
- lib/awful/vpc.rb
|
102
|
+
homepage: ''
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
metadata: {}
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
requirements: []
|
121
|
+
rubyforge_project:
|
122
|
+
rubygems_version: 2.2.2
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: Simple AWS command-line tool.
|
126
|
+
test_files: []
|