solutious-rudy 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +75 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +36 -0
- data/Rakefile +68 -0
- data/bin/rudy +175 -0
- data/bin/rudy-ec2 +108 -0
- data/lib/aws_sdb.rb +3 -0
- data/lib/aws_sdb/error.rb +42 -0
- data/lib/aws_sdb/service.rb +215 -0
- data/lib/console.rb +385 -0
- data/lib/rudy.rb +210 -0
- data/lib/rudy/aws.rb +68 -0
- data/lib/rudy/aws/ec2.rb +304 -0
- data/lib/rudy/aws/s3.rb +3 -0
- data/lib/rudy/aws/simpledb.rb +53 -0
- data/lib/rudy/command/addresses.rb +46 -0
- data/lib/rudy/command/backups.rb +175 -0
- data/lib/rudy/command/base.rb +839 -0
- data/lib/rudy/command/config.rb +77 -0
- data/lib/rudy/command/deploy.rb +12 -0
- data/lib/rudy/command/disks.rb +213 -0
- data/lib/rudy/command/environment.rb +74 -0
- data/lib/rudy/command/groups.rb +61 -0
- data/lib/rudy/command/images.rb +99 -0
- data/lib/rudy/command/instances.rb +85 -0
- data/lib/rudy/command/machines.rb +170 -0
- data/lib/rudy/command/metadata.rb +41 -0
- data/lib/rudy/command/release.rb +174 -0
- data/lib/rudy/command/volumes.rb +66 -0
- data/lib/rudy/config.rb +93 -0
- data/lib/rudy/metadata.rb +26 -0
- data/lib/rudy/metadata/backup.rb +160 -0
- data/lib/rudy/metadata/disk.rb +138 -0
- data/lib/rudy/scm/svn.rb +68 -0
- data/lib/rudy/utils.rb +64 -0
- data/lib/storable.rb +280 -0
- data/lib/tryouts.rb +40 -0
- data/rudy.gemspec +76 -0
- data/support/mailtest +40 -0
- data/support/rudy-ec2-startup +200 -0
- data/tryouts/console_tryout.rb +91 -0
- metadata +188 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
module Command
|
5
|
+
class Volumes < Rudy::Command::Base
|
6
|
+
|
7
|
+
def destroy_volumes_valid?
|
8
|
+
id = @argv.first
|
9
|
+
raise "No volume ID provided" unless id
|
10
|
+
raise "I will not help you destroy production!" if @global.environment == "prod"
|
11
|
+
raise "The volume #{id} doesn't exist!" unless @ec2.volumes.exists?(id)
|
12
|
+
exit unless are_you_sure? 4
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def destroy_volumes
|
17
|
+
id = @argv.first
|
18
|
+
disk = Rudy::MetaData::Disk.find_from_volume(@sdb, id)
|
19
|
+
|
20
|
+
begin
|
21
|
+
puts "Detaching #{id}"
|
22
|
+
@ec2.volumes.detach(id)
|
23
|
+
sleep 3
|
24
|
+
|
25
|
+
puts "Destroying #{id}"
|
26
|
+
@ec2.volumes.destroy(id)
|
27
|
+
|
28
|
+
if disk
|
29
|
+
puts "Deleteing metadata for #{disk.name}"
|
30
|
+
Rudy::MetaData::Disk.destroy(@sdb, disk)
|
31
|
+
end
|
32
|
+
|
33
|
+
rescue => ex
|
34
|
+
puts "Error while detaching volume #{id}: #{ex.message}"
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def volumes
|
41
|
+
machines = {}
|
42
|
+
volumes = @ec2.volumes.list
|
43
|
+
@ec2.volumes.list.each do |volume|
|
44
|
+
machine = @ec2.instances.get(volume[:aws_instance_id])
|
45
|
+
machines[ volume[:aws_instance_id] ] ||= {
|
46
|
+
:machine => machine,
|
47
|
+
:volumes => []
|
48
|
+
}
|
49
|
+
machines[ volume[:aws_instance_id] ][:volumes] << volume
|
50
|
+
end
|
51
|
+
|
52
|
+
machines.each_pair do |instance_id, hash|
|
53
|
+
machine = hash[:machine]
|
54
|
+
env = (machine[:aws_groups]) ? machine[:aws_groups] : "Not-attached"
|
55
|
+
puts "Environment: #{env}"
|
56
|
+
hash[:volumes].each do |vol|
|
57
|
+
disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
|
58
|
+
print_volume(vol, disk)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
data/lib/rudy/config.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
require 'caesars'
|
5
|
+
|
6
|
+
class AWSInfo < Caesars
|
7
|
+
def valid?
|
8
|
+
(!account.nil? && !accesskey.nil? && !secretkey.nil?) &&
|
9
|
+
(!account.empty? && !accesskey.empty? && !secretkey.empty?)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Defaults < Caesars
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class Routines < Caesars
|
18
|
+
|
19
|
+
def create(*args, &b)
|
20
|
+
hash_handler(:create, *args, &b)
|
21
|
+
end
|
22
|
+
def destroy(*args, &b)
|
23
|
+
hash_handler(:destroy, *args, &b)
|
24
|
+
end
|
25
|
+
def restore(*args, &b)
|
26
|
+
hash_handler(:restore, *args, &b)
|
27
|
+
end
|
28
|
+
def mount(*args, &b)
|
29
|
+
hash_handler(:mount, *args, &b)
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Force the specified keyword to always be treated as a hash.
|
34
|
+
# Example:
|
35
|
+
#
|
36
|
+
# startup do
|
37
|
+
# disks do
|
38
|
+
# create "/path/2" # Available as hash: [action][disks][create][/path/2] == {}
|
39
|
+
# create "/path/4" do # Available as hash: [action][disks][create][/path/4] == {size => 14}
|
40
|
+
# size 14
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
def hash_handler(caesars_meth, *args, &b)
|
46
|
+
# TODO: Move to caesars
|
47
|
+
return @caesars_properties[caesars_meth] if @caesars_properties.has_key?(caesars_meth) && args.empty? && b.nil?
|
48
|
+
return nil if args.empty? && b.nil?
|
49
|
+
return method_missing(caesars_meth, *args, &b) if args.empty?
|
50
|
+
|
51
|
+
caesars_name = args.shift
|
52
|
+
|
53
|
+
prev = @caesars_pointer
|
54
|
+
@caesars_pointer[caesars_meth] ||= Caesars::Hash.new
|
55
|
+
hash = Caesars::Hash.new
|
56
|
+
@caesars_pointer = hash
|
57
|
+
b.call if b
|
58
|
+
@caesars_pointer = prev
|
59
|
+
@caesars_pointer[caesars_meth][caesars_name] = hash
|
60
|
+
@caesars_pointer = prev
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Machines < Caesars
|
65
|
+
end
|
66
|
+
|
67
|
+
class Config < Caesars::Config
|
68
|
+
dsl Rudy::AWSInfo::DSL
|
69
|
+
dsl Rudy::Defaults::DSL
|
70
|
+
dsl Rudy::Routines::DSL
|
71
|
+
dsl Rudy::Machines::DSL
|
72
|
+
|
73
|
+
def postprocess
|
74
|
+
# TODO: give caesar attributes setter methods
|
75
|
+
self.awsinfo.cert &&= File.expand_path(self.awsinfo.cert)
|
76
|
+
self.awsinfo.privatekey &&= File.expand_path(self.awsinfo.privatekey)
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def look_and_load
|
81
|
+
cwd = Dir.pwd
|
82
|
+
# Rudy looks for configs in all these locations
|
83
|
+
@paths += Dir.glob(File.join('/etc', 'rudy', '*.rb')) || []
|
84
|
+
@paths += Dir.glob(File.join(cwd, 'config', 'rudy', '*.rb')) || []
|
85
|
+
@paths += Dir.glob(File.join(cwd, '.rudy', '*.rb')) || []
|
86
|
+
refresh
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
require 'aws_sdb'
|
3
|
+
|
4
|
+
module Rudy
|
5
|
+
module MetaData
|
6
|
+
attr_accessor :sdb
|
7
|
+
attr_accessor :ec2
|
8
|
+
|
9
|
+
def initalize(sdb, ec2)
|
10
|
+
@sdb = sdb
|
11
|
+
@ec2 = ec2
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
module MetaData
|
17
|
+
|
18
|
+
module ObjectBase
|
19
|
+
extend self
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module Rudy
|
5
|
+
module MetaData
|
6
|
+
class Backup < Storable
|
7
|
+
include Rudy::MetaData::ObjectBase
|
8
|
+
extend Rudy::MetaData::ObjectBase
|
9
|
+
|
10
|
+
@@rtype = "back"
|
11
|
+
|
12
|
+
field :rtype
|
13
|
+
field :awsid
|
14
|
+
|
15
|
+
field :region
|
16
|
+
field :zone
|
17
|
+
field :environment
|
18
|
+
field :role
|
19
|
+
field :position
|
20
|
+
field :path
|
21
|
+
|
22
|
+
field :date
|
23
|
+
field :time
|
24
|
+
field :second
|
25
|
+
|
26
|
+
field :unixtime => Integer
|
27
|
+
|
28
|
+
field :size
|
29
|
+
field :volume
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
@zone = DEFAULT_ZONE
|
33
|
+
@region = DEFAULT_REGION
|
34
|
+
@position = "01"
|
35
|
+
@rtype = @@rtype
|
36
|
+
end
|
37
|
+
|
38
|
+
def rtype
|
39
|
+
@@rtype
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.rtype
|
43
|
+
@@rtype
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def name
|
48
|
+
time = Time.at(@unixtime)
|
49
|
+
Backup.generate_name(@zone, @environment, @role, @position, @path, time)
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid?
|
53
|
+
@zone && @environment && @role && @position && @path && @date && @time && @second
|
54
|
+
end
|
55
|
+
|
56
|
+
def time_stamp
|
57
|
+
#return [@date, @time] if @date && @time
|
58
|
+
now = Time.now.utc
|
59
|
+
datetime = Backup.format_timestamp(now).split(RUDY_DELIM)
|
60
|
+
@unixtime = now.to_i
|
61
|
+
@date, @time, @second = datetime
|
62
|
+
end
|
63
|
+
|
64
|
+
def nice_time
|
65
|
+
return "" unless @date && @time
|
66
|
+
t = @date.scan(/(\d\d\d\d)(\d\d)(\d\d)/).join('-')
|
67
|
+
t << " " << @time.scan(/(\d\d)(\d\d)/).join(':')
|
68
|
+
t
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_query(more=[], remove=[])
|
72
|
+
criteria = [:rtype, :zone, :environment, :role, :position, :path, :date, :time, :second, *more]
|
73
|
+
criteria -= [*remove].flatten
|
74
|
+
query = "select * from #{RUDY_DOMAIN} where unixtime > '0' "
|
75
|
+
criteria.each do |n|
|
76
|
+
query << "and #{n} = '#{self.send(n.to_sym)}'"
|
77
|
+
end
|
78
|
+
query << " order by unixtime desc"
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
str = ""
|
83
|
+
field_names.each do |key|
|
84
|
+
str << sprintf(" %22s: %s#{$/}", key, self.send(key.to_sym))
|
85
|
+
end
|
86
|
+
str
|
87
|
+
end
|
88
|
+
|
89
|
+
def disk
|
90
|
+
Disk.generate_name(@zone, @environment, @role, @position, @path)
|
91
|
+
end
|
92
|
+
|
93
|
+
# 20090224-1813-36
|
94
|
+
def Backup.format_timestamp(dat)
|
95
|
+
mon, day, hour, min, sec = [dat.mon, dat.day, dat.hour, dat.min, dat.sec].collect { |v| v.to_s.rjust(2, "0") }
|
96
|
+
[dat.year, mon, day, RUDY_DELIM, hour, min, RUDY_DELIM, sec].join
|
97
|
+
end
|
98
|
+
|
99
|
+
# Times are converted to UTC
|
100
|
+
# back-us-east-1b-stage-app-01-rilli-app-20090224-1813-36
|
101
|
+
def Backup.generate_name(zon, env, rol, pos, pat, dat, sep=File::SEPARATOR)
|
102
|
+
raise "The date you provided is not a Time object" unless dat.is_a?(Time)
|
103
|
+
pos = pos.to_s.rjust 2, '0'
|
104
|
+
dirs = pat.split sep if pat
|
105
|
+
dirs.shift while dirs && (dirs[0].nil? || dirs[0].empty?)
|
106
|
+
timestamp = Backup.format_timestamp(dat.utc)
|
107
|
+
[@@rtype, zon, env, rol, pos, dirs, timestamp].flatten.join(RUDY_DELIM)
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def Backup.for_disk(sdb, disk, max=50)
|
112
|
+
list = Backup.list(sdb, disk.zone, disk.environment, disk.role, disk.position, disk.path) || []
|
113
|
+
list[0..(max-1)]
|
114
|
+
end
|
115
|
+
|
116
|
+
def Backup.get(sdb, name)
|
117
|
+
object = sdb.get_attributes(RUDY_DOMAIN, name)
|
118
|
+
raise "Object #{name} does not exist!" unless object.has_key?(:attributes) && !object[:attributes].empty?
|
119
|
+
self.from_hash(object[:attributes])
|
120
|
+
end
|
121
|
+
|
122
|
+
def Backup.save(sdb, obj, replace = :replace)
|
123
|
+
sdb.store(RUDY_DOMAIN, obj.name, obj.to_hash, replace)
|
124
|
+
end
|
125
|
+
|
126
|
+
def Backup.list(sdb, zon, env=nil, rol=nil, pos=nil, path=nil, date=nil)
|
127
|
+
query = "select * from #{RUDY_DOMAIN} where "
|
128
|
+
query << "rtype = '#{rtype}' "
|
129
|
+
query << " and zone = '#{zon}'" if zon
|
130
|
+
query << " and environment = '#{env}'" if env
|
131
|
+
query << " and role = '#{rol}'" if rol
|
132
|
+
query << " and position = '#{pos}'" if pos
|
133
|
+
query << " and path = '#{path}'" if path
|
134
|
+
query << " and date = '#{date}'" if date
|
135
|
+
query << " and unixtime != '0' order by unixtime desc"
|
136
|
+
list = []
|
137
|
+
sdb.select(query).each do |obj|
|
138
|
+
list << self.from_hash(obj)
|
139
|
+
end
|
140
|
+
list
|
141
|
+
end
|
142
|
+
|
143
|
+
def Backup.destroy(sdb, name)
|
144
|
+
back = Backup.get(sdb, name) # get raises an exception if the disk doesn't exist
|
145
|
+
sdb.destroy(RUDY_DOMAIN, name)
|
146
|
+
true # wtf: RightAws::SimpleDB doesn't tell us whether it succeeds. We'll assume!
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
def Backup.is_defined?(sdb, backup)
|
152
|
+
query = backup.to_query()
|
153
|
+
puts query
|
154
|
+
!sdb.select(query).empty?
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
@@ -0,0 +1,138 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
|
5
|
+
module MetaData
|
6
|
+
class Disk < Storable
|
7
|
+
|
8
|
+
@@rtype = 'disk'
|
9
|
+
|
10
|
+
# This is a flag used internally to specify that a volume has been
|
11
|
+
# created for this disk, but not formated.
|
12
|
+
attr_accessor :raw_volume
|
13
|
+
|
14
|
+
field :rtype
|
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
|
+
@backups = []
|
30
|
+
@rtype = @@rtype.to_s
|
31
|
+
@raw_volume = false
|
32
|
+
end
|
33
|
+
|
34
|
+
def rtype
|
35
|
+
@@rtype.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def rtype=(val)
|
39
|
+
end
|
40
|
+
|
41
|
+
def name
|
42
|
+
Disk.generate_name(@zone, @environment, @role, @position, @path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid?
|
46
|
+
@zone && @environment && @role && @position && @path && @size && @device
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_query(more=[], remove=[])
|
50
|
+
criteria = [:rtype, :zone, :environment, :role, :position, :path, *more]
|
51
|
+
criteria -= [*remove].flatten
|
52
|
+
query = []
|
53
|
+
criteria.each do |n|
|
54
|
+
query << "['#{n}' = '#{self.send(n.to_sym)}'] "
|
55
|
+
end
|
56
|
+
query.join(" intersection ")
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
str = ""
|
61
|
+
field_names.each do |key|
|
62
|
+
str << sprintf(" %22s: %s#{$/}", key, self.send(key.to_sym))
|
63
|
+
end
|
64
|
+
str
|
65
|
+
end
|
66
|
+
|
67
|
+
def Disk.generate_name(zon, env, rol, pos, pat, sep=File::SEPARATOR)
|
68
|
+
pos = pos.to_s.rjust 2, '0'
|
69
|
+
dirs = pat.split sep if pat
|
70
|
+
dirs.shift while dirs && (dirs[0].nil? || dirs[0].empty?)
|
71
|
+
["disk", zon, env, rol, pos, *dirs].join(RUDY_DELIM)
|
72
|
+
end
|
73
|
+
|
74
|
+
def Disk.get(sdb, name)
|
75
|
+
disk = sdb.get_attributes(RUDY_DOMAIN, name)
|
76
|
+
return nil unless disk && disk.has_key?(:attributes) && !disk[:attributes].empty?
|
77
|
+
# raise "Disk #{name} does not exist!" unless
|
78
|
+
Rudy::MetaData::Disk.from_hash(disk[:attributes])
|
79
|
+
end
|
80
|
+
|
81
|
+
def Disk.destroy(sdb, disk)
|
82
|
+
disk = Disk.get(sdb, disk) if disk.is_a?(String) # get raises an exception if the disk doesn't exist
|
83
|
+
sdb.destroy(RUDY_DOMAIN, disk.name)
|
84
|
+
true # wtf: RightAws::SimpleDB doesn't tell us whether it succeeds. We'll assume!
|
85
|
+
end
|
86
|
+
|
87
|
+
def Disk.save(sdb, disk)
|
88
|
+
sdb.store(RUDY_DOMAIN, disk.name, disk.to_hash, :replace)
|
89
|
+
end
|
90
|
+
|
91
|
+
def Disk.is_defined?(sdb, disk)
|
92
|
+
# We don't care about the path, but we do care about the device
|
93
|
+
# which is not part of the disk's name.
|
94
|
+
query = disk.to_query(:device, :path)
|
95
|
+
!sdb.query_with_attributes(RUDY_DOMAIN, query).empty?
|
96
|
+
end
|
97
|
+
|
98
|
+
def Disk.find_from_volume(sdb, vol_id)
|
99
|
+
query = "['awsid' = '#{vol_id}']"
|
100
|
+
res = sdb.query_with_attributes(RUDY_DOMAIN, query)
|
101
|
+
if res.empty?
|
102
|
+
nil
|
103
|
+
else
|
104
|
+
disk = Rudy::MetaData::Disk.from_hash(res.values.first)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
def Disk.find_from_path(sdb, path)
|
110
|
+
query = "['path' = '#{path}']"
|
111
|
+
res = sdb.query_with_attributes(RUDY_DOMAIN, query)
|
112
|
+
if res.empty?
|
113
|
+
nil
|
114
|
+
else
|
115
|
+
disk = Rudy::MetaData::Disk.from_hash(res.values.first)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def Disk.list(sdb, zon, env=nil, rol=nil, pos=nil)
|
120
|
+
query = ''
|
121
|
+
query << "['rtype' = '#{@@rtype}']" if zon
|
122
|
+
query << " intersection ['zone' = '#{zon}']" if zon
|
123
|
+
query << " intersection ['environment' = '#{env}']" if env
|
124
|
+
query << " intersection ['role' = '#{rol}']" if rol
|
125
|
+
query << " intersection ['position' = '#{pos}']" if pos
|
126
|
+
|
127
|
+
list = []
|
128
|
+
sdb.query_with_attributes(RUDY_DOMAIN, query).each_pair do |name, hash|
|
129
|
+
#puts "DISK: #{hash.to_yaml}"
|
130
|
+
list << Rudy::MetaData::Disk.from_hash(hash)
|
131
|
+
end
|
132
|
+
list
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|