rudy 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +53 -3
- data/README.rdoc +9 -5
- data/bin/rudy +115 -292
- data/bin/rudy-ec2 +107 -0
- data/lib/console.rb +322 -278
- data/lib/rudy.rb +78 -55
- data/lib/rudy/aws/ec2.rb +63 -5
- data/lib/rudy/command/addresses.rb +18 -13
- data/lib/rudy/command/backups.rb +175 -0
- data/lib/rudy/command/base.rb +664 -146
- data/lib/rudy/command/config.rb +77 -0
- data/lib/rudy/command/deploy.rb +12 -0
- data/lib/rudy/command/disks.rb +165 -195
- data/lib/rudy/command/environment.rb +42 -64
- data/lib/rudy/command/groups.rb +21 -19
- data/lib/rudy/command/images.rb +34 -19
- data/lib/rudy/command/instances.rb +46 -92
- data/lib/rudy/command/machines.rb +161 -0
- data/lib/rudy/command/metadata.rb +14 -30
- data/lib/rudy/command/release.rb +174 -0
- data/lib/rudy/command/volumes.rb +26 -10
- data/lib/rudy/config.rb +93 -0
- data/lib/rudy/metadata/backup.rb +1 -1
- data/lib/rudy/metadata/disk.rb +15 -50
- data/lib/rudy/scm/svn.rb +32 -21
- data/lib/rudy/utils.rb +2 -3
- data/lib/storable.rb +4 -0
- data/lib/tryouts.rb +40 -0
- data/rudy.gemspec +25 -9
- data/support/mailtest +40 -0
- data/support/rudy-ec2-startup +41 -15
- data/tryouts/console_tryout.rb +91 -0
- metadata +86 -11
- data/lib/drydock.rb +0 -524
- data/lib/rudy/command/stage.rb +0 -45
- data/lib/rudy/metadata/config.rb +0 -8
- data/lib/rudy/metadata/environment.rb +0 -0
@@ -5,52 +5,36 @@ module Rudy
|
|
5
5
|
class Metadata < Rudy::Command::Base
|
6
6
|
|
7
7
|
|
8
|
-
#
|
9
|
-
def
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
}
|
15
|
-
puts "Putting #{instances.join(', ')} into #{machine_group}"
|
16
|
-
|
17
|
-
@sdb.store(RUDY_DOMAIN, "group_#{machine_group}", attributes)
|
18
|
-
|
19
|
-
p group_metadata
|
8
|
+
# Print Rudy's metadata to STDOUT
|
9
|
+
def metadata
|
10
|
+
group_metadata.each_pair do |n,h|
|
11
|
+
puts n.att(:bright)
|
12
|
+
puts h.inspect, ""
|
13
|
+
end
|
20
14
|
end
|
21
15
|
|
22
|
-
|
23
|
-
|
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)
|
16
|
+
def destroy_metadata_valid?
|
17
|
+
false
|
29
18
|
end
|
30
19
|
|
31
20
|
def destroy_metadata
|
32
21
|
@sdb.domains.destroy(RUDY_DOMAIN)
|
33
22
|
end
|
34
23
|
|
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
24
|
|
43
|
-
|
25
|
+
|
26
|
+
def info
|
44
27
|
domains = @sdb.domains.list[:domains]
|
45
28
|
puts "Domains: #{domains.join(", ")}"
|
46
29
|
end
|
47
|
-
|
30
|
+
|
31
|
+
private
|
48
32
|
def check_environment
|
49
33
|
raise "No Amazon keys provided!" unless has_keys?
|
50
|
-
raise "No SSH keypairs provided!" unless
|
34
|
+
raise "No SSH keypairs provided!" unless has_keypair?
|
51
35
|
true
|
52
36
|
end
|
53
|
-
|
37
|
+
|
54
38
|
end
|
55
39
|
end
|
56
40
|
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
module Command
|
5
|
+
class Release < Rudy::Command::Base
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
def release_valid?
|
11
|
+
|
12
|
+
relroutine = @config.routines.find_deferred(@global.environment, @global.role, :release)
|
13
|
+
raise "No release routines defined for #{machine_group}" if relroutine.nil?
|
14
|
+
|
15
|
+
raise "No EC2 .pem keys provided" unless has_pem_keys?
|
16
|
+
raise "No SSH key provided for #{@global.user} in #{machine_group}!" unless has_keypair?
|
17
|
+
raise "No SSH key provided for root in #{machine_group}!" unless has_keypair?(:root)
|
18
|
+
|
19
|
+
@list = @ec2.instances.list(machine_group)
|
20
|
+
unless @list.empty?
|
21
|
+
msg = "#{machine_group} is in use, probably with another release. #{$/}"
|
22
|
+
msg << 'Sort it out and run "rudy shutdown" before continuing.'
|
23
|
+
raise msg
|
24
|
+
end
|
25
|
+
|
26
|
+
@scm, @scm_params = find_scm(:release)
|
27
|
+
|
28
|
+
raise "No SCM defined for release routine" unless @scm
|
29
|
+
raise "#{Dir.pwd} is not a working copy" unless @scm.working_copy?(Dir.pwd)
|
30
|
+
raise "There are local changes. Please revert or check them in." unless @scm.everything_checked_in?
|
31
|
+
raise "Invalid base URI (#{@scm_params[:base]})." unless @scm.valid_uri?(@scm_params[:base])
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
def rerelease_valid?
|
39
|
+
relroutine = @config.routines.find_deferred(@global.environment, @global.role, :rerelease)
|
40
|
+
raise "No rerelease routines defined for #{machine_group}" if relroutine.nil?
|
41
|
+
|
42
|
+
raise "No EC2 .pem keys provided" unless has_pem_keys?
|
43
|
+
raise "No SSH key provided for #{@global.user} in #{machine_group}!" unless has_keypair?
|
44
|
+
raise "No SSH key provided for root in #{machine_group}!" unless has_keypair?(:root)
|
45
|
+
|
46
|
+
@list = @ec2.instances.list(machine_group)
|
47
|
+
if @list.empty?
|
48
|
+
msg = "There are no machines running in #{machine_group}. #{$/}"
|
49
|
+
msg << 'You must run "rudy release" before you can rerelease.'
|
50
|
+
raise msg
|
51
|
+
end
|
52
|
+
|
53
|
+
@scm, @scm_params = find_scm(:rerelease)
|
54
|
+
|
55
|
+
raise "No SCM defined for release routine" unless @scm
|
56
|
+
raise "#{Dir.pwd} is not a working copy" unless @scm.working_copy?(Dir.pwd)
|
57
|
+
raise "There are local changes. Please revert or check them in." unless @scm.everything_checked_in?
|
58
|
+
raise "Invalid base URI (#{@scm_params[:base]})." unless @scm.valid_uri?(@scm_params[:base])
|
59
|
+
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def rerelease
|
64
|
+
puts "Updating release from working copy".att(:bright)
|
65
|
+
|
66
|
+
tag, revision = @scm.local_info
|
67
|
+
puts "tag: #{tag}"
|
68
|
+
puts "rev: #{revision}"
|
69
|
+
|
70
|
+
execute_disk_routines(@list.values, :rerelease)
|
71
|
+
|
72
|
+
if @scm
|
73
|
+
|
74
|
+
puts "Running SCM command".att(:bright)
|
75
|
+
ssh do |session|
|
76
|
+
cmd = "svn #{@scm_params[:command]}"
|
77
|
+
puts "#{cmd}"
|
78
|
+
session.exec!("cd #{@scm_params[:path]}")
|
79
|
+
session.exec!(cmd)
|
80
|
+
puts "#{@scm_params[:command]} complete"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
execute_routines(@list.values, :rerelease, :after)
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
# <li>Creates a release tag based on the working copy on your machine</li>
|
90
|
+
# <li>Starts a new stage instance</li>
|
91
|
+
# <li>Executes release routines</li>
|
92
|
+
def release
|
93
|
+
# TODO: store metadata about release with local username and hostname
|
94
|
+
puts "Creating release from working copy".att(:bright)
|
95
|
+
|
96
|
+
exit unless are_you_sure?
|
97
|
+
|
98
|
+
tag = @scm.create_release(@global.local_user, @option.msg)
|
99
|
+
puts "Done! (#{tag})"
|
100
|
+
|
101
|
+
if @option.switch
|
102
|
+
puts "Switching working copy to new tag".att(:bright)
|
103
|
+
@scm.switch_working_copy(tag)
|
104
|
+
end
|
105
|
+
|
106
|
+
@option.image ||= machine_image
|
107
|
+
|
108
|
+
switch_user("root")
|
109
|
+
|
110
|
+
puts "Starting #{machine_group}".att(:bright)
|
111
|
+
|
112
|
+
instances = @ec2.instances.create(@option.image, machine_group.to_s, File.basename(keypairpath), machine_data.to_yaml, @global.zone)
|
113
|
+
inst = instances.first
|
114
|
+
|
115
|
+
if @option.address ||= machine_address
|
116
|
+
puts "Associating #{@option.address} to #{inst[:aws_instance_id]}".att(:bright)
|
117
|
+
@ec2.addresses.associate(inst[:aws_instance_id], @option.address)
|
118
|
+
end
|
119
|
+
|
120
|
+
wait_for_machine(inst[:aws_instance_id])
|
121
|
+
inst = @ec2.instances.get(inst[:aws_instance_id])
|
122
|
+
|
123
|
+
#inst = @ec2.instances.list(machine_group).values
|
124
|
+
|
125
|
+
|
126
|
+
execute_disk_routines(inst, :release)
|
127
|
+
|
128
|
+
if @scm
|
129
|
+
|
130
|
+
puts "Running SCM command".att(:bright)
|
131
|
+
ssh do |session|
|
132
|
+
cmd = "svn #{@scm_params[:command]} #{tag} #{@scm_params[:path]}"
|
133
|
+
puts "#{cmd}"
|
134
|
+
session.exec!(cmd)
|
135
|
+
puts "#{@scm_params[:command]} complete"
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
execute_routines(inst, :release, :after)
|
141
|
+
|
142
|
+
print_instance inst
|
143
|
+
|
144
|
+
puts "Done!"
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
def find_scm(routine)
|
149
|
+
env, rol, att = @global.environment, @global.role
|
150
|
+
|
151
|
+
# Look for the source control engine, checking all known scm values.
|
152
|
+
# The available one will look like [environment][role][release][svn]
|
153
|
+
params = nil
|
154
|
+
scm_name = nil
|
155
|
+
SUPPORTED_SCM_NAMES.each do |v|
|
156
|
+
scm_name = v
|
157
|
+
params = @config.routines.find(env, rol, routine, scm_name)
|
158
|
+
break if params
|
159
|
+
end
|
160
|
+
|
161
|
+
if params
|
162
|
+
klass = eval "Rudy::SCM::#{scm_name.to_s.upcase}"
|
163
|
+
scm = klass.new(:base => params[:base])
|
164
|
+
end
|
165
|
+
|
166
|
+
[scm, params]
|
167
|
+
|
168
|
+
end
|
169
|
+
private :find_scm
|
170
|
+
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
data/lib/rudy/command/volumes.rb
CHANGED
@@ -4,24 +4,40 @@ module Rudy
|
|
4
4
|
module Command
|
5
5
|
class Volumes < Rudy::Command::Base
|
6
6
|
|
7
|
-
def
|
7
|
+
def destroy_volumes_valid?
|
8
|
+
id = @argv.first
|
8
9
|
raise "No volume ID provided" unless id
|
9
|
-
raise "I will not help you destroy production!" if @environment == "prod"
|
10
|
+
raise "I will not help you destroy production!" if @global.environment == "prod"
|
10
11
|
raise "The volume #{id} doesn't exist!" unless @ec2.volumes.exists?(id)
|
11
|
-
|
12
|
+
exit unless are_you_sure? 4
|
13
|
+
true
|
14
|
+
end
|
12
15
|
|
13
|
-
|
14
|
-
@
|
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
|
15
24
|
|
16
|
-
|
17
|
-
|
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}"
|
18
35
|
end
|
19
36
|
|
20
|
-
Rudy::MetaData::Disk.save(@sdb, disk)
|
21
37
|
|
22
38
|
end
|
23
39
|
|
24
|
-
def
|
40
|
+
def volumes
|
25
41
|
machines = {}
|
26
42
|
volumes = @ec2.volumes.list
|
27
43
|
@ec2.volumes.list.each do |volume|
|
@@ -38,7 +54,7 @@ module Rudy
|
|
38
54
|
env = (machine[:aws_groups]) ? machine[:aws_groups] : "Not-attached"
|
39
55
|
puts "Environment: #{env}"
|
40
56
|
hash[:volumes].each do |vol|
|
41
|
-
disk = Rudy::MetaData::Disk.
|
57
|
+
disk = Rudy::MetaData::Disk.find_from_volume(@sdb, vol[:aws_id])
|
42
58
|
print_volume(vol, disk)
|
43
59
|
end
|
44
60
|
end
|
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
|
+
|
data/lib/rudy/metadata/backup.rb
CHANGED
data/lib/rudy/metadata/disk.rb
CHANGED
@@ -26,9 +26,6 @@ module Rudy
|
|
26
26
|
field :size
|
27
27
|
|
28
28
|
def initialize
|
29
|
-
@device = "/dev/sdh"
|
30
|
-
@zone = DEFAULT_ZONE
|
31
|
-
@region = DEFAULT_REGION
|
32
29
|
@backups = []
|
33
30
|
@rtype = @@rtype.to_s
|
34
31
|
@raw_volume = false
|
@@ -46,7 +43,7 @@ module Rudy
|
|
46
43
|
end
|
47
44
|
|
48
45
|
def valid?
|
49
|
-
@zone && @environment && @role && @position && @path
|
46
|
+
@zone && @environment && @role && @position && @path && @size && @device
|
50
47
|
end
|
51
48
|
|
52
49
|
def to_query(more=[], remove=[])
|
@@ -76,14 +73,14 @@ module Rudy
|
|
76
73
|
|
77
74
|
def Disk.get(sdb, name)
|
78
75
|
disk = sdb.get_attributes(RUDY_DOMAIN, name)
|
79
|
-
|
80
|
-
raise "Disk #{name} does not exist!" unless
|
76
|
+
return nil unless disk && disk.has_key?(:attributes) && !disk[:attributes].empty?
|
77
|
+
# raise "Disk #{name} does not exist!" unless
|
81
78
|
Rudy::MetaData::Disk.from_hash(disk[:attributes])
|
82
79
|
end
|
83
80
|
|
84
|
-
def Disk.destroy(sdb,
|
85
|
-
disk = Disk.get(sdb,
|
86
|
-
sdb.destroy(RUDY_DOMAIN, name)
|
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)
|
87
84
|
true # wtf: RightAws::SimpleDB doesn't tell us whether it succeeds. We'll assume!
|
88
85
|
end
|
89
86
|
|
@@ -98,7 +95,7 @@ module Rudy
|
|
98
95
|
!sdb.query_with_attributes(RUDY_DOMAIN, query).empty?
|
99
96
|
end
|
100
97
|
|
101
|
-
def Disk.
|
98
|
+
def Disk.find_from_volume(sdb, vol_id)
|
102
99
|
query = "['awsid' = '#{vol_id}']"
|
103
100
|
res = sdb.query_with_attributes(RUDY_DOMAIN, query)
|
104
101
|
if res.empty?
|
@@ -108,47 +105,15 @@ module Rudy
|
|
108
105
|
end
|
109
106
|
end
|
110
107
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Otherwise we need to start one
|
122
|
-
unless disk.awsid
|
123
|
-
puts "No active EBS volume found for #{disk.name}"
|
124
|
-
|
125
|
-
# TODO: pull actual backups
|
126
|
-
backups = Rudy::MetaData::Backup.for_disk(sdb, disk, 2)
|
127
|
-
|
128
|
-
if backups.is_a?(Array) && !backups.empty?
|
129
|
-
backup = backups.first
|
130
|
-
if ec2.snapshots.exists?(backup.awsid)
|
131
|
-
puts "We'll use the most recent backup (#{backup.awsid})..."
|
132
|
-
volume = ec2.volumes.create(disk.zone, disk.size, backup.awsid)
|
133
|
-
else
|
134
|
-
puts "The backup refers to a snapshot that doesn't exist."
|
135
|
-
puts backup.name, backup.awsid
|
136
|
-
puts "You need to delete this backup metadata before continuing."
|
137
|
-
exit 1
|
138
|
-
end
|
139
|
-
else
|
140
|
-
puts "We'll create one from scratch..."
|
141
|
-
volume = ec2.volumes.create(disk.zone, disk.size, nil)
|
142
|
-
disk.raw_volume = true
|
143
|
-
end
|
144
|
-
|
145
|
-
puts "Saving disk metadata"
|
146
|
-
disk.awsid = volume[:aws_id]
|
147
|
-
Disk.save(sdb, disk)
|
148
|
-
puts ""
|
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)
|
149
116
|
end
|
150
|
-
|
151
|
-
disk
|
152
117
|
end
|
153
118
|
|
154
119
|
def Disk.list(sdb, zon, env=nil, rol=nil, pos=nil)
|