rudy 0.2
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/CHANGES.txt +21 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +32 -0
- data/Rakefile +68 -0
- data/bin/rudy +258 -0
- data/lib/drydock.rb +525 -0
- data/lib/rudy.rb +102 -0
- data/lib/rudy/aws.rb +65 -0
- data/lib/rudy/aws/ec2.rb +197 -0
- data/lib/rudy/aws/s3.rb +3 -0
- data/lib/rudy/aws/simpledb.rb +48 -0
- data/lib/rudy/command/addresses.rb +41 -0
- data/lib/rudy/command/base.rb +275 -0
- data/lib/rudy/command/commit.rb +10 -0
- data/lib/rudy/command/disks.rb +61 -0
- data/lib/rudy/command/environment.rb +95 -0
- data/lib/rudy/command/groups.rb +59 -0
- data/lib/rudy/command/images.rb +61 -0
- data/lib/rudy/command/instances.rb +109 -0
- data/lib/rudy/command/metadata.rb +57 -0
- data/lib/rudy/command/release.rb +43 -0
- data/lib/rudy/command/volumes.rb +13 -0
- data/lib/rudy/metadata/disk.rb +142 -0
- data/lib/rudy/metadata/environment.rb +0 -0
- data/lib/rudy/scm/svn.rb +57 -0
- data/lib/rudy/utils.rb +65 -0
- data/lib/storable.rb +268 -0
- data/rudy.gemspec +52 -0
- data/support/rudy-ec2-startup +166 -0
- metadata +87 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
module Command
|
5
|
+
class Images < Rudy::Command::Base
|
6
|
+
|
7
|
+
|
8
|
+
def print_images
|
9
|
+
@ec2.images.list.each do |img|
|
10
|
+
print_image img
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_image
|
15
|
+
|
16
|
+
if @user != "root"
|
17
|
+
puts "This command will be run as root"
|
18
|
+
@user = "root" # We need to be root for this operation!
|
19
|
+
end
|
20
|
+
|
21
|
+
raise "No EC2 .pem keys provided" unless has_pem_keys?
|
22
|
+
raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
|
23
|
+
raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
|
24
|
+
|
25
|
+
machine_list = @ec2.instances.list(machine_group)
|
26
|
+
machine = machine_list.values.first # NOTE: Only one machine per group, for now...
|
27
|
+
|
28
|
+
raise "There's no machine running in #{machine_group}" unless machine
|
29
|
+
raise "The primary machine in #{machine_group} is not in a running state" unless machine[:aws_state] == 'running'
|
30
|
+
|
31
|
+
puts "The new image will be based on #{machine_group}_01"
|
32
|
+
|
33
|
+
unless @account
|
34
|
+
puts "Enter your 12 digit Amazon account number:"
|
35
|
+
@account = gets.chomp
|
36
|
+
end
|
37
|
+
|
38
|
+
unless @image_name
|
39
|
+
puts "Enter the image name:"
|
40
|
+
@image_name = gets.chomp
|
41
|
+
end
|
42
|
+
|
43
|
+
unless @bucket_name
|
44
|
+
puts "Enter the S3 bucket that will store the image:"
|
45
|
+
@bucket_name = gets.chomp
|
46
|
+
end
|
47
|
+
|
48
|
+
scp machine[:dns_name], keypairpath, user, @ec2_cert, "/mnt/"
|
49
|
+
scp machine[:dns_name], keypairpath, user, @ec2_private_key, "/mnt/"
|
50
|
+
|
51
|
+
ssh machine[:dns_name], keypairpath, user, "ec2-bundle-vol -r i386 -p #{@image_name} -k /mnt/pk-*pem -c /mnt/cert*pem -u #{@account}"
|
52
|
+
ssh machine[:dns_name], keypairpath, user, "ec2-upload-bundle -b #{@bucket_name} -m /tmp/#{@image_name}.manifest.xml -a #{@access_key} -s #{@secret_key}"
|
53
|
+
|
54
|
+
# Why did I not use the Ruby API?
|
55
|
+
sh "ec2-register -K #{@ec2_private_key} -C #{@ec2_cert} #{@bucket_name}/#{@image_name}.manifest.xml"
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
module Command
|
5
|
+
class Instances < Rudy::Command::Base
|
6
|
+
|
7
|
+
def print_instances(filter=nil)
|
8
|
+
filter = machine_group if filter.nil? && !@all
|
9
|
+
if instance_id?(filter)
|
10
|
+
inst = @ec2.instances.get(filter)
|
11
|
+
raise "The instance #{filter} does not exist" if inst.empty?
|
12
|
+
list = {inst[:aws_instance_id] => inst}
|
13
|
+
else
|
14
|
+
raise "The security group #{filter} does not exist" if filter && !@ec2.groups.exists?(filter)
|
15
|
+
list = @ec2.instances.list(filter)
|
16
|
+
if list.empty?
|
17
|
+
msg = "There are no instances running"
|
18
|
+
msg << " in the group #{filter}" if filter
|
19
|
+
raise msg
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
list.each_pair do |id, inst|
|
24
|
+
print_instance inst
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy_instances(filter=nil)
|
30
|
+
raise "No instance ID or group name provided" if filter.nil?
|
31
|
+
raise "I will not help you destroy production!" if @environment == "prod" || filter =~ /^prod/
|
32
|
+
if @ec2.groups.exists?(filter)
|
33
|
+
list = @ec2.instances.list(filter)
|
34
|
+
raise "The group #{filter} has no running instances" if list.empty?
|
35
|
+
instance = list.keys.first
|
36
|
+
else
|
37
|
+
instance = filter
|
38
|
+
end
|
39
|
+
puts "Destroying #{instance}!"
|
40
|
+
@ec2.instances.destroy instance
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_instance
|
44
|
+
@image ||= machine_image
|
45
|
+
|
46
|
+
@user = "root"
|
47
|
+
|
48
|
+
rig = @ec2.instances.list(machine_group)
|
49
|
+
raise "There is already an instance running in #{machine_group}" unless rig.empty?
|
50
|
+
raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
|
51
|
+
raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
|
52
|
+
|
53
|
+
user_data = {
|
54
|
+
:dbmaster => "localhost",
|
55
|
+
:role => @role,
|
56
|
+
:env => @environment
|
57
|
+
}
|
58
|
+
|
59
|
+
puts "Starting an instance in #{machine_group}"
|
60
|
+
instances = @ec2.instances.create(@image, machine_group.to_s, File.basename(keypairpath), user_data.to_yaml, @zone)
|
61
|
+
inst = instances.first
|
62
|
+
id, state = inst[:aws_instance_id], inst[:aws_state]
|
63
|
+
|
64
|
+
if @address
|
65
|
+
puts "Associating #{@address} to #{id}"
|
66
|
+
@ec2.addresses.associate(id, @address)
|
67
|
+
end
|
68
|
+
|
69
|
+
print "Waiting for #{id} to become available"
|
70
|
+
|
71
|
+
while @ec2.instances.pending?(id)
|
72
|
+
sleep 2
|
73
|
+
print '.'
|
74
|
+
STDOUT.flush
|
75
|
+
end
|
76
|
+
|
77
|
+
machine = @ec2.instances.get(id)
|
78
|
+
|
79
|
+
puts " It's up!"
|
80
|
+
print "Waiting for SSH daemon at #{machine[:dns_name]}"
|
81
|
+
while !Rudy::Utils.service_available?(machine[:dns_name], 22)
|
82
|
+
print '.'
|
83
|
+
STDOUT.flush
|
84
|
+
end
|
85
|
+
puts " It's up!"
|
86
|
+
|
87
|
+
print "Looking for disk metadata for #{machine[:aws_availability_zone]}... "
|
88
|
+
disks = Rudy::MetaData::Disk.list(@sdb, machine[:aws_availability_zone], @environment, @role, @position)
|
89
|
+
|
90
|
+
if disks.empty?
|
91
|
+
puts "None"
|
92
|
+
else
|
93
|
+
puts "#{disks.size} disk(s)."
|
94
|
+
disks.each do |disk|
|
95
|
+
|
96
|
+
do_dirty_disk_volume_deeds(disk, machine)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
puts
|
101
|
+
ssh machine[:dns_name], keypairpath, user, "df -h" # Display current mounts
|
102
|
+
puts
|
103
|
+
puts "Done!"
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
module Command
|
5
|
+
class Metadata < Rudy::Command::Base
|
6
|
+
|
7
|
+
|
8
|
+
# Update Rudy's metadata for a running instance
|
9
|
+
def update_metadata(instances)
|
10
|
+
attributes = {
|
11
|
+
'role' => role,
|
12
|
+
'environment' => environment,
|
13
|
+
'instances' => instances
|
14
|
+
}
|
15
|
+
puts "Putting #{instances.join(', ')} into #{machine_group}"
|
16
|
+
|
17
|
+
@sdb.store(RUDY_DOMAIN, "group_#{machine_group}", attributes)
|
18
|
+
|
19
|
+
p group_metadata
|
20
|
+
end
|
21
|
+
|
22
|
+
# Print Rudy's metadata to STDOUT
|
23
|
+
def print_metadata(instances=[])
|
24
|
+
p group_metadata
|
25
|
+
#query = "['instances' = '#{instances.first}'] union ['group' = '#{machine}']"
|
26
|
+
#
|
27
|
+
# p @sdb.get_attributes(RUDY_DOMAIN, "instances_#{machine}")
|
28
|
+
# p @sdb.query(RUDY_DOMAIN, query)
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy_metadata
|
32
|
+
@sdb.domains.destroy(RUDY_DOMAIN)
|
33
|
+
end
|
34
|
+
|
35
|
+
def setup
|
36
|
+
puts "Checking environment..."
|
37
|
+
check_environment
|
38
|
+
|
39
|
+
puts "Creating SimpleDB domain called #{RUDY_DOMAIN}"
|
40
|
+
@sdb.domains.create(RUDY_DOMAIN)
|
41
|
+
end
|
42
|
+
|
43
|
+
def info
|
44
|
+
domains = @sdb.domains.list[:domains]
|
45
|
+
puts "Domains: #{domains.join(", ")}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_environment
|
49
|
+
raise "No Amazon keys provided!" unless has_keys?
|
50
|
+
raise "No SSH keypairs provided!" unless has_keypairs?
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
module Command
|
5
|
+
class Release < Rudy::Command::Base
|
6
|
+
|
7
|
+
|
8
|
+
def create_release
|
9
|
+
|
10
|
+
if @user != "root"
|
11
|
+
#puts "This command will be run as root"
|
12
|
+
@user = "root" # We need to be root for this operation!
|
13
|
+
end
|
14
|
+
|
15
|
+
raise "No EC2 .pem keys provided" unless has_pem_keys?
|
16
|
+
raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
|
17
|
+
raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
|
18
|
+
|
19
|
+
raise "The security group #{filter} does not exist" if machine_group && !@ec2.groups.exists?(machine_group)
|
20
|
+
list = @ec2.instances.list(machine_group)
|
21
|
+
raise "Please start an instance in #{machine_group} before releasing!" if list.empty?
|
22
|
+
puts "Creating release from: #{Dir.pwd}"
|
23
|
+
|
24
|
+
tag = @scm.create_release
|
25
|
+
|
26
|
+
machine = list.values.first # NOTE: we're assuming there's only one machine
|
27
|
+
|
28
|
+
#basename = File.basename(@rscripts[keypairname])
|
29
|
+
#scp machine[:dns_name], keypairpath, user, @rscripts[keypairname], "/mnt/"
|
30
|
+
#ssh machine[:dns_name], keypairpath, user, "chmod 755 /mnt/#{basename} && /mnt/#{basename} #{tag}"
|
31
|
+
|
32
|
+
@user = "rudy"
|
33
|
+
basename = File.basename(@rscripts[keypairname])
|
34
|
+
|
35
|
+
scp machine[:dns_name], keypairpath, user, @rscripts[keypairname], "~/"
|
36
|
+
ssh machine[:dns_name], keypairpath, user, "chmod 755 ~/#{basename} && ~/#{basename} test #{@access_key} #{@secret_key} r1ll1r1ll1"
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
class MetaData
|
5
|
+
attr_accessor :sdb
|
6
|
+
|
7
|
+
def initalize(sdb)
|
8
|
+
@sdb = sdb
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
class MetaData
|
13
|
+
class Disk < Storable
|
14
|
+
|
15
|
+
field :awsid
|
16
|
+
|
17
|
+
field :environment
|
18
|
+
field :role
|
19
|
+
field :path
|
20
|
+
field :position
|
21
|
+
|
22
|
+
field :zone
|
23
|
+
field :region
|
24
|
+
field :device
|
25
|
+
field :backups => Array
|
26
|
+
field :size
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@device = "/dev/sdh"
|
30
|
+
@zone = DEFAULT_ZONE
|
31
|
+
@region = DEFAULT_REGION
|
32
|
+
@backups = []
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def name
|
37
|
+
Disk.generate_name(@zone, @environment, @role, @position, @path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid?
|
41
|
+
@zone && @environment && @role && @position && @path
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_query(more=[], remove=[])
|
45
|
+
criteria = [:zone, :environment, :role, :position, :path, *more]
|
46
|
+
criteria -= [*remove].flatten
|
47
|
+
query = []
|
48
|
+
criteria.each do |n|
|
49
|
+
query << "['#{n}' = '#{self.send(n.to_sym)}']"
|
50
|
+
end
|
51
|
+
query.join(" intersection ")
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
str = ""
|
56
|
+
field_names.each do |key|
|
57
|
+
str << sprintf(" %22s: %s#{$/}", key, self.send(key.to_sym))
|
58
|
+
end
|
59
|
+
str
|
60
|
+
end
|
61
|
+
|
62
|
+
def Disk.generate_name(zon, env, rol, pos, pat, sep=File::SEPARATOR)
|
63
|
+
pos = pos.to_s.rjust 2, '0'
|
64
|
+
dirs = pat.split sep if pat
|
65
|
+
dirs.shift while dirs && (dirs[0].nil? || dirs[0].empty?)
|
66
|
+
["disk", zon, env, rol, pos, *dirs].join(RUDY_DELIM)
|
67
|
+
end
|
68
|
+
|
69
|
+
def Disk.get(sdb, name)
|
70
|
+
disk = sdb.get_attributes(RUDY_DOMAIN, name)
|
71
|
+
raise "Disk #{name} does not exist!" unless disk && disk.has_key?(:attributes)
|
72
|
+
Rudy::MetaData::Disk.from_hash(disk[:attributes])
|
73
|
+
end
|
74
|
+
|
75
|
+
def Disk.destroy(sdb, name)
|
76
|
+
disk = Disk.get(sdb, name) # get raises an exception if the disk doesn't exist
|
77
|
+
sdb.destroy(RUDY_DOMAIN, name)
|
78
|
+
true # wtf: RightAws::SimpleDB doesn't tell us whether it succeeds. We'll assume!
|
79
|
+
end
|
80
|
+
|
81
|
+
def Disk.save(sdb, disk)
|
82
|
+
sdb.store(RUDY_DOMAIN, disk.name, disk.to_hash, :replace)
|
83
|
+
end
|
84
|
+
|
85
|
+
def Disk.is_defined?(sdb, disk)
|
86
|
+
# We don't care about the path, but we do care about the device
|
87
|
+
# which is not part of the disk's name.
|
88
|
+
query = disk.to_query(:device, :path)
|
89
|
+
!sdb.query_with_attributes(RUDY_DOMAIN, query).empty?
|
90
|
+
end
|
91
|
+
|
92
|
+
def Disk.update_volume(sdb, ec2, disk, machine)
|
93
|
+
|
94
|
+
disk = Disk.get(sdb, disk) if disk.is_a?(String)
|
95
|
+
raise "You must provide a disk name or obect" unless disk.is_a?(Rudy::MetaData::Disk)
|
96
|
+
|
97
|
+
|
98
|
+
# Make sure the volume is still running
|
99
|
+
disk.awsid = nil if disk.awsid && !ec2.volumes.exists?(disk.awsid)
|
100
|
+
|
101
|
+
|
102
|
+
# Otherwise we need to start one
|
103
|
+
unless disk.awsid
|
104
|
+
puts "No active EBS volume found for #{disk.name}"
|
105
|
+
|
106
|
+
backup = disk.backups.last
|
107
|
+
if backup
|
108
|
+
puts "We'll use the most recent backup..."
|
109
|
+
volume = ec2.volumes.create(disk.zone, disk.size, backup)
|
110
|
+
puts "Attaching #{disk.awsid} to #{id} (#{disk.device})"
|
111
|
+
ec2.volumes.attach(machine[:aws_instance_id], disk.awsid, disk.device)
|
112
|
+
else
|
113
|
+
puts "We'll create one from scratch..."
|
114
|
+
volume = ec2.volumes.create(disk.zone, disk.size, nil)
|
115
|
+
disk.awsid = volume[:aws_id]
|
116
|
+
puts "Saving disk metadata"
|
117
|
+
Disk.save(sdb, disk)
|
118
|
+
end
|
119
|
+
puts ""
|
120
|
+
end
|
121
|
+
|
122
|
+
disk
|
123
|
+
end
|
124
|
+
|
125
|
+
def Disk.list(sdb, zon, env=nil, rol=nil, pos=nil)
|
126
|
+
query = ''
|
127
|
+
query << "['zone' = '#{zon}']" if zon
|
128
|
+
query << " intersection ['environment' = '#{env}']" if env
|
129
|
+
query << " intersection ['role' = '#{rol}']" if rol
|
130
|
+
query << " intersection ['position' = '#{pos}']" if pos
|
131
|
+
|
132
|
+
list = []
|
133
|
+
sdb.query_with_attributes(RUDY_DOMAIN, query).each_pair do |name, hash|
|
134
|
+
list << Rudy::MetaData::Disk.from_hash(hash)
|
135
|
+
end
|
136
|
+
list
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
File without changes
|
data/lib/rudy/scm/svn.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Rudy
|
5
|
+
module SCM
|
6
|
+
class SVN
|
7
|
+
attr_accessor :base_uri
|
8
|
+
|
9
|
+
def initialize(uri)
|
10
|
+
@base_uri = uri
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_release
|
14
|
+
raise "There are local changes. Please revert or check them in!" unless everything_checked_in?
|
15
|
+
raise "Invalid base URI (#{@base_uri}). Check RUDY_SVN_BASE." unless valid_uri?(@base_uri)
|
16
|
+
raise "You must run this command from SVN you want to release from!" unless svn_dir?(Dir.pwd)
|
17
|
+
|
18
|
+
re = `svn info`.match /^URL:\s+(.+)$/
|
19
|
+
release = re[1] if re
|
20
|
+
|
21
|
+
release_tag = "#{@base_uri}/#{generate_release_tag}"
|
22
|
+
|
23
|
+
puts "Creating tag: #{release_tag}"
|
24
|
+
cmd = "svn copy -m 'Another Release by Rudy!' #{release} #{release_tag}"
|
25
|
+
|
26
|
+
`#{cmd}`
|
27
|
+
|
28
|
+
release_tag
|
29
|
+
end
|
30
|
+
|
31
|
+
def generate_release_tag
|
32
|
+
now = Time.now
|
33
|
+
mon = now.mon.to_s.rjust(2, '0')
|
34
|
+
day = now.day.to_s.rjust(2, '0')
|
35
|
+
rev = "r01"
|
36
|
+
criteria = ['rudy', now.year, mon, day, rev]
|
37
|
+
tag = criteria.join(RUDY_DELIM)
|
38
|
+
# Keep incrementing the revision number until we find the next one.
|
39
|
+
tag.succ! while (valid_uri?("#{@base_uri}/#{tag}"))
|
40
|
+
tag
|
41
|
+
end
|
42
|
+
|
43
|
+
def svn_dir?(path)
|
44
|
+
(File.exists?(File.join(path, '.svn')))
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid_uri?(uri)
|
48
|
+
ret = `svn info #{uri} 2>1&` || '' # Valid SVN URIs will return some info
|
49
|
+
(ret =~ /Repository UUID/) ? true : false
|
50
|
+
end
|
51
|
+
|
52
|
+
def everything_checked_in?
|
53
|
+
`svn diff . 2>1&` == '' # svn diff should return nothing
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|