rudy 0.3.0 → 0.3.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/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