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 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in awful.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ #-*- mode: ruby; -*-
3
+
4
+ require 'awful'
5
+ require 'awful/auto_scaling'
6
+
7
+ Awful::AutoScaling.start(ARGV)
data/bin/ec2 ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ #-*- mode: ruby; -*-
3
+
4
+ require 'awful'
5
+ require 'awful/ec2'
6
+
7
+ Awful::Ec2.start(ARGV)
data/bin/elb ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ #-*- mode: ruby; -*-
3
+
4
+ require 'awful'
5
+ require 'awful/elb'
6
+
7
+ Awful::Elb.start(ARGV)
data/bin/lc ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ #-*- mode: ruby; -*-
3
+
4
+ require 'awful'
5
+ require 'awful/launch_config'
6
+
7
+ Awful::LaunchConfig.start(ARGV)
data/bin/subnet ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ #-*- mode: ruby; -*-
3
+
4
+ require 'awful'
5
+ require 'awful/subnet'
6
+
7
+ Awful::Subnet.start(ARGV)
data/bin/vpc ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/ruby
2
+ #-*- mode: ruby; -*-
3
+
4
+ require 'awful'
5
+ require 'awful/vpc'
6
+
7
+ Awful::Vpc.start(ARGV)
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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Awful
2
+ VERSION = "0.0.1"
3
+ 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: []