rudy 0.3.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rudy +39 -9
- data/lib/rudy.rb +59 -20
- data/lib/rudy/aws/ec2.rb +4 -0
- data/lib/rudy/command/base.rb +26 -13
- data/lib/rudy/command/disks.rb +101 -26
- data/lib/rudy/command/environment.rb +51 -57
- data/lib/rudy/command/instances.rb +26 -4
- data/lib/rudy/metadata/config.rb +8 -0
- data/lib/storable.rb +4 -1
- data/rudy.gemspec +2 -3
- data/support/rudy-ec2-startup +136 -129
- metadata +3 -4
- data/lib/rudy/command/commit.rb +0 -10
- data/lib/rudy/metadata/ec2startup.rb +0 -2
data/bin/rudy
CHANGED
@@ -27,7 +27,7 @@ require 'drydock'
|
|
27
27
|
require 'rudy'
|
28
28
|
extend Drydock
|
29
29
|
|
30
|
-
|
30
|
+
|
31
31
|
|
32
32
|
default :commands
|
33
33
|
|
@@ -35,11 +35,17 @@ default :commands
|
|
35
35
|
global_usage "rudy [global options] COMMAND [command options]"
|
36
36
|
global_option :A, :access_key, "AWS Access Key", String
|
37
37
|
global_option :S, :secret_key, "AWS Secret Access Key", String
|
38
|
+
global_option :R, :region, String, "Connect to a specific EC2 region (default: #{Rudy::DEFAULT_REGION})"
|
39
|
+
|
40
|
+
#global_option :z, :zone, String, "Connect to a specific EC2 zone (default: #{Rudy::DEFAULT_ZONE})"
|
41
|
+
global_option :e, :environment, String, "Connect to the specified environment (default: #{Rudy::DEFAULT_ENVIRONMENT})"
|
42
|
+
global_option :r, :role, String, "Connect to a machine with the specified role (defalt: #{Rudy::DEFAULT_ROLE})"
|
43
|
+
global_option :p, :position, String, "Position in the machine in its group (default: #{Rudy::DEFAULT_POSITION})"
|
44
|
+
|
45
|
+
global_option :u, :user, String, "Provide a username (default: #{Rudy::DEFAULT_USER})"
|
46
|
+
|
47
|
+
#global_option :c, :config, String, "Specify the config file to read (default: #{Rudy::RUDY_CONFIG})"
|
38
48
|
|
39
|
-
global_option :p, :position, String, "Position in the machine in its group (default: 01)"
|
40
|
-
global_option :u, :user, String, "Provide a username (default: rudy)"
|
41
|
-
global_option :e, :environment, String, "Connect to the specified environment (default: stage)"
|
42
|
-
global_option :r, :role, String, "Connect to a machine with the specified role (defalt: app)"
|
43
49
|
|
44
50
|
global_option :V, :version, "Display version number" do
|
45
51
|
puts "Rudy version: #{Rudy::VERSION}"
|
@@ -49,8 +55,11 @@ global_option :v, :verbose, "Increase verbosity of output (i.e. -v or -vv or -vv
|
|
49
55
|
@verbose ||= 0
|
50
56
|
@verbose += 1
|
51
57
|
end
|
58
|
+
global_option :q, :quiet, "Run with less output"
|
52
59
|
|
53
|
-
command :commands do
|
60
|
+
command :commands => Rudy::Command::Metadata do |obj|
|
61
|
+
obj.print_header
|
62
|
+
|
54
63
|
puts "Rudy can do all of these things:", ""
|
55
64
|
command_names.sort.each do |cmd|
|
56
65
|
puts " %16s" % cmd
|
@@ -60,6 +69,8 @@ command :commands do
|
|
60
69
|
puts
|
61
70
|
end
|
62
71
|
|
72
|
+
debug :off
|
73
|
+
|
63
74
|
usage "rudy init"
|
64
75
|
command :init => Rudy::Command::Metadata do |obj|
|
65
76
|
obj.setup
|
@@ -70,6 +81,13 @@ command :info => Rudy::Command::Metadata do |obj|
|
|
70
81
|
obj.info
|
71
82
|
end
|
72
83
|
|
84
|
+
option :a, :all, "Display config settings for all machines"
|
85
|
+
option :d, :defaults, "Display the default value for the supplied parameter"
|
86
|
+
usage "rudy [-f config-file] config [param-name]"
|
87
|
+
command :config => Rudy::Command::Environment do |obj, argv|
|
88
|
+
obj.config(argv.first)
|
89
|
+
end
|
90
|
+
|
73
91
|
|
74
92
|
option :e, :external, "Display only external IP address"
|
75
93
|
option :i, :internal, "Display only internal IP address"
|
@@ -172,7 +190,8 @@ command :disks => Rudy::Command::Disks do |obj, argv|
|
|
172
190
|
end
|
173
191
|
end
|
174
192
|
|
175
|
-
|
193
|
+
option :s, :snapshot, String, "Create a backup entry from an existing snapshot"
|
194
|
+
option :Z, :synchronize, "Check for and delete backup metadata with no snapshot. DOES NOT delete snapshots."
|
176
195
|
#option :T, :tidy, "Tidy existing backups"
|
177
196
|
option :D, :destroy, "Destroy a backup"
|
178
197
|
option :C, :create, "Create a backup"
|
@@ -181,7 +200,13 @@ command :backups => Rudy::Command::Disks do |obj, argv|
|
|
181
200
|
capture(:stderr) do
|
182
201
|
obj.print_header
|
183
202
|
if obj.create
|
184
|
-
obj.create_backup
|
203
|
+
obj.create_backup(argv.first)
|
204
|
+
elsif obj.synchronize
|
205
|
+
unless argv.empty?
|
206
|
+
puts "The disk you specified will be ignored."
|
207
|
+
argv.clear
|
208
|
+
end
|
209
|
+
obj.sync_backups
|
185
210
|
elsif obj.destroy
|
186
211
|
raise "No backup specified" if argv.empty?
|
187
212
|
#exit unless are_you_sure?
|
@@ -235,7 +260,7 @@ option :a, :account, String, "Your Amazon Account Number"
|
|
235
260
|
option :i, :image_name, String, "The name of the image"
|
236
261
|
option :b, :bucket_name, String, "The name of the bucket that will store the image"
|
237
262
|
option :C, :create, "Create an image"
|
238
|
-
option :D, :destroy, "Deregister an image"
|
263
|
+
option :D, :destroy, "Deregister an image (currently _does not_ remove images files from S3)"
|
239
264
|
usage "rudy images [-C -i name -b bucket -a account] [-D AMI-ID]"
|
240
265
|
command :images => Rudy::Command::Images do |obj, argv|
|
241
266
|
capture(:stderr) do
|
@@ -245,14 +270,19 @@ command :images => Rudy::Command::Images do |obj, argv|
|
|
245
270
|
puts "Make sure the machine is clean. I don't want archive no crud!"
|
246
271
|
exit unless are_you_sure?
|
247
272
|
obj.create_image
|
273
|
+
|
248
274
|
elsif obj.destroy
|
249
275
|
obj.deregister(argv.first)
|
276
|
+
|
250
277
|
else
|
251
278
|
obj.print_images
|
252
279
|
end
|
280
|
+
|
253
281
|
end
|
254
282
|
end
|
255
283
|
|
284
|
+
|
285
|
+
|
256
286
|
command :stage => Rudy::Command::Stage do |obj|
|
257
287
|
capture(:stderr) do
|
258
288
|
obj.print_header
|
data/lib/rudy.rb
CHANGED
@@ -7,6 +7,65 @@ require 'yaml'
|
|
7
7
|
require 'console'
|
8
8
|
require 'storable'
|
9
9
|
|
10
|
+
module Rudy #:nodoc:
|
11
|
+
RUDY_DOMAIN = ENV['RUDY_DOMAIN'] || "rudy_state"
|
12
|
+
RUDY_DELIM = ENV['RUDY_DELIM'] || '-'
|
13
|
+
RUDY_CONFIG = File.join(ENV['HOME'] || ENV['USERPROFILE'], '.rudy')
|
14
|
+
|
15
|
+
DEFAULT_REGION = ENV['EC2_DEFAULT_REGION'] || 'us-east-1'
|
16
|
+
DEFAULT_ZONE = ENV['EC2_DEFAULT_ZONE'] || 'us-east-1b'
|
17
|
+
DEFAULT_ENVIRONMENT = ENV['EC2_DEFAULT_ENVIRONMENT'] || 'stage'
|
18
|
+
DEFAULT_ROLE = ENV['EC2_DEFAULT_ROLE'] || 'app'
|
19
|
+
DEFAULT_POSITION = ENV['EC2_DEFAULT_POSITION'] || '01'
|
20
|
+
|
21
|
+
DEFAULT_USER = ENV['EC2_DEFAULT_USER'] || 'rudy'
|
22
|
+
|
23
|
+
module VERSION #:nodoc:
|
24
|
+
MAJOR = 0.freeze unless defined? MAJOR
|
25
|
+
MINOR = 3.freeze unless defined? MINOR
|
26
|
+
TINY = 2.freeze unless defined? TINY
|
27
|
+
def self.to_s
|
28
|
+
[MAJOR, MINOR, TINY].join('.')
|
29
|
+
end
|
30
|
+
def self.to_f
|
31
|
+
self.to_s.to_f
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Determine if we're running directly on EC2 or
|
36
|
+
# "some other machine". We do this by checking if
|
37
|
+
# the file /etc/ec2/instance-id exists. This
|
38
|
+
# file is written by /etc/init.d/rudy-ec2-startup.
|
39
|
+
# NOTE: Is there a way to know definitively that this is EC2?
|
40
|
+
# We could make a request to the metadata IP addresses.
|
41
|
+
def self.in_situ?
|
42
|
+
File.exists?('/etc/ec2/instance-id')
|
43
|
+
end
|
44
|
+
|
45
|
+
class Config < Storable
|
46
|
+
|
47
|
+
attr_accessor :path
|
48
|
+
|
49
|
+
field :userdata => Hash
|
50
|
+
|
51
|
+
def initialize(args={:path => nil})
|
52
|
+
@path = args[:path] || RUDY_CONFIG
|
53
|
+
@userdata = {
|
54
|
+
'default' => {
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def get(name)
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
def exists?
|
64
|
+
File.exists?(@path)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
10
69
|
require 'rudy/aws'
|
11
70
|
require 'rudy/metadata'
|
12
71
|
require 'rudy/scm/svn'
|
@@ -26,26 +85,6 @@ rescue LoadError => ex
|
|
26
85
|
exit 1
|
27
86
|
end
|
28
87
|
|
29
|
-
module Rudy #:nodoc:
|
30
|
-
RUDY_DOMAIN = ENV['RUDY_DOMAIN'] || "rudy_state"
|
31
|
-
RUDY_DELIM = ENV['RUDY_DELIM'] || '-'
|
32
|
-
DEFAULT_REGION = ENV['EC2_DEFAULT_REGION'] || 'us-east-1'
|
33
|
-
DEFAULT_ZONE = ENV['EC2_DEFAULT_ZONE'] || 'us-east-1b'
|
34
|
-
|
35
|
-
module VERSION #:nodoc:
|
36
|
-
MAJOR = 0.freeze unless defined? MAJOR
|
37
|
-
MINOR = 3.freeze unless defined? MINOR
|
38
|
-
TINY = 0.freeze unless defined? TINY
|
39
|
-
def self.to_s
|
40
|
-
[MAJOR, MINOR, TINY].join('.')
|
41
|
-
end
|
42
|
-
def self.to_f
|
43
|
-
self.to_s.to_f
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
88
|
|
50
89
|
# Capture STDOUT or STDERR to prevent it from being printed.
|
51
90
|
#
|
data/lib/rudy/aws/ec2.rb
CHANGED
data/lib/rudy/command/base.rb
CHANGED
@@ -31,6 +31,8 @@ module Rudy
|
|
31
31
|
attr_reader :domains
|
32
32
|
attr_reader :machine_images
|
33
33
|
|
34
|
+
attr_reader :config
|
35
|
+
|
34
36
|
def init
|
35
37
|
@access_key = ENV['AWS_ACCESS_KEY'] unless @access_key
|
36
38
|
@secret_key = ENV['AWS_SECRET_KEY'] unless @secret_key
|
@@ -42,14 +44,16 @@ module Rudy
|
|
42
44
|
if ENV['RUDY_SVN_BASE']
|
43
45
|
@scm = Rudy::SCM::SVN.new(ENV['RUDY_SVN_BASE'])
|
44
46
|
end
|
47
|
+
|
48
|
+
@config = File.exists?(RUDY_CONFIG) ? Rudy::Config.from_file(RUDY_CONFIG) : Rudy::Config.new
|
45
49
|
|
46
|
-
@user ||= 'rudy'
|
47
|
-
@environment ||= 'stage'
|
48
|
-
@role ||= 'app'
|
49
|
-
@position ||= '01'
|
50
|
-
|
51
|
-
@zone ||= DEFAULT_ZONE
|
52
50
|
@region ||= DEFAULT_REGION
|
51
|
+
@zone ||= DEFAULT_ZONE
|
52
|
+
@environment ||= DEFAULT_ENVIRONMENT
|
53
|
+
@role ||= DEFAULT_ROLE
|
54
|
+
@position ||= DEFAULT_POSITION
|
55
|
+
|
56
|
+
@user ||= DEFAULT_USER
|
53
57
|
|
54
58
|
@keypairs = {}
|
55
59
|
ENV.keys.select { |key| key.match /EC2_KEYPAIR/i }.each do |key|
|
@@ -81,12 +85,15 @@ module Rudy
|
|
81
85
|
end
|
82
86
|
end
|
83
87
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
+
if @access_key && @secret_key
|
89
|
+
@ec2 = Rudy::AWS::EC2.new(@access_key, @secret_key)
|
90
|
+
# RightAws::SdbInterface is on thin ice. No select support! Or sort!
|
91
|
+
@sdb = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
|
92
|
+
#@s3 = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
|
93
|
+
end
|
88
94
|
end
|
89
95
|
|
96
|
+
|
90
97
|
# Raises exceptions if the requested user does
|
91
98
|
# not have a valid keypair configured. (See: EC2_KEYPAIR_*)
|
92
99
|
def check_keys
|
@@ -119,8 +126,9 @@ module Rudy
|
|
119
126
|
@machine_images[machine_group]
|
120
127
|
end
|
121
128
|
|
129
|
+
# TODO: fix machine_group to include zone
|
122
130
|
def machine_name
|
123
|
-
[machine_group, @position].join(RUDY_DELIM)
|
131
|
+
[@zone, machine_group, @position].join(RUDY_DELIM)
|
124
132
|
end
|
125
133
|
|
126
134
|
def keypairname
|
@@ -213,8 +221,13 @@ module Rudy
|
|
213
221
|
!!!!!!!!! YOU ARE PLAYING WITH PRODUCTION !!!!!!!!!
|
214
222
|
=======================================================
|
215
223
|
=======================================================)
|
216
|
-
puts msg.color(:bright, :red, :bg_white)
|
217
|
-
|
224
|
+
puts msg.color(:bright, :red, :bg_white), $/ unless @quiet
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
if Rudy.in_situ?
|
229
|
+
msg = %q(============ THIS IS EC2 ============)
|
230
|
+
puts msg.color(:bright, :blue, :bg_white), $/ unless @quiet
|
218
231
|
end
|
219
232
|
|
220
233
|
end
|
data/lib/rudy/command/disks.rb
CHANGED
@@ -13,6 +13,43 @@ module Rudy
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
# Check for backups pointing to snapshots that don't exist.
|
17
|
+
def sync_backups
|
18
|
+
criteria = [@zone]
|
19
|
+
criteria += [@environment, @role] unless @all
|
20
|
+
|
21
|
+
puts "Looking for backup metadata with delinquent snapshots..."
|
22
|
+
to_be_deleted = {} # snap-id => backup
|
23
|
+
Rudy::MetaData::Backup.list(@sdb, *criteria).each do |backup|
|
24
|
+
to_be_deleted[backup.awsid] = backup unless @ec2.snapshots.exists?(backup.awsid)
|
25
|
+
end
|
26
|
+
|
27
|
+
if to_be_deleted.empty?
|
28
|
+
puts "All backups are in-sync with snapshots. Nothing to do."
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
puts
|
33
|
+
puts "These backup metadata will be deleted:"
|
34
|
+
to_be_deleted.each do |snap_id, backup|
|
35
|
+
puts "%s: %s" % [snap_id, backup.name]
|
36
|
+
end
|
37
|
+
|
38
|
+
puts
|
39
|
+
are_you_sure?
|
40
|
+
|
41
|
+
puts
|
42
|
+
puts "Deleting..."
|
43
|
+
to_be_deleted.each do |snap_id, backup|
|
44
|
+
print " -> #{backup.name}... "
|
45
|
+
@sdb.destroy(RUDY_DOMAIN, backup.name)
|
46
|
+
puts "done"
|
47
|
+
end
|
48
|
+
|
49
|
+
puts "Done!"
|
50
|
+
end
|
51
|
+
|
52
|
+
|
16
53
|
def destroy_backup(name)
|
17
54
|
puts "Destroying #{name}"
|
18
55
|
begin
|
@@ -36,53 +73,90 @@ module Rudy
|
|
36
73
|
puts "Done."
|
37
74
|
end
|
38
75
|
|
39
|
-
def create_backup
|
76
|
+
def create_backup(diskname=nil)
|
40
77
|
machine = find_current_machine
|
41
|
-
disks = Rudy::MetaData::Disk.list(@sdb, machine[:aws_availability_zone], @environment, @role, @position)
|
42
|
-
volumes = @ec2.instances.volumes(machine[:aws_instance_id])
|
43
|
-
|
44
|
-
puts "Machine: #{machine_name}"
|
45
78
|
|
79
|
+
disks = Rudy::MetaData::Disk.list(@sdb, machine[:aws_availability_zone], @environment, @role, @position)
|
46
80
|
raise "The machine #{machine_name} does not have any disk metadata" if disks.empty?
|
47
|
-
raise "The machine #{machine_name} does not have any volumes attached." if volumes.empty?
|
48
81
|
|
49
|
-
puts "
|
82
|
+
puts "Machine: #{machine_name}"
|
50
83
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
# TODO: Look for the disk based on the machine
|
57
|
-
raise "No disk associated to volume #{volume[:aws_id]}" unless disk
|
84
|
+
if @snapshot
|
85
|
+
raise "You must supply a diskname when using an existing snapshot" unless diskname
|
86
|
+
raise "The snapshot #{@snapshot} does not exist" unless @ec2.snapshots.exists?(@snapshot)
|
87
|
+
disk = Rudy::MetaData::Disk.get(@sdb, diskname)
|
58
88
|
|
89
|
+
raise "The disk #{diskname} does not exist" unless disk
|
90
|
+
backup = Rudy::MetaData::Backup.new
|
91
|
+
backup.awsid = @snapshot
|
59
92
|
backup.time_stamp
|
60
|
-
|
61
|
-
|
93
|
+
|
62
94
|
# Populate machine infos
|
63
95
|
[:zone, :environment, :role, :position].each do |n|
|
64
96
|
val = instance_variable_get("@#{n}")
|
65
97
|
backup.send("#{n}=", val) if val
|
66
98
|
end
|
67
|
-
|
99
|
+
|
68
100
|
# Populate disk infos
|
69
101
|
[:path, :size].each do |n|
|
70
102
|
backup.send("#{n}=", disk.send(n)) if disk.send(n)
|
71
103
|
end
|
72
104
|
|
73
|
-
snap = @ec2.snapshots.create(volume[:aws_id])
|
74
|
-
|
75
|
-
if !snap || !snap.is_a?(Hash)
|
76
|
-
puts "There was an unknown problem creating #{backup.name}. Continuing..."
|
77
|
-
next
|
78
|
-
end
|
79
|
-
|
80
|
-
backup.awsid = snap[:aws_id]
|
81
105
|
|
82
106
|
Rudy::MetaData::Backup.save(@sdb, backup)
|
83
107
|
|
84
108
|
puts backup.name
|
109
|
+
|
110
|
+
else
|
111
|
+
volumes = @ec2.instances.volumes(machine[:aws_instance_id])
|
112
|
+
raise "The machine #{machine_name} does not have any volumes attached." if volumes.empty?
|
113
|
+
|
114
|
+
puts "#{disks.size} Disk(s) defined with #{volumes.size} Volume(s) running"
|
115
|
+
|
116
|
+
volumes.each do |volume|
|
117
|
+
print "Volume #{volume[:aws_id]}... "
|
118
|
+
disk = Rudy::MetaData::Disk.from_volume(@sdb, volume[:aws_id])
|
119
|
+
backup = Rudy::MetaData::Backup.new
|
120
|
+
|
121
|
+
# TODO: Look for the disk based on the machine
|
122
|
+
raise "No disk associated to volume #{volume[:aws_id]}" unless disk
|
123
|
+
|
124
|
+
backup.volume = volume[:aws_id]
|
125
|
+
|
126
|
+
snap = @ec2.snapshots.create(volume[:aws_id])
|
127
|
+
|
128
|
+
backup.awsid = snap[:aws_id]
|
129
|
+
|
130
|
+
if !snap || !snap.is_a?(Hash)
|
131
|
+
puts "There was an unknown problem creating #{backup.name}. Continuing with the next volume..."
|
132
|
+
next
|
133
|
+
end
|
134
|
+
|
135
|
+
backup.time_stamp
|
136
|
+
|
137
|
+
# Populate machine infos
|
138
|
+
[:zone, :environment, :role, :position].each do |n|
|
139
|
+
val = instance_variable_get("@#{n}")
|
140
|
+
backup.send("#{n}=", val) if val
|
141
|
+
end
|
142
|
+
|
143
|
+
# Populate disk infos
|
144
|
+
[:path, :size].each do |n|
|
145
|
+
backup.send("#{n}=", disk.send(n)) if disk.send(n)
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
Rudy::MetaData::Backup.save(@sdb, backup)
|
150
|
+
|
151
|
+
puts backup.name
|
152
|
+
|
153
|
+
end
|
154
|
+
|
85
155
|
end
|
156
|
+
|
157
|
+
|
158
|
+
|
159
|
+
|
86
160
|
end
|
87
161
|
|
88
162
|
|
@@ -101,7 +175,8 @@ module Rudy
|
|
101
175
|
|
102
176
|
print_disks
|
103
177
|
end
|
104
|
-
|
178
|
+
|
179
|
+
|
105
180
|
def print_disks
|
106
181
|
criteria = [@zone]
|
107
182
|
criteria += [@environment, @role] unless @all
|
@@ -14,7 +14,58 @@ module Rudy
|
|
14
14
|
module Command
|
15
15
|
class Environment < Rudy::Command::Base
|
16
16
|
|
17
|
+
|
18
|
+
# Display configuration from the local user data file (~/.rudy).
|
19
|
+
# This config contains user data which is sent to each EC2 when
|
20
|
+
# it's created.
|
21
|
+
#
|
22
|
+
# The primary purpose of this command is to give other apps a way
|
23
|
+
# to check various configuration values. (This is probably mostly
|
24
|
+
# useful on an instance itself).
|
25
|
+
#
|
26
|
+
# # Display the default value.
|
27
|
+
# $ rudy config -d param-name
|
28
|
+
#
|
29
|
+
# # Display the value for a specific machine.
|
30
|
+
# $ rudy -e prod -r db config param-name
|
31
|
+
#
|
32
|
+
# # Display all configuration
|
33
|
+
# $ rudy config --all
|
34
|
+
#
|
35
|
+
def config(name=nil)
|
36
|
+
return unless @config.exists?
|
37
|
+
puts "Config: #{@config.path}" if @verbose > 0
|
17
38
|
|
39
|
+
|
40
|
+
which = @defaults ? @user : machine_name
|
41
|
+
puts "Machine: #{which}" if @verbose > 0
|
42
|
+
|
43
|
+
return unless @config.userdata && @config.userdata.is_a?(Hash)
|
44
|
+
#return unless @config.userdata[which] && @config.userdata[which].is_a?(Hash)
|
45
|
+
|
46
|
+
# The .rudy config files have a different structure.
|
47
|
+
# There's no userdata -> which. Just the config settings.
|
48
|
+
if Rudy.in_situ?
|
49
|
+
if name && @config.userdata.has_key?(name)
|
50
|
+
puts @config.userdata[name]
|
51
|
+
else
|
52
|
+
puts @config.to_yaml
|
53
|
+
end
|
54
|
+
else
|
55
|
+
if name && @config.userdata.has_key?(which)
|
56
|
+
value = @config.userdata[which][name.to_s]
|
57
|
+
puts value if value
|
58
|
+
elsif @all
|
59
|
+
puts @config.to_yaml
|
60
|
+
else
|
61
|
+
value = @config.userdata[which]
|
62
|
+
puts value.to_yaml if value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
18
69
|
def connect
|
19
70
|
check_keys
|
20
71
|
machine = find_current_machine
|
@@ -33,64 +84,7 @@ module Rudy
|
|
33
84
|
end
|
34
85
|
|
35
86
|
|
36
|
-
# Start EC2 instances to run the stage.
|
37
|
-
# Returns a hash in the same format as +instances+
|
38
|
-
def build_stage
|
39
|
-
|
40
|
-
rig = running_instance_groups(@app_group)
|
41
|
-
raise "You already have a stage: #{rig.join(', ')}" unless rig.empty?
|
42
|
-
|
43
|
-
root_keypair_name = File.basename(@app_root_keypair)
|
44
|
-
|
45
|
-
# This is read by the Rudy start up script /etc/init.d/rudy-ec2-startup
|
46
|
-
user_data = {
|
47
|
-
:dbmaster => "localhost",
|
48
|
-
:role => "FE",
|
49
|
-
:env => "stage"
|
50
|
-
}
|
51
|
-
|
52
|
-
ret = @ec2.run_instances(@app_ami, 1, 1, [@app_group], root_keypair_name, user_data.to_yaml, 'public')
|
53
|
-
puts "The instance has started. Please wait while it boots..."
|
54
|
-
puts "Check '#{$0} state' for aws_state 'running'."
|
55
|
-
puts "Then run '#{$0} stage --start"
|
56
|
-
associate_address
|
57
|
-
ret
|
58
|
-
end
|
59
|
-
|
60
|
-
|
61
|
-
# Start (rake bootstrap, etc...) the rails app on the stage
|
62
|
-
def start_stage
|
63
|
-
|
64
|
-
rig = running_instance_groups(@app_group)
|
65
|
-
raise "There is no stage start!" if rig.empty?
|
66
|
-
cmds =[]
|
67
|
-
cmds.each do |cmd|
|
68
|
-
puts cmd
|
69
|
-
`#{cmd}`
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|
73
|
-
|
74
|
-
# Shutdown all instances in the stage security group
|
75
|
-
def destroy_stage
|
76
|
-
|
77
|
-
rig = running_instance_groups(@app_group)
|
78
|
-
raise "There is no stage to destroy!" if rig.empty?
|
79
|
-
stopped = []
|
80
|
-
rig.each do |inst|
|
81
|
-
stopped << inst[:aws_instance_id]
|
82
|
-
end
|
83
|
-
destroy_instances(stopped)
|
84
|
-
end
|
85
87
|
|
86
|
-
#def svn_tag_exists?(rtag, prefix)
|
87
|
-
# return false if rtag.empty?
|
88
|
-
# `svn info tag/`
|
89
|
-
#end
|
90
|
-
|
91
|
-
def tag_release
|
92
|
-
|
93
|
-
end
|
94
88
|
|
95
89
|
end
|
96
90
|
end
|
@@ -50,14 +50,36 @@ module Rudy
|
|
50
50
|
raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
|
51
51
|
raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
|
52
52
|
|
53
|
-
|
54
|
-
|
53
|
+
|
54
|
+
machine_data = {
|
55
|
+
# Give the machine an identity
|
56
|
+
:zone => @zone,
|
57
|
+
:environment => @environment,
|
55
58
|
:role => @role,
|
56
|
-
:
|
59
|
+
:position => @position,
|
60
|
+
|
61
|
+
# Add hosts to the /etc/hosts file
|
62
|
+
:hosts => {
|
63
|
+
:dbmaster => "127.0.0.1",
|
64
|
+
},
|
65
|
+
|
66
|
+
:userdata => {}
|
57
67
|
}
|
58
68
|
|
69
|
+
# Populate userdata with settings from ~/.rudy
|
70
|
+
if @config.userdata.is_a?(Hash) && !@config.userdata.empty?
|
71
|
+
# Build a set of parameters for each user on the requested
|
72
|
+
# machine. Set default values first and overwrite. (TODO)
|
73
|
+
@config.userdata.each_pair do |n,hash|
|
74
|
+
user = n # TODO: should be prepared to parse "us-east-1b-stage-app-rudy" and "us-east-1b-stage-app-01-rudy"
|
75
|
+
machine_data[:userdata][user] = hash
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
59
79
|
puts "Starting an instance in #{machine_group}"
|
60
|
-
|
80
|
+
puts "with userdata:", machine_data.to_yaml
|
81
|
+
|
82
|
+
instances = @ec2.instances.create(@image, machine_group.to_s, File.basename(keypairpath), machine_data.to_yaml, @zone)
|
61
83
|
inst = instances.first
|
62
84
|
id, state = inst[:aws_instance_id], inst[:aws_state]
|
63
85
|
|
data/lib/storable.rb
CHANGED
@@ -86,8 +86,9 @@ class Storable
|
|
86
86
|
end
|
87
87
|
|
88
88
|
# Create a new instance of the object using data from file.
|
89
|
-
def self.from_file(file_path
|
89
|
+
def self.from_file(file_path, format='yaml')
|
90
90
|
raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
|
91
|
+
raise "#{self} doesn't support from_#{format}" unless self.respond_to?("from_#{format}")
|
91
92
|
format = format || File.extname(file_path).tr('.', '')
|
92
93
|
me = send("from_#{format}", read_file_to_array(file_path))
|
93
94
|
me.format = format
|
@@ -116,6 +117,8 @@ class Storable
|
|
116
117
|
|
117
118
|
if field_types[index] == Array
|
118
119
|
((value ||= []) << stored_value).flatten
|
120
|
+
elsif field_types[index] == Hash
|
121
|
+
value = stored_value
|
119
122
|
else
|
120
123
|
# SimpleDB stores attribute shit as lists of values
|
121
124
|
value = stored_value.first if stored_value.is_a?(Array) && stored_value.size == 1
|
data/rudy.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
@spec = Gem::Specification.new do |s|
|
2
2
|
s.name = "rudy"
|
3
|
-
s.version = "0.3.
|
3
|
+
s.version = "0.3.2"
|
4
4
|
s.summary = "Rudy is a handy staging and deployment tool for Amazon EC2."
|
5
5
|
s.description = "Rudy is a handy staging and deployment tool for Amazon EC2."
|
6
6
|
s.author = "Delano Mandelbaum"
|
@@ -27,7 +27,6 @@
|
|
27
27
|
lib/rudy/aws/simpledb.rb
|
28
28
|
lib/rudy/command/addresses.rb
|
29
29
|
lib/rudy/command/base.rb
|
30
|
-
lib/rudy/command/commit.rb
|
31
30
|
lib/rudy/command/disks.rb
|
32
31
|
lib/rudy/command/environment.rb
|
33
32
|
lib/rudy/command/groups.rb
|
@@ -38,8 +37,8 @@
|
|
38
37
|
lib/rudy/command/volumes.rb
|
39
38
|
lib/rudy/metadata.rb
|
40
39
|
lib/rudy/metadata/backup.rb
|
40
|
+
lib/rudy/metadata/config.rb
|
41
41
|
lib/rudy/metadata/disk.rb
|
42
|
-
lib/rudy/metadata/ec2startup.rb
|
43
42
|
lib/rudy/metadata/environment.rb
|
44
43
|
lib/rudy/scm/svn.rb
|
45
44
|
lib/rudy/utils.rb
|
data/support/rudy-ec2-startup
CHANGED
@@ -2,17 +2,14 @@
|
|
2
2
|
|
3
3
|
# what: Rudy EC2 startup script
|
4
4
|
# who: delano@solutious.com
|
5
|
-
# when: 2009-02-
|
5
|
+
# when: 2009-02-25 (rev: 4)
|
6
|
+
|
7
|
+
# NOTE: This is a prototype version of this script. A cleaner version
|
8
|
+
# with better documentation is forthcoming.
|
6
9
|
|
7
10
|
# Runs when an ec2 instance startups up.
|
8
11
|
# Grabs configuration from the run time user data and stores it locally.
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# :dbmaster: IP ADDRESS or LOCAL HOSTNAME (default: localhost)
|
12
|
-
# :role: app, db, etc... (default: app)
|
13
|
-
# :env: stage, prod (default: stage)
|
14
|
-
# :access_key: amazon access key
|
15
|
-
# :secret_key: amazon secret key
|
12
|
+
# See Rudy::Command::Instances#start_instance for the expected yaml format.
|
16
13
|
|
17
14
|
# Put this in /etc/init.d. Then:
|
18
15
|
# * chmod 755 rudy-ec2-startup
|
@@ -23,145 +20,155 @@
|
|
23
20
|
# * cd /etc/rc5.d
|
24
21
|
# * ln -s ../init.d/rudy-ec2-startup S17rudy
|
25
22
|
|
26
|
-
# NOTE: This is a prototype version of this script. A cleaner version
|
27
|
-
# with better documentation is forthcoming.
|
28
|
-
|
29
|
-
|
30
23
|
require 'yaml'
|
31
24
|
require 'resolv'
|
32
25
|
|
33
26
|
LOGFILE = '/var/log/rudy-ec2-startup'
|
34
27
|
USERDATA = 'http://169.254.169.254/2008-02-01/user-data'
|
35
28
|
METADATA = 'http://169.254.169.254/2008-02-01/meta-data'
|
36
|
-
METADATAPARAMS = ['instance-id', 'instance-type']
|
37
29
|
|
30
|
+
#LOGFILE = '/tmp/rudy-ec2-startup'
|
31
|
+
#USERDATA = 'http://127.0.0.1/2008-02-01/user-data'
|
32
|
+
#METADATA = 'http://127.0.0.1/2008-02-01/meta-data'
|
38
33
|
|
39
|
-
|
40
|
-
metadata = {}
|
41
|
-
|
42
|
-
begin
|
43
|
-
METADATAPARAMS.each do |param|
|
44
|
-
log " ---> #{param}"
|
45
|
-
metadata[param.to_s] = run("curl -s #{METADATA}/#{param}")
|
46
|
-
end
|
47
|
-
rescue => ex
|
48
|
-
log("Problem getting metadata: #{ex.message}")
|
49
|
-
end
|
50
|
-
metadata
|
51
|
-
end
|
34
|
+
METADATAPARAMS = ['instance-id', 'instance-type']
|
52
35
|
|
53
|
-
|
54
|
-
IO.popen(command, 'r+') do |io|
|
55
|
-
#io.puts input
|
56
|
-
#io.close_write
|
57
|
-
return io.read
|
58
|
-
end
|
59
|
-
end
|
36
|
+
RUDY_CONFIG_FILE='.rudy'
|
60
37
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
38
|
+
module Rudy
|
39
|
+
module EC2Startup
|
40
|
+
extend self
|
41
|
+
|
42
|
+
VERSION = 4.freeze
|
43
|
+
|
44
|
+
def process_config
|
45
|
+
begin
|
46
|
+
File.unlink(LOGFILE) if File.exists?(LOGFILE)
|
47
|
+
log "RUDY (rev#{VERSION}) is in the house"
|
48
|
+
|
49
|
+
log "Grabing configuration..."
|
50
|
+
config_yaml = run("curl -s #{USERDATA}")
|
51
|
+
|
52
|
+
log "Grabing meta-data..."
|
53
|
+
metadata = get_metadata
|
54
|
+
|
55
|
+
log "Processing metadata..."
|
56
|
+
METADATAPARAMS.each do |name|
|
57
|
+
default = name.gsub('instance-', '')
|
58
|
+
value = (metadata[name] && !metadata[name].empty?) ? metadata[name] : default
|
59
|
+
log " ---> #{name}: #{value}"
|
60
|
+
write_to_file("/etc/ec2/#{name}", value, "w")
|
61
|
+
end
|
62
|
+
|
63
|
+
log "Processing userdata..."
|
64
|
+
|
65
|
+
if !config_yaml || config_yaml.empty?
|
66
|
+
raise "Nothing to process (did you supply user-data when you created the instance)"
|
67
|
+
end
|
68
|
+
|
69
|
+
config = YAML::load(config_yaml)
|
70
|
+
config ||= {}
|
71
|
+
|
72
|
+
zone = config[:zone] ? config[:zone].downcase : "zone"
|
73
|
+
environment = config[:environment] ? config[:environment].downcase : "env"
|
74
|
+
role = config[:role] ? config[:role].downcase : "role"
|
75
|
+
position = config[:position] ? config[:position].downcase : "00"
|
76
|
+
|
77
|
+
hostname = [zone, environment, role, position].join('-')
|
78
|
+
log "Setting hostname: #{hostname}"
|
79
|
+
`hostname #{hostname}`
|
80
|
+
|
81
|
+
config[:hosts] ||= {}
|
82
|
+
config[:hosts][hostname] = '127.0.0.1'
|
83
|
+
|
84
|
+
if config[:userdata] && config[:userdata].is_a?(Hash)
|
85
|
+
# TODO: How to we get the path to any user's home directory?
|
86
|
+
# (when we're not running as that user.)
|
87
|
+
config[:userdata].each_pair do |n,hash|
|
88
|
+
fileparts = (n === "root") ? ['/root'] : ['/home', n]
|
89
|
+
fileparts << RUDY_CONFIG_FILE
|
90
|
+
filepath = File.join(fileparts)
|
91
|
+
log "Writing to #{filepath}"
|
92
|
+
user_data = {
|
93
|
+
:userdata => hash
|
94
|
+
}
|
95
|
+
write_to_file(filepath, user_data.to_yaml, "w")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if config[:hosts] && config[:hosts].is_a?(Hash)
|
100
|
+
log "Updating /etc/hosts..."
|
101
|
+
config[:hosts].each_pair do |name, address|
|
102
|
+
# Respect existing entries
|
103
|
+
next if read_file('/etc/hosts') =~ /#{name}/
|
104
|
+
|
105
|
+
ip_address = (address !~ /^\d.+/) ? Resolv.getaddress(address) : address
|
106
|
+
|
107
|
+
log " ---> #{name}: #{ip_address}"
|
108
|
+
write_to_file("/etc/hosts", "\n#{ip_address}\t#{name}\n")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
log "Done!"
|
113
|
+
|
114
|
+
rescue => ex
|
115
|
+
log "ERROR: #{ex.message}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def get_metadata
|
121
|
+
metadata = {}
|
122
|
+
|
123
|
+
begin
|
124
|
+
METADATAPARAMS.each do |param|
|
125
|
+
log " ---> #{param}"
|
126
|
+
metadata[param.to_s] = run("curl -s #{METADATA}/#{param}")
|
127
|
+
metadata[param.to_s] = nil if metadata[param.to_s].empty?
|
128
|
+
end
|
129
|
+
rescue => ex
|
130
|
+
log("Problem getting metadata: #{ex.message}")
|
131
|
+
end
|
132
|
+
metadata
|
133
|
+
end
|
65
134
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
135
|
+
def run(command, input='')
|
136
|
+
IO.popen(command, 'r+') do |io|
|
137
|
+
io.read
|
138
|
+
end
|
139
|
+
end
|
71
140
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
141
|
+
def get_formatted_time
|
142
|
+
t = Time.now
|
143
|
+
t_out = t.strftime("%H:%M:%S%p (%m/%d/%Y)")
|
144
|
+
end
|
75
145
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
82
|
-
contents
|
83
|
-
end
|
146
|
+
def write_to_file (filename, s, type='a')
|
147
|
+
f = File.open(filename,type)
|
148
|
+
f.puts s
|
149
|
+
f.close
|
150
|
+
end
|
84
151
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
puts msg
|
89
|
-
end
|
152
|
+
def read_file(path)
|
153
|
+
read_file_to_array(path).join('')
|
154
|
+
end
|
90
155
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
log "Grabing meta-data..."
|
98
|
-
metadata = get_metadata
|
99
|
-
|
100
|
-
raise "Ooooooops, no configuration" if !config_yaml || config_yaml.empty?
|
101
|
-
|
102
|
-
config = YAML::load(config_yaml)
|
103
|
-
|
104
|
-
if config && config[:role]
|
105
|
-
myrole = config[:role].downcase
|
106
|
-
|
107
|
-
else
|
108
|
-
myrole = "app"
|
109
|
-
end
|
110
|
-
|
111
|
-
log "My role is: #{myrole}"
|
112
|
-
|
113
|
-
env = config && config[:env] ? config[:env] : "stage"
|
114
|
-
|
115
|
-
unless read_file('/etc/hosts') =~ /dbmaster/
|
116
|
-
if config && config[:dbmaster]
|
117
|
-
log "Updating dbmaster in /etc/hosts..."
|
118
|
-
|
119
|
-
case config[:dbmaster]
|
120
|
-
when /^\d.+/
|
121
|
-
ip_address = config[:dbmaster]
|
122
|
-
else
|
123
|
-
ip_address = Resolv.getaddress(config[:dbmaster])
|
156
|
+
def read_file_to_array(path)
|
157
|
+
contents = []
|
158
|
+
return contents unless File.exists?(path)
|
159
|
+
open(path, 'r') do |l|
|
160
|
+
contents = l.readlines
|
124
161
|
end
|
125
|
-
|
126
|
-
log "The IP address for my dbmaster is: #{ip_address}"
|
127
|
-
write_to_file("/etc/hosts", "\n#{ip_address}\tdbmaster\n")
|
128
|
-
|
129
|
-
else
|
130
|
-
write_to_file("/etc/hosts", "\n127.0.0.1\tdbmaster\n")
|
162
|
+
contents
|
131
163
|
end
|
132
|
-
end
|
133
|
-
|
134
164
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
mytype = "unknown"
|
144
|
-
if metadata['instance-type']
|
145
|
-
mytype = metadata['instance-type']
|
146
|
-
log "My instance type: #{metadata['instance-type']}"
|
147
|
-
write_to_file("/etc/instance-type", metadata['instance-type'], "w")
|
148
|
-
end
|
149
|
-
|
150
|
-
|
151
|
-
hostname = "#{env}-#{myrole}-#{mytype.gsub('.', '')}-#{myid}"
|
152
|
-
log "Setting hostname to #{hostname}"
|
153
|
-
`hostname #{hostname}`
|
154
|
-
|
155
|
-
unless read_file('/etc/hosts') =~ /#{hostname}/
|
156
|
-
log "Adding an entry to /etc/hosts for: #{hostname}"
|
157
|
-
write_to_file("/etc/hosts", "\n127.0.0.1\t#{hostname}")
|
165
|
+
def log(s)
|
166
|
+
msg = "#{get_formatted_time}: #{s}"
|
167
|
+
write_to_file(LOGFILE, msg)
|
168
|
+
puts msg
|
169
|
+
end
|
170
|
+
|
158
171
|
end
|
159
|
-
|
160
|
-
log "Done!"
|
161
|
-
|
162
|
-
rescue => ex
|
163
|
-
log ex.message
|
164
172
|
end
|
165
|
-
|
166
|
-
|
167
173
|
|
174
|
+
Rudy::EC2Startup.process_config
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rudy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delano Mandelbaum
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-02-
|
12
|
+
date: 2009-02-26 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -40,7 +40,6 @@ files:
|
|
40
40
|
- lib/rudy/aws/simpledb.rb
|
41
41
|
- lib/rudy/command/addresses.rb
|
42
42
|
- lib/rudy/command/base.rb
|
43
|
-
- lib/rudy/command/commit.rb
|
44
43
|
- lib/rudy/command/disks.rb
|
45
44
|
- lib/rudy/command/environment.rb
|
46
45
|
- lib/rudy/command/groups.rb
|
@@ -51,8 +50,8 @@ files:
|
|
51
50
|
- lib/rudy/command/volumes.rb
|
52
51
|
- lib/rudy/metadata.rb
|
53
52
|
- lib/rudy/metadata/backup.rb
|
53
|
+
- lib/rudy/metadata/config.rb
|
54
54
|
- lib/rudy/metadata/disk.rb
|
55
|
-
- lib/rudy/metadata/ec2startup.rb
|
56
55
|
- lib/rudy/metadata/environment.rb
|
57
56
|
- lib/rudy/scm/svn.rb
|
58
57
|
- lib/rudy/utils.rb
|
data/lib/rudy/command/commit.rb
DELETED