awsome 0.0.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.
- data/bin/awsome +28 -0
- data/lib/awsome.rb +81 -0
- data/lib/awsome/debian.rb +36 -0
- data/lib/awsome/ec2.rb +109 -0
- data/lib/awsome/ec2/instance.rb +76 -0
- data/lib/awsome/elb.rb +66 -0
- data/lib/awsome/elb/load_balancer.rb +30 -0
- data/lib/awsome/executor.rb +66 -0
- data/lib/awsome/instance_requirement.rb +56 -0
- data/lib/awsome/matchmaker.rb +96 -0
- data/lib/awsome/requirements.rb +20 -0
- data/lib/awsome/requirements_options.rb +20 -0
- data/lib/awsome/ssh.rb +27 -0
- metadata +74 -0
data/bin/awsome
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'awsome'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
requirements_file = 'requirements.yml'
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = 'Usage: awsome [options]'
|
9
|
+
opts.on('-r', '--requirements [FILE]', 'Use a requirements file from another location (default is ./requirements.yml)') do |opt|
|
10
|
+
requirements_file = opt
|
11
|
+
end
|
12
|
+
opts.on('-v', '--verbose', 'Print commands as they are executed') do |opt|
|
13
|
+
Awsome.config.verbose = true
|
14
|
+
end
|
15
|
+
opts.on('-d', '--debug', 'Run ec2 commands with --debug') do |opt|
|
16
|
+
Awsome.config.debug = true
|
17
|
+
end
|
18
|
+
opts.on('-s', '--stacks', 'Print out full stack traces when commands are executed') do |opt|
|
19
|
+
Awsome.config.stacks = true
|
20
|
+
end
|
21
|
+
end.parse!
|
22
|
+
|
23
|
+
instances = Awsome::Ec2.describe_instances('instance-state-name' => 'running')
|
24
|
+
requirements = Awsome::Requirements.from_yaml_file(requirements_file)
|
25
|
+
matchmaker = Awsome::Matchmaker.new(instances, requirements)
|
26
|
+
executor = Awsome::Executor.new(matchmaker.matches)
|
27
|
+
|
28
|
+
executor.execute
|
data/lib/awsome.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
module Awsome
|
2
|
+
def self.config
|
3
|
+
@@config ||= Config.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.execute(command, options={})
|
7
|
+
command = command.join(' ') if command.is_a?(Array)
|
8
|
+
if config.verbose
|
9
|
+
puts
|
10
|
+
puts command
|
11
|
+
puts '='*command.length
|
12
|
+
end
|
13
|
+
if options[:system]
|
14
|
+
result = system(command)
|
15
|
+
else
|
16
|
+
result = `#{command}`
|
17
|
+
result = options[:preprocess].call(result) if options[:preprocess]
|
18
|
+
result = map_table(result, options)
|
19
|
+
end
|
20
|
+
ensure
|
21
|
+
if config.verbose
|
22
|
+
case result
|
23
|
+
when String then puts result
|
24
|
+
else ap result
|
25
|
+
end
|
26
|
+
end
|
27
|
+
if config.stacks
|
28
|
+
ap Kernel.caller
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.map_table(table, options)
|
33
|
+
return table unless options[:columns]
|
34
|
+
rows = table.split("\n")
|
35
|
+
rows.select! { |row| row =~ options[:filter] } if options[:filter]
|
36
|
+
rows.collect do |row|
|
37
|
+
properties = {}
|
38
|
+
values = options[:delimiter] ? row.split(options[:delimiter]) : row.split
|
39
|
+
values.each_with_index { |value, index|
|
40
|
+
field = options[:columns][index]
|
41
|
+
properties[field] = value
|
42
|
+
}
|
43
|
+
properties
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.wait_until(opts={}, &block)
|
48
|
+
retries = opts[:retries] || 5
|
49
|
+
interval = opts[:interval] || 5
|
50
|
+
while !yield && retries > 0
|
51
|
+
puts "block returned false (#{retries} more retries)..." if Awsome.config.verbose
|
52
|
+
sleep interval
|
53
|
+
retries -= 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Config
|
58
|
+
attr_accessor :region, :url, :aws_access_key, :aws_secret_key, :connection_timeout,
|
59
|
+
:request_timeout, :verbose, :show_empty_fields, :debug, :show_request, :stacks
|
60
|
+
|
61
|
+
def initialize
|
62
|
+
@region = ENV['REGION']
|
63
|
+
@url = ENV['EC2_URL']
|
64
|
+
@aws_access_key = ENV['AWS_ACCESS_KEY']
|
65
|
+
@aws_secret_key = ENV['AWS_SECRET_KEY']
|
66
|
+
@show_empty_fields = true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'set'
|
72
|
+
require 'awesome_print'
|
73
|
+
require 'awsome/ec2.rb'
|
74
|
+
require 'awsome/elb.rb'
|
75
|
+
require 'awsome/ssh.rb'
|
76
|
+
require 'awsome/debian.rb'
|
77
|
+
require 'awsome/requirements.rb'
|
78
|
+
require 'awsome/executor.rb'
|
79
|
+
require 'awsome/matchmaker.rb'
|
80
|
+
require 'awsome/requirements_options.rb'
|
81
|
+
require 'awsome/instance_requirement.rb'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Awsome
|
2
|
+
module Debian
|
3
|
+
def self.describe_debian_packages(hostname)
|
4
|
+
if Awsome::Ssh.scp("#{hostname}:/home/ubuntu/packages.csv", './packages.csv')
|
5
|
+
File.open('./packages.csv', 'r').read.strip.split(',')
|
6
|
+
else
|
7
|
+
[]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.remove_debian_packages(hostname, *packages)
|
12
|
+
installed = describe_debian_packages(hostname)
|
13
|
+
remaining = installed.to_set - packages.to_set
|
14
|
+
|
15
|
+
return if packages.empty?
|
16
|
+
|
17
|
+
Awsome::Ssh.ssh hostname, "sudo apt-get update"
|
18
|
+
Awsome::Ssh.ssh hostname, "sudo apt-get remove -y --force-yes #{packages.join(' ')}"
|
19
|
+
Awsome::Ssh.ssh hostname, "echo #{remaining.to_a.join(',')} > ~/packages.csv"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.install_debian_packages(hostname, *packages)
|
23
|
+
installed = describe_debian_packages(hostname)
|
24
|
+
remaining = installed.to_set + packages.to_set
|
25
|
+
|
26
|
+
Awsome::Ssh.ssh hostname, "sudo apt-get update"
|
27
|
+
|
28
|
+
if packages.empty?
|
29
|
+
Awsome::Ssh.ssh hostname, "sudo apt-get upgrade"
|
30
|
+
else
|
31
|
+
Awsome::Ssh.ssh hostname, "sudo apt-get install -y --force-yes #{packages.join(' ')}"
|
32
|
+
Awsome::Ssh.ssh hostname, "echo #{remaining.to_a.join(',')} > ~/packages.csv"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/awsome/ec2.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
module Awsome
|
2
|
+
module Ec2
|
3
|
+
def self.config
|
4
|
+
Awsome.config
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.command(*cmd)
|
8
|
+
args = cmd.last.is_a?(Hash) && cmd.pop
|
9
|
+
options = cmd
|
10
|
+
options << "--region #{config.region}"
|
11
|
+
options << "--url #{config.url}"
|
12
|
+
options << "--aws-access-key #{config.aws_access_key}"
|
13
|
+
options << "--aws-secret-key #{config.aws_secret_key}"
|
14
|
+
options << "--connection-timeout #{config.connection_timeout}" if config.connection_timeout
|
15
|
+
options << "--request-timeout #{config.request_timeout}" if config.request_timeout
|
16
|
+
options << "--verbose" if config.verbose
|
17
|
+
options << "--show-empty-fields" if config.show_empty_fields
|
18
|
+
options << "--debug" if config.debug
|
19
|
+
args && args.each { |k,v| options << "--#{k} #{v}" }
|
20
|
+
options.join(' ')
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.map_table(*args)
|
24
|
+
Awsome.map_table(*args)
|
25
|
+
end
|
26
|
+
|
27
|
+
@@run_instance_fields = %w(
|
28
|
+
instance_identifier
|
29
|
+
instance_id
|
30
|
+
ami_id
|
31
|
+
state
|
32
|
+
key
|
33
|
+
ami_launch_index
|
34
|
+
product_code
|
35
|
+
instance_type
|
36
|
+
instance_launch_time
|
37
|
+
availability_zone
|
38
|
+
)
|
39
|
+
|
40
|
+
def self.run_instance(properties)
|
41
|
+
cmd = command('ec2-run-instances', properties['ami_id'],
|
42
|
+
:group => properties['security_group_ids'],
|
43
|
+
:key => properties['key'],
|
44
|
+
'instance-type'.to_sym => properties['instance_type'],
|
45
|
+
'availability-zone'.to_sym => properties['availability_zone']
|
46
|
+
)
|
47
|
+
Awsome::Ec2::Instance.new(Awsome.execute(cmd, columns: @@run_instance_fields, filter: /^INSTANCE/).first)
|
48
|
+
end
|
49
|
+
|
50
|
+
@@describe_instance_fields = %w(
|
51
|
+
reservation_identifier
|
52
|
+
reservation_id
|
53
|
+
aws_account_id
|
54
|
+
security_group_ids
|
55
|
+
instance_identifier
|
56
|
+
instance_id
|
57
|
+
ami_id
|
58
|
+
public_dns_name
|
59
|
+
private_dns_name
|
60
|
+
state
|
61
|
+
key
|
62
|
+
ami_launch_index
|
63
|
+
product_codes
|
64
|
+
instance_type
|
65
|
+
instance_launch_time
|
66
|
+
availability_zone
|
67
|
+
)
|
68
|
+
|
69
|
+
def self.describe_instances(filters={})
|
70
|
+
cmd = [Awsome::Ec2.command('ec2-describe-instances')]
|
71
|
+
cmd += filters.collect { |k,v| "--filter \"#{k}=#{v}\"" }
|
72
|
+
preprocess = Proc.new { |text| text.gsub("\nINSTANCE", " INSTANCE") }
|
73
|
+
properties = Awsome.execute(cmd, columns: @@describe_instance_fields, filter: /^RESERVATION/, preprocess: preprocess)
|
74
|
+
properties.collect { |p| Awsome::Ec2::Instance.new(p) }
|
75
|
+
end
|
76
|
+
|
77
|
+
@@describe_volumes_fields = %w(
|
78
|
+
attachment_identifier
|
79
|
+
volume_id
|
80
|
+
instance_id
|
81
|
+
device
|
82
|
+
state
|
83
|
+
date
|
84
|
+
)
|
85
|
+
|
86
|
+
def self.describe_volumes(filters={})
|
87
|
+
cmd = [Awsome::Ec2.command('ec2-describe-volumes')]
|
88
|
+
cmd += filters.collect { |k,v| "--filter \"#{k}=#{v}\"" }
|
89
|
+
Awsome.execute(cmd, columns: @@describe_volumes_fields, filter: /^ATTACHMENT/)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.detach_volume(volume_id)
|
93
|
+
cmd = Awsome::Ec2.command('ec2-detach-volume', volume_id)
|
94
|
+
Awsome.execute(cmd)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.attach_volume(volume_id, instance_id, device)
|
98
|
+
cmd = Awsome::Ec2.command('ec2-attach-volume', volume_id, instance: instance_id, device: device)
|
99
|
+
Awsome.execute(cmd)
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.terminate_instances(*instance_ids)
|
103
|
+
cmd = Awsome::Ec2.command("ec2-terminate-instances #{instance_ids.join(' ')}")
|
104
|
+
Awsome.execute(cmd)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
require 'awsome/ec2/instance.rb'
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Awsome
|
2
|
+
module Ec2
|
3
|
+
class Instance
|
4
|
+
attr_reader :properties
|
5
|
+
|
6
|
+
def initialize(property_hash)
|
7
|
+
raise 'properties must be a hash' unless property_hash.is_a?(Hash)
|
8
|
+
@properties = property_hash.clone
|
9
|
+
end
|
10
|
+
|
11
|
+
def packages
|
12
|
+
Awsome::Debian.describe_debian_packages(@properties['public_dns_name']).to_set
|
13
|
+
end
|
14
|
+
|
15
|
+
def volumes
|
16
|
+
Awsome::Ec2.describe_volumes('attachment.instance-id' => @properties['instance_id']).collect do |p|
|
17
|
+
p['volume_id']
|
18
|
+
end.to_set
|
19
|
+
end
|
20
|
+
|
21
|
+
def wait_until_running!
|
22
|
+
Awsome.wait_until(interval: 10) do
|
23
|
+
reload!
|
24
|
+
@properties['state'] =~ /^running/
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def wait_for_ssh!
|
29
|
+
Awsome.wait_until(interval: 10) { has_ssh? }
|
30
|
+
end
|
31
|
+
|
32
|
+
def reattach_volumes(*volumes)
|
33
|
+
volumes.each do |info|
|
34
|
+
Awsome::Ec2.detach_volume(info['id'])
|
35
|
+
Awsome::Ec2.attach_volume(info['id'], @properties['instance_id'], info['device'])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def deregister_from_elbs
|
40
|
+
elbs.each { |elb| elb.deregister(@properties['instance_id']) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def register_with_elbs(*load_balancer_names)
|
44
|
+
Awsome::Elb.describe_lbs(*load_balancer_names).each { |elb| elb.register(@properties['instance_id']) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def remove_packages(*packages)
|
48
|
+
Awsome::Debian.remove_debian_packages(@properties['public_dns_name'], *packages)
|
49
|
+
end
|
50
|
+
|
51
|
+
def install_packages(*packages)
|
52
|
+
Awsome::Debian.install_debian_packages(@properties['public_dns_name'], *packages)
|
53
|
+
end
|
54
|
+
|
55
|
+
def terminate
|
56
|
+
Awsome::Ec2.terminate_instances(@properties['instance_id'])
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def reload!
|
61
|
+
instance = Awsome::Ec2.describe_instances('instance-id' => @properties['instance_id']).first
|
62
|
+
raise "instance #{@properties['instance_id']} not found" if instance.nil?
|
63
|
+
@properties = instance.properties.clone
|
64
|
+
end
|
65
|
+
|
66
|
+
def has_ssh?
|
67
|
+
Awsome::Ssh.has_ssh?(@properties['public_dns_name'])
|
68
|
+
end
|
69
|
+
|
70
|
+
def elbs
|
71
|
+
Awsome::Elb.describe_lbs.select { |elb| elb.instances.include?(@properties['instance_id']) }
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/awsome/elb.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Awsome
|
2
|
+
module Elb
|
3
|
+
@@delimiter = " OH YEAH BANANAS "
|
4
|
+
|
5
|
+
def self.config
|
6
|
+
Awsome.config
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.command(*cmd)
|
10
|
+
args = cmd.last.is_a?(Hash) && cmd.pop
|
11
|
+
options = cmd
|
12
|
+
options << "--region #{config.region}"
|
13
|
+
# options << "--url #{config.url}" # explicitly setting the url this way breaks the elb tools
|
14
|
+
options << "--access-key-id #{config.aws_access_key}"
|
15
|
+
options << "--secret-key #{config.aws_secret_key}"
|
16
|
+
options << "--show-long"
|
17
|
+
options << "--delimiter \"#{@@delimiter}\""
|
18
|
+
options << "--show-request" if config.show_request
|
19
|
+
options << "--show-empty-fields" if config.show_empty_fields
|
20
|
+
options << "--connection-timeout #{config.connection_timeout}" if config.connection_timeout
|
21
|
+
args && args.each { |k,v| options << "--#{k} #{v}" }
|
22
|
+
options.join(' ')
|
23
|
+
end
|
24
|
+
|
25
|
+
@@describe_lbs_columns = %w(
|
26
|
+
identifier
|
27
|
+
name
|
28
|
+
dns_name
|
29
|
+
canonical_hosted_zone_name
|
30
|
+
canonical_hosted_zone_name_id
|
31
|
+
health_check
|
32
|
+
availability_zones
|
33
|
+
subnets
|
34
|
+
vpc_id
|
35
|
+
instance_ids
|
36
|
+
listener_descriptions
|
37
|
+
backend_server_descriptions
|
38
|
+
app_cookie_stickiness_policies
|
39
|
+
lb_cookie_stickiness_policies
|
40
|
+
other_policies
|
41
|
+
source_security_group
|
42
|
+
security_groups
|
43
|
+
created_time
|
44
|
+
scheme
|
45
|
+
pagination_marker
|
46
|
+
)
|
47
|
+
|
48
|
+
def self.describe_lbs(*load_balancer_names)
|
49
|
+
cmd = Awsome::Elb.command("elb-describe-lbs #{load_balancer_names.join(' ')}")
|
50
|
+
properties = Awsome.execute(cmd, columns: @@describe_lbs_columns, delimiter: @@delimiter)
|
51
|
+
properties.collect { |p| Awsome::Elb::LoadBalancer.new(p) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.deregister_instance_from_lb(load_balancer_name, instance_id)
|
55
|
+
cmd = Awsome::Elb.command('elb-deregister-instances-from-lb', load_balancer_name, instances: instance_id, delimiter: @@delimiter)
|
56
|
+
Awsome.execute(cmd)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.register_instance_with_lb(load_balancer_name, instance_id)
|
60
|
+
cmd = Awsome::Elb.command('elb-register-instances-with-lb', load_balancer_name, instances: instance_id, delimiter: @@delimiter)
|
61
|
+
Awsome.execute(cmd)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
require 'awsome/elb/load_balancer.rb'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Awsome
|
2
|
+
module Elb
|
3
|
+
class LoadBalancer
|
4
|
+
attr_reader :properties
|
5
|
+
|
6
|
+
def initialize(property_hash)
|
7
|
+
@properties = property_hash.clone
|
8
|
+
end
|
9
|
+
|
10
|
+
def instances
|
11
|
+
@properties['instance_ids'].gsub('"', '').split(',').collect(&:strip).to_set
|
12
|
+
end
|
13
|
+
|
14
|
+
def deregister(instance_id)
|
15
|
+
Awsome::Elb.deregister_instance_from_lb(@properties['name'], instance_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def register(instance_id)
|
19
|
+
Awsome::Elb.register_instance_with_lb(@properties['name'], instance_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def reload!
|
24
|
+
elb = Awsome::Elb.describe_lbs(@properties['name']).first
|
25
|
+
raise "elb #{@properties['name']} not found" if elb.nil?
|
26
|
+
@properties = elb.properties.clone
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Awsome
|
2
|
+
class Executor
|
3
|
+
def initialize(matches)
|
4
|
+
@matches = matches
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute
|
8
|
+
@matches.each { |s, m| execute_match(s, m) }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def execute_match(signature, match)
|
14
|
+
@i_pool = match[:i_pool]
|
15
|
+
@r_pool = match[:r_pool]
|
16
|
+
|
17
|
+
puts "instance pool:"
|
18
|
+
ap @i_pool
|
19
|
+
|
20
|
+
puts "requirement pool:"
|
21
|
+
ap @r_pool
|
22
|
+
|
23
|
+
run
|
24
|
+
wait_for_ssh
|
25
|
+
reattach_volumes
|
26
|
+
deploy
|
27
|
+
terminate
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
@i_pool.each_with_index do |instance, i|
|
32
|
+
@i_pool[i] = Awsome::Ec2.run_instance(@r_pool[i].properties) if instance.nil?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def wait_for_ssh
|
37
|
+
@i_pool.each do |i|
|
38
|
+
i.wait_until_running!
|
39
|
+
i.wait_for_ssh!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def reattach_volumes
|
44
|
+
@i_pool.each_with_index do |instance, i|
|
45
|
+
instance.reattach_volumes(*@r_pool[i].volumes_to_attach(instance))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def deploy
|
50
|
+
@i_pool.each_with_index do |instance, i|
|
51
|
+
instance.deregister_from_elbs
|
52
|
+
instance.remove_packages(*@r_pool[i].packages_to_remove(instance))
|
53
|
+
instance.install_packages(*@r_pool[i].packages_to_install(instance))
|
54
|
+
instance.register_with_elbs(*@r_pool[i].elbs)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def terminate
|
59
|
+
@i_pool.each_with_index do |instance, i|
|
60
|
+
next if i < @r_pool.length
|
61
|
+
instance.deregister_from_elbs
|
62
|
+
instance.terminate
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Awsome
|
2
|
+
class InstanceRequirement
|
3
|
+
|
4
|
+
attr_reader :properties
|
5
|
+
|
6
|
+
def initialize(properties, options)
|
7
|
+
@properties = properties.clone
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def elbs
|
12
|
+
@properties['elbs']
|
13
|
+
end
|
14
|
+
|
15
|
+
def volumes_to_attach(instance)
|
16
|
+
(volumes - volumes_attached_to(instance)).collect { |v| @options.find_volume(v) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def volumes_to_detach(instance)
|
20
|
+
(volumes_attached_to(instance) - volumes).collect { |v| @options.find_volume(v) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def volume_change_count(instance)
|
24
|
+
volumes_to_attach(instance).size + volumes_to_detach(instance).size
|
25
|
+
end
|
26
|
+
|
27
|
+
def packages_to_install(instance)
|
28
|
+
packages - packages_installed_on(instance)
|
29
|
+
end
|
30
|
+
|
31
|
+
def packages_to_remove(instance)
|
32
|
+
packages_installed_on(instance) - packages
|
33
|
+
end
|
34
|
+
|
35
|
+
def package_change_count(instance)
|
36
|
+
packages_to_install(instance).size + packages_to_remove(instance).size
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def packages
|
41
|
+
@properties['packages'].to_set
|
42
|
+
end
|
43
|
+
|
44
|
+
def volumes
|
45
|
+
@options.filter_volume_ids(@properties['volumes'])
|
46
|
+
end
|
47
|
+
|
48
|
+
def packages_installed_on(instance)
|
49
|
+
instance ? instance.packages : Set[]
|
50
|
+
end
|
51
|
+
|
52
|
+
def volumes_attached_to(instance)
|
53
|
+
instance ? @options.filter_volume_ids(instance.volumes) : Set[]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Awsome
|
2
|
+
class Matchmaker
|
3
|
+
def initialize(instances, requirements)
|
4
|
+
@requirement_pools = requirements.instances.reject do |i|
|
5
|
+
requirements.options.except_instance_ids.include?(i.properties['instance_id'])
|
6
|
+
end.group_by { |i| signature_for(i) }
|
7
|
+
|
8
|
+
@instance_pools = instances.reject do |i|
|
9
|
+
requirements.options.except_instance_ids.include?(i.properties['instance_id'])
|
10
|
+
end.group_by { |i| signature_for(i) }
|
11
|
+
|
12
|
+
@signatures = @requirement_pools.keys.to_set + @instance_pools.keys.to_set
|
13
|
+
|
14
|
+
@signatures.each do |s|
|
15
|
+
@requirement_pools[s] ||= []
|
16
|
+
@instance_pools[s] ||= []
|
17
|
+
|
18
|
+
# nil indicates new instance will be brought up
|
19
|
+
shortage = @requirement_pools[s].size - @instance_pools[s].size
|
20
|
+
@instance_pools[s] += [nil]*shortage if shortage > 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@@signature_fields = %w( ami_id key instance_type availability_zone security_group_ids )
|
25
|
+
|
26
|
+
def signature_for(instance)
|
27
|
+
@@signature_fields.collect { |k| instance.properties[k] }.join(',')
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches
|
31
|
+
@signatures.reduce({}) { |memo, s| memo.merge(s => best_match(s)) }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def best_match(signature)
|
37
|
+
i_pool = @instance_pools[signature]
|
38
|
+
r_pool = @requirement_pools[signature]
|
39
|
+
|
40
|
+
best = nil
|
41
|
+
|
42
|
+
permute i_pool do |i_pool_perm|
|
43
|
+
best = score_match(i_pool_perm, r_pool, best)
|
44
|
+
end
|
45
|
+
|
46
|
+
best
|
47
|
+
end
|
48
|
+
|
49
|
+
def score_match(i_pool, r_pool, best)
|
50
|
+
v_delta = 0
|
51
|
+
p_delta = 0
|
52
|
+
|
53
|
+
i_pool.each_with_index do |i, index|
|
54
|
+
r = r_pool[index]
|
55
|
+
if r
|
56
|
+
v_delta += r.volume_change_count(i)
|
57
|
+
p_delta += r.package_change_count(i)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
top_score({
|
62
|
+
v_delta: v_delta,
|
63
|
+
p_delta: p_delta,
|
64
|
+
i_pool: i_pool,
|
65
|
+
r_pool: r_pool
|
66
|
+
}, best)
|
67
|
+
end
|
68
|
+
|
69
|
+
def top_score(*scores)
|
70
|
+
scores.sort_by do |s1, s2|
|
71
|
+
case
|
72
|
+
when s1.nil? && s2.nil? then 0
|
73
|
+
when s2.nil? then -1
|
74
|
+
when s1.nil? then 1
|
75
|
+
when s1[:v_delta] < s2[:v_delta] then -1
|
76
|
+
when s1[:v_delta] > s2[:v_delta] then 1
|
77
|
+
when s1[:p_delta] < s2[:p_delta] then -1
|
78
|
+
when s1[:p_delta] > s2[:p_delta] then 1
|
79
|
+
else 0
|
80
|
+
end
|
81
|
+
end.first
|
82
|
+
end
|
83
|
+
|
84
|
+
def permute(array, permutation=[], &block)
|
85
|
+
if array.empty?
|
86
|
+
yield(permutation.clone)
|
87
|
+
else
|
88
|
+
0.upto(array.length - 1) do |i|
|
89
|
+
permutation << array.slice!(i)
|
90
|
+
permute(array, permutation, &block)
|
91
|
+
array.insert(i, permutation.pop)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Awsome
|
4
|
+
class Requirements
|
5
|
+
attr_reader :options
|
6
|
+
def initialize(requirements_hash)
|
7
|
+
@requirements = requirements_hash.clone
|
8
|
+
@options = Awsome::RequirementsOptions.new(@requirements)
|
9
|
+
end
|
10
|
+
def self.from_yaml_file(filename)
|
11
|
+
new(YAML::load(File.open(filename, 'r').read))
|
12
|
+
end
|
13
|
+
def instances
|
14
|
+
@requirements['instances'].collect do |instance_req|
|
15
|
+
Awsome::InstanceRequirement.new(instance_req, @options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Awsome
|
2
|
+
class RequirementsOptions
|
3
|
+
def initialize(requirements)
|
4
|
+
@options = (requirements['options'] || {}).clone
|
5
|
+
end
|
6
|
+
def only_volume_ids
|
7
|
+
@options['volumes'].collect { |v| v['id'] }.to_set
|
8
|
+
end
|
9
|
+
def except_instance_ids
|
10
|
+
(@options['except_instance_ids'] || []).to_set
|
11
|
+
end
|
12
|
+
def filter_volume_ids(ids)
|
13
|
+
ids = ids.to_set unless ids.is_a?(Set)
|
14
|
+
only_volume_ids.any? ? only_volume_ids & ids : ids
|
15
|
+
end
|
16
|
+
def find_volume(volume_id)
|
17
|
+
@options['volumes'].find { |v| v['id'] == volume_id }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/awsome/ssh.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Awsome
|
2
|
+
module Ssh
|
3
|
+
def self.ensure_ssh_env_vars!
|
4
|
+
raise 'SSH_KEY environment variable not set' if ENV['SSH_KEY'].nil?
|
5
|
+
raise 'SSH_USER environment varibale not set' if ENV['SSH_USER'].nil?
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.ssh(host, *scripts)
|
9
|
+
ensure_ssh_env_vars!
|
10
|
+
Awsome.execute("ssh -i #{ENV['SSH_KEY']} #{ENV['SSH_USER']}@#{host} \"#{scripts.join(' && ')}\"")
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.scp(from, to)
|
14
|
+
ensure_ssh_env_vars!
|
15
|
+
if File.exists?(from)
|
16
|
+
to = "#{ENV['SSH_USER']}@#{to}"
|
17
|
+
else
|
18
|
+
from = "#{ENV['SSH_USER']}@#{from}"
|
19
|
+
end
|
20
|
+
Awsome.execute("scp -i #{ENV['SSH_KEY']} #{from} #{to}", system: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.has_ssh?(host)
|
24
|
+
Awsome.execute("nc -zw 2 #{host} 22 < /dev/null", system: true)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: awsome
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sebastian Goodman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-11 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: awesome_print
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: AWS library targeted specifically for continuous integration.
|
31
|
+
email: sebastian@foodocs.com
|
32
|
+
executables:
|
33
|
+
- awsome
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- lib/awsome.rb
|
38
|
+
- lib/awsome/matchmaker.rb
|
39
|
+
- lib/awsome/elb/load_balancer.rb
|
40
|
+
- lib/awsome/requirements.rb
|
41
|
+
- lib/awsome/ec2.rb
|
42
|
+
- lib/awsome/executor.rb
|
43
|
+
- lib/awsome/ssh.rb
|
44
|
+
- lib/awsome/requirements_options.rb
|
45
|
+
- lib/awsome/elb.rb
|
46
|
+
- lib/awsome/debian.rb
|
47
|
+
- lib/awsome/ec2/instance.rb
|
48
|
+
- lib/awsome/instance_requirement.rb
|
49
|
+
- bin/awsome
|
50
|
+
homepage: http://github.com/0x0539/awsome
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.23
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Intelligently plan automated, incremental deployments.
|
74
|
+
test_files: []
|