rudy 0.3.2 → 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 +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)
|