awsome 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|