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,275 @@
|
|
1
|
+
|
2
|
+
module Rudy
|
3
|
+
class UnknownInstance < RuntimeError; end
|
4
|
+
end
|
5
|
+
|
6
|
+
module Rudy
|
7
|
+
module Command
|
8
|
+
class NoCred < RuntimeError; end;
|
9
|
+
|
10
|
+
class Base < Drydock::Command
|
11
|
+
|
12
|
+
attr_accessor :access_key
|
13
|
+
attr_accessor :secret_key
|
14
|
+
attr_accessor :ec2_cert
|
15
|
+
attr_accessor :ec2_private_key
|
16
|
+
attr_accessor :keypairs
|
17
|
+
|
18
|
+
attr_reader :user
|
19
|
+
attr_reader :environment
|
20
|
+
attr_reader :role
|
21
|
+
attr_reader :position
|
22
|
+
|
23
|
+
attr_reader :zone
|
24
|
+
attr_reader :region
|
25
|
+
|
26
|
+
attr_reader :scm
|
27
|
+
|
28
|
+
attr_reader :rscripts
|
29
|
+
|
30
|
+
attr_reader :machine_images
|
31
|
+
|
32
|
+
def init
|
33
|
+
@access_key = ENV['AWS_ACCESS_KEY'] unless @access_key
|
34
|
+
@secret_key = ENV['AWS_SECRET_KEY'] unless @secret_key
|
35
|
+
|
36
|
+
@ec2_cert = ENV['EC2_CERT'] unless @ec2_cert
|
37
|
+
@ec2_private_key = ENV['EC2_PRIVATE_KEY'] unless @ec2_private_key
|
38
|
+
|
39
|
+
if ENV['RUDY_SVN_BASE']
|
40
|
+
@scm = Rudy::SCM::SVN.new(ENV['RUDY_SVN_BASE'])
|
41
|
+
end
|
42
|
+
|
43
|
+
@user ||= 'rudy'
|
44
|
+
@environment ||= 'stage'
|
45
|
+
@role ||= 'app'
|
46
|
+
@position ||= '01'
|
47
|
+
|
48
|
+
@zone ||= DEFAULT_ZONE
|
49
|
+
@region ||= DEFAULT_REGION
|
50
|
+
|
51
|
+
@keypairs = {}
|
52
|
+
ENV.keys.select { |key| key.match /EC2_KEYPAIR/i }.each do |key|
|
53
|
+
ec2, keypair, env, role, user = key.split '_' # EC2_KEYPAIR_STAGE_APP_RUDY
|
54
|
+
raise "#{key} is malformed." unless env && role && user
|
55
|
+
new_key = "#{env}-#{role}-#{user}".downcase
|
56
|
+
@keypairs[new_key] = ENV[key]
|
57
|
+
end
|
58
|
+
|
59
|
+
@rscripts = {}
|
60
|
+
ENV.keys.select { |key| key.match /RUDY_RSCRIPT/i }.each do |key|
|
61
|
+
rudy, rscript, env, role, user = key.split '_' # RUDY_RSCRIPT_STAGE_APP_ROOT
|
62
|
+
raise "#{key} is malformed." unless env && role && user
|
63
|
+
new_key = "#{env}-#{role}-#{user}".downcase
|
64
|
+
@rscripts[new_key] = ENV[key]
|
65
|
+
end
|
66
|
+
|
67
|
+
@machine_images = {}
|
68
|
+
ENV.keys.select { |key| key.match /EC2_AMI_/i }.each do |key|
|
69
|
+
ec2, ami, env, role = key.split '_' # RUDY_RSCRIPT_STAGE_APP_ROOT
|
70
|
+
raise "#{key} is malformed." unless env && role
|
71
|
+
new_key = "#{env}-#{role}".downcase
|
72
|
+
@machine_images[new_key] = ENV[key]
|
73
|
+
end
|
74
|
+
|
75
|
+
if @verbose > 1 && Drydock.debug?
|
76
|
+
instance_variables.each do |var|
|
77
|
+
puts "#{var}: #{instance_variable_get(var)}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@sdb = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
|
82
|
+
@ec2 = Rudy::AWS::EC2.new(@access_key, @secret_key)
|
83
|
+
#@s3 = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
|
84
|
+
end
|
85
|
+
|
86
|
+
def has_pem_keys?
|
87
|
+
(@ec2_cert && File.exists?(@ec2_cert) &&
|
88
|
+
@ec2_private_key && File.exists?(@ec2_private_key))
|
89
|
+
end
|
90
|
+
|
91
|
+
def has_keys?
|
92
|
+
(@access_key && @secret_key)
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_keypair?(name)
|
96
|
+
(has_keypairs? && @keypairs.has_key?(name))
|
97
|
+
end
|
98
|
+
|
99
|
+
def has_keypairs?
|
100
|
+
(!@keypairs.empty?)
|
101
|
+
end
|
102
|
+
|
103
|
+
def machine_group
|
104
|
+
[@environment, @role].join(RUDY_DELIM)
|
105
|
+
end
|
106
|
+
|
107
|
+
def machine_image
|
108
|
+
@machine_images[machine_group]
|
109
|
+
end
|
110
|
+
|
111
|
+
def machine_name
|
112
|
+
[machine_group, @position].join(RUDY_DELIM)
|
113
|
+
end
|
114
|
+
|
115
|
+
def keypairname
|
116
|
+
[machine_group, user].join(RUDY_DELIM)
|
117
|
+
end
|
118
|
+
|
119
|
+
def keypairpath
|
120
|
+
return unless has_keypair?(keypairname)
|
121
|
+
@keypairs[keypairname]
|
122
|
+
end
|
123
|
+
|
124
|
+
def instance_id?(id=nil)
|
125
|
+
(id && id[0,2] == "i-")
|
126
|
+
end
|
127
|
+
|
128
|
+
def image_id?(id=nil)
|
129
|
+
(id && id[0,4] == "ami-")
|
130
|
+
end
|
131
|
+
|
132
|
+
def volume_id?(id=nil)
|
133
|
+
(id && id[0,4] == "vol-")
|
134
|
+
end
|
135
|
+
|
136
|
+
def snapshot_id?(id=nil)
|
137
|
+
(id && id[0,5] == "snap-")
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def do_dirty_disk_volume_deeds(disk, machine)
|
142
|
+
puts "-"*30
|
143
|
+
puts "Disk: #{disk.name} (path: #{disk.path}, device: #{disk.device})"
|
144
|
+
puts
|
145
|
+
|
146
|
+
if @ec2.instances.attached_volume?(machine[:aws_instance_id], disk.device)
|
147
|
+
raise "#{disk.device} is already in use on #{machine[:aws_instance_id]}! (Try umounting it)"
|
148
|
+
end
|
149
|
+
|
150
|
+
new_volume = false
|
151
|
+
if !disk.awsid || (disk.awsid && !@ec2.volumes.exists?(disk.awsid))
|
152
|
+
disk = Rudy::MetaData::Disk.update_volume(@sdb, @ec2, disk, machine)
|
153
|
+
new_volume = true
|
154
|
+
end
|
155
|
+
|
156
|
+
raise "Unknown error creating volume! #{disk.awsid}" unless disk && disk.awsid
|
157
|
+
|
158
|
+
puts "Attaching #{disk.awsid} to #{machine[:aws_instance_id]}"
|
159
|
+
@ec2.volumes.attach(machine[:aws_instance_id], disk.awsid, disk.device)
|
160
|
+
sleep 2
|
161
|
+
|
162
|
+
@user = "root"
|
163
|
+
|
164
|
+
if new_volume
|
165
|
+
puts "Creating the filesystem (mkfs.ext3 -F #{disk.device})"
|
166
|
+
capture(:stdout) do
|
167
|
+
ssh machine[:dns_name], keypairpath, user, "mkfs.ext3 -F #{disk.device}"
|
168
|
+
end
|
169
|
+
sleep 2
|
170
|
+
end
|
171
|
+
|
172
|
+
puts "Mounting #{disk.device} to #{disk.path}"
|
173
|
+
capture(:stdout) do
|
174
|
+
ssh machine[:dns_name], keypairpath, user, "mkdir -p #{disk.path} && mount -t ext3 #{disk.device} #{disk.path}"
|
175
|
+
end
|
176
|
+
sleep 1
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
# Print a default header to the screen for every command.
|
183
|
+
# +cmd+ is the name of the command current running.
|
184
|
+
def print_header(cmd=nil)
|
185
|
+
print "RUDY v#{Rudy::VERSION}"
|
186
|
+
print " -- #{@alias}" if @alias
|
187
|
+
puts
|
188
|
+
|
189
|
+
criteria = []
|
190
|
+
[:zone, :environment, :role, :position].each do |n|
|
191
|
+
val = instance_variable_get("@#{n}")
|
192
|
+
criteria << "[#{n} = #{val}]"
|
193
|
+
end
|
194
|
+
puts criteria.join(" and ")
|
195
|
+
|
196
|
+
if (@environment == "prod")
|
197
|
+
puts %q(
|
198
|
+
=======================================================
|
199
|
+
=======================================================
|
200
|
+
!!!!!!!!! YOU ARE PLAYING WITH PRODUCTION !!!!!!!!!
|
201
|
+
=======================================================
|
202
|
+
=======================================================
|
203
|
+
)
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
def print_footer
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
|
214
|
+
def group_metadata(env=@environment, role=@role)
|
215
|
+
query = "['environment' = '#{env}'] intersection ['role' = '#{role}']"
|
216
|
+
@sdb.query_with_attributes(RUDY_DOMAIN, query)
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
# Print info about a running instance
|
221
|
+
# +inst+ is a hash
|
222
|
+
def print_instance(inst)
|
223
|
+
puts '-'*60
|
224
|
+
puts "Instance: #{inst[:aws_instance_id]} (AMI: #{inst[:aws_image_id]})"
|
225
|
+
[:aws_state, :dns_name, :private_dns_name, :aws_availability_zone, :aws_launch_time, :ssh_key_name].each do |key|
|
226
|
+
printf(" %22s: %s#{$/}", key, inst[key]) if inst[key]
|
227
|
+
end
|
228
|
+
printf(" %22s: %s#{$/}", 'aws_groups', inst[:aws_groups].join(', '))
|
229
|
+
puts
|
230
|
+
end
|
231
|
+
|
232
|
+
def print_image(img)
|
233
|
+
puts '-'*60
|
234
|
+
puts "Image: #{img[:aws_location]}"
|
235
|
+
img.each_pair do |key, value|
|
236
|
+
printf(" %22s: %s#{$/}", key, value) if value
|
237
|
+
end
|
238
|
+
puts
|
239
|
+
end
|
240
|
+
|
241
|
+
def print_disk(disk)
|
242
|
+
puts '-'*60
|
243
|
+
puts "Disk: #{disk.name}"
|
244
|
+
puts disk.to_s
|
245
|
+
puts
|
246
|
+
end
|
247
|
+
|
248
|
+
# Print info about a a security group
|
249
|
+
# +group+ is an OpenStruct
|
250
|
+
def print_group(group)
|
251
|
+
puts '-'*60
|
252
|
+
puts "%12s: %s" % ['GROUP', group[:aws_group_name]]
|
253
|
+
puts
|
254
|
+
|
255
|
+
group_ip = {}
|
256
|
+
group[:aws_perms].each do |perm|
|
257
|
+
(group_ip[ perm[:cidr_ips] ] ||= []) << "#{perm[:protocol]}/#{perm[:from_port]}-#{perm[:to_port]}"
|
258
|
+
end
|
259
|
+
|
260
|
+
puts "%22s %s" % ["source address/mask", "protocol/ports (from, to)"]
|
261
|
+
|
262
|
+
|
263
|
+
group_ip.each_pair do |ip, perms|
|
264
|
+
puts "%22s %s" % [ip, perms.shift]
|
265
|
+
perms.each do |perm|
|
266
|
+
puts "%22s %s" % ['', perm]
|
267
|
+
end
|
268
|
+
puts
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
module Command
|
5
|
+
class Disks < Rudy::Command::Base
|
6
|
+
|
7
|
+
|
8
|
+
def create_disk
|
9
|
+
disk = Rudy::MetaData::Disk.new
|
10
|
+
[:environment, :role, :position, :path, :device, :size].each do |n|
|
11
|
+
val = instance_variable_get("@#{n}")
|
12
|
+
disk.send("#{n}=", val) if val
|
13
|
+
end
|
14
|
+
|
15
|
+
raise "Not enough info was provided to define a disk (#{disk.name})" unless disk.valid?
|
16
|
+
raise "The device #{disk.device} is already in use on that machine" if Rudy::MetaData::Disk.is_defined?(@sdb, disk)
|
17
|
+
puts "Creating disk metadata for #{disk.name}"
|
18
|
+
|
19
|
+
Rudy::MetaData::Disk.save(@sdb, disk)
|
20
|
+
|
21
|
+
print_disks
|
22
|
+
end
|
23
|
+
|
24
|
+
def print_disks
|
25
|
+
criteria = [@zone]
|
26
|
+
criteria += [@environment, @role] unless @all
|
27
|
+
|
28
|
+
Rudy::MetaData::Disk.list(@sdb, *criteria).each do |disk|
|
29
|
+
print_disk disk
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def attach_disk(name)
|
35
|
+
puts "Looking for #{name}"
|
36
|
+
disk = Rudy::MetaData::Disk.get(@sdb, name)
|
37
|
+
instances = @ec2.instances.list(machine_group)
|
38
|
+
raise "There are no instances running in #{machine_group}" if !instances || instances.empty?
|
39
|
+
instance_id = instances.keys.first
|
40
|
+
machine = instances.values.first
|
41
|
+
|
42
|
+
do_dirty_disk_volume_deeds(disk, machine)
|
43
|
+
|
44
|
+
|
45
|
+
puts
|
46
|
+
ssh machine[:dns_name], keypairpath, user, "df -h" # Display current mounts
|
47
|
+
puts
|
48
|
+
|
49
|
+
puts "Done!"
|
50
|
+
end
|
51
|
+
|
52
|
+
def destroy_disk(name)
|
53
|
+
puts "Destroying #{name}"
|
54
|
+
@sdb.destroy(RUDY_DOMAIN, name)
|
55
|
+
puts "Done."
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
# :role:
|
7
|
+
# :env:
|
8
|
+
# :access_key:
|
9
|
+
# :secret_key:
|
10
|
+
# :dbmachine:
|
11
|
+
|
12
|
+
|
13
|
+
module Rudy
|
14
|
+
module Command
|
15
|
+
class Environment < Rudy::Command::Base
|
16
|
+
|
17
|
+
def connect
|
18
|
+
raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
|
19
|
+
raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
|
20
|
+
machine_list = @ec2.instances.list(machine_group)
|
21
|
+
machine = machine_list.values.first # NOTE: Only one machine per group, for now...
|
22
|
+
|
23
|
+
raise "There's no machine running in #{machine_group}" unless machine
|
24
|
+
raise "The primary machine in #{machine_group} is not in a running state" unless machine[:aws_state] == 'running'
|
25
|
+
cmd = "ssh -i #{keypairpath} #{user}@#{machine[:dns_name]}"
|
26
|
+
puts cmd
|
27
|
+
system(cmd) unless @print
|
28
|
+
end
|
29
|
+
|
30
|
+
# Start EC2 instances to run the stage.
|
31
|
+
# Returns a hash in the same format as +instances+
|
32
|
+
def build_stage
|
33
|
+
|
34
|
+
rig = running_instance_groups(@app_group)
|
35
|
+
raise "You already have a stage: #{rig.join(', ')}" unless rig.empty?
|
36
|
+
|
37
|
+
root_keypair_name = File.basename(@app_root_keypair)
|
38
|
+
|
39
|
+
# This is read by the Rudy start up script /etc/init.d/rudy-ec2-startup
|
40
|
+
user_data = {
|
41
|
+
:dbmaster => "localhost",
|
42
|
+
:role => "FE",
|
43
|
+
:env => "stage"
|
44
|
+
}
|
45
|
+
|
46
|
+
ret = @ec2.run_instances(@app_ami, 1, 1, [@app_group], root_keypair_name, user_data.to_yaml, 'public')
|
47
|
+
puts "The instance has started. Please wait while it boots..."
|
48
|
+
puts "Check '#{$0} state' for aws_state 'running'."
|
49
|
+
puts "Then run '#{$0} stage --start"
|
50
|
+
associate_address
|
51
|
+
ret
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Start (rake bootstrap, etc...) the rails app on the stage
|
56
|
+
def start_stage
|
57
|
+
|
58
|
+
rig = running_instance_groups(@app_group)
|
59
|
+
raise "There is no stage start!" if rig.empty?
|
60
|
+
cmds =[]
|
61
|
+
cmds.each do |cmd|
|
62
|
+
puts cmd
|
63
|
+
`#{cmd}`
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
# Shutdown all instances in the stage security group
|
69
|
+
def destroy_stage
|
70
|
+
|
71
|
+
rig = running_instance_groups(@app_group)
|
72
|
+
raise "There is no stage to destroy!" if rig.empty?
|
73
|
+
stopped = []
|
74
|
+
rig.each do |inst|
|
75
|
+
stopped << inst[:aws_instance_id]
|
76
|
+
end
|
77
|
+
destroy_instances(stopped)
|
78
|
+
end
|
79
|
+
|
80
|
+
#def svn_tag_exists?(rtag, prefix)
|
81
|
+
# return false if rtag.empty?
|
82
|
+
# `svn info tag/`
|
83
|
+
#end
|
84
|
+
|
85
|
+
def tag_release
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
__END__
|
94
|
+
|
95
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
|
4
|
+
module Rudy
|
5
|
+
module Command
|
6
|
+
class Groups < Rudy::Command::Base
|
7
|
+
|
8
|
+
def print_groups(name=nil)
|
9
|
+
name = machine_group if name.nil? && !@all
|
10
|
+
@ec2.groups.list(name).each do |grp|
|
11
|
+
print_group grp
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_group(name=nil)
|
16
|
+
name = machine_group if name.nil?
|
17
|
+
raise "The group #{name} already exists" if @ec2.groups.exists?(name)
|
18
|
+
|
19
|
+
puts "Creating group #{name}"
|
20
|
+
@ec2.groups.create(name)
|
21
|
+
|
22
|
+
modify_group name
|
23
|
+
end
|
24
|
+
|
25
|
+
def modify_group(name=nil)
|
26
|
+
name = machine_group if name.nil?
|
27
|
+
raise "The group #{name} does not exist" unless @ec2.groups.exists?(name)
|
28
|
+
|
29
|
+
@addresses = [Rudy::Utils::external_ip_address] if @addresses.nil?
|
30
|
+
@ports = [22,80,443] if @ports.nil?
|
31
|
+
@protocols = ["tcp"] if @protocols.nil?
|
32
|
+
|
33
|
+
# Make sure the IP addresses have ranges
|
34
|
+
@addresses.collect! { |ip| (ip.match /\/\d+/) ? ip : "#{ip}/32" }
|
35
|
+
|
36
|
+
@protocols.each do |protocol|
|
37
|
+
puts "Adding ports #{@ports.join(',')} (#{protocol}) for #{@addresses.join(', ')}"
|
38
|
+
@addresses.each do |address|
|
39
|
+
@ports.each do |port|
|
40
|
+
@ec2.groups.modify(name, port, port, protocol, address)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
print_groups name
|
46
|
+
end
|
47
|
+
|
48
|
+
def destroy_group(name=nil)
|
49
|
+
name = machine_group if name.nil?
|
50
|
+
raise "The group #{name} does not exist" unless @ec2.groups.exists?(name)
|
51
|
+
|
52
|
+
puts "Destroying group #{name}"
|
53
|
+
@ec2.groups.destroy(name)
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|