solutious-rudy 0.4.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/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
|