rudy 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.txt +6 -2
- data/README.rdoc +1 -1
- data/bin/rudy +65 -24
- data/lib/aws_sdb/error.rb +42 -0
- data/lib/aws_sdb/service.rb +215 -0
- data/lib/aws_sdb.rb +3 -0
- data/lib/console.rb +341 -0
- data/lib/rudy/aws/ec2.rb +45 -0
- data/lib/rudy/aws/simpledb.rb +5 -0
- data/lib/rudy/aws.rb +3 -0
- data/lib/rudy/command/base.rb +47 -23
- data/lib/rudy/command/disks.rb +109 -2
- data/lib/rudy/command/environment.rb +4 -12
- data/lib/rudy/command/images.rb +15 -2
- data/lib/rudy/command/stage.rb +1 -1
- data/lib/rudy/command/volumes.rb +38 -1
- data/lib/rudy/metadata/backup.rb +160 -0
- data/lib/rudy/metadata/disk.rb +54 -23
- data/lib/rudy/metadata/ec2startup.rb +2 -0
- data/lib/rudy/metadata.rb +26 -0
- data/lib/rudy.rb +38 -7
- data/lib/storable.rb +20 -15
- data/rudy.gemspec +8 -1
- metadata +9 -2
data/lib/console.rb
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
|
2
|
+
# Adapted from: http://github.com/oneup/ruby-console/tree/tput
|
3
|
+
|
4
|
+
if __FILE__ == $0
|
5
|
+
puts "This is a module, don't run it from the command line."
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
|
9
|
+
class String
|
10
|
+
def color(*arg) # colorize a string
|
11
|
+
Console.color(arg) +
|
12
|
+
self +
|
13
|
+
Console.color(:default)
|
14
|
+
end
|
15
|
+
def printAt(*arg)
|
16
|
+
row = 0
|
17
|
+
col = 0
|
18
|
+
if not arg.is_a?(Array)
|
19
|
+
arg = [arg]
|
20
|
+
end
|
21
|
+
if arg.length > 0
|
22
|
+
row = arg[0]
|
23
|
+
end
|
24
|
+
if arg.length > 1
|
25
|
+
col = arg[1]
|
26
|
+
end
|
27
|
+
Cursor.save
|
28
|
+
Cursor.position = row, col
|
29
|
+
print self
|
30
|
+
Cursor.restore
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Console
|
35
|
+
def self.clear
|
36
|
+
system "tput clear"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.reset
|
40
|
+
system "tput reset"
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.color(*arg) # colorize a string
|
44
|
+
if arg[0].is_a?(Array)
|
45
|
+
arg = arg[0]
|
46
|
+
end
|
47
|
+
if arg.length == 0
|
48
|
+
arg = :default, :red, :bg_default
|
49
|
+
end
|
50
|
+
attribute = { # mapper for the attributes
|
51
|
+
:normal => 0,
|
52
|
+
:bright => 1,
|
53
|
+
:dim => 2,
|
54
|
+
:underscore => 4,
|
55
|
+
:blink => 5,
|
56
|
+
:reverse => 7,
|
57
|
+
:hidden => 8,
|
58
|
+
:default => 0
|
59
|
+
}
|
60
|
+
fg_color = { # mapper for the foreground color
|
61
|
+
:black => 30,
|
62
|
+
:red => 31,
|
63
|
+
:green => 32,
|
64
|
+
:yellow => 33,
|
65
|
+
:blue => 34,
|
66
|
+
:magenta => 35,
|
67
|
+
:cyan => 36,
|
68
|
+
:white => 37,
|
69
|
+
:default => 39
|
70
|
+
}
|
71
|
+
bg_color = { # mapper for the background color
|
72
|
+
:bg_black => 40,
|
73
|
+
:bg_red => 41,
|
74
|
+
:bg_green => 42,
|
75
|
+
:bg_yellow => 43,
|
76
|
+
:bg_blue => 44,
|
77
|
+
:bg_magenta => 45,
|
78
|
+
:bg_cyan => 46,
|
79
|
+
:bg_white => 47,
|
80
|
+
:bg_default => 49
|
81
|
+
}
|
82
|
+
if arg.length > 0 # turn symbols into numbers
|
83
|
+
arg[0] = attribute[arg[0]] # attributes
|
84
|
+
end
|
85
|
+
if arg.length > 1
|
86
|
+
arg[1] = fg_color[arg[1]] # foreground color
|
87
|
+
end
|
88
|
+
if arg.length > 2
|
89
|
+
arg[2] = bg_color[arg[2]] # background color
|
90
|
+
end
|
91
|
+
"\e[#{arg.join(";")}m" # magic ansi escape sequence
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.color=(arg)
|
95
|
+
if not arg.is_a?(Array)
|
96
|
+
arg = [arg]
|
97
|
+
end
|
98
|
+
if arg.length == 0
|
99
|
+
arg = :default, :red, :bg_default
|
100
|
+
end
|
101
|
+
print self.color(arg)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.width
|
105
|
+
`tput cols`.chomp.to_i
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.height
|
109
|
+
`tput lines`.chomp.to_i
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Cursor
|
114
|
+
def self.position
|
115
|
+
row = ""
|
116
|
+
col = ""
|
117
|
+
c = ""
|
118
|
+
|
119
|
+
termsettings = `stty -g`
|
120
|
+
system("stty raw -echo")
|
121
|
+
print "\e[6n"
|
122
|
+
while (c = STDIN.getc.chr) != ";"
|
123
|
+
if c == "\e" or c == "["
|
124
|
+
next
|
125
|
+
else
|
126
|
+
row += c
|
127
|
+
end
|
128
|
+
end
|
129
|
+
while (c = STDIN.getc.chr) != "R"
|
130
|
+
col += c
|
131
|
+
end
|
132
|
+
system("stty #{termsettings}")
|
133
|
+
|
134
|
+
[row, col]
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.position=(arg)
|
138
|
+
row = 0
|
139
|
+
col = 0
|
140
|
+
if not arg.is_a?(Array)
|
141
|
+
arg = [arg]
|
142
|
+
end
|
143
|
+
if arg.length > 0
|
144
|
+
row = arg[0]
|
145
|
+
end
|
146
|
+
if arg.length > 1
|
147
|
+
col = arg[1]
|
148
|
+
end
|
149
|
+
system "tput cup #{row} #{col}"
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.up(*arg)
|
153
|
+
if arg.length == 0
|
154
|
+
arg[0] == 1
|
155
|
+
end
|
156
|
+
print "\e[#{arg[0]}A"
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.down(*arg)
|
160
|
+
if arg.length == 0
|
161
|
+
arg[0] == 1
|
162
|
+
end
|
163
|
+
print "\e[#{arg[0]}B"
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.right(*arg)
|
167
|
+
if arg.length == 0
|
168
|
+
arg[0] == 1
|
169
|
+
end
|
170
|
+
system "tput cuf #{arg[0]}"
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.left(*arg)
|
174
|
+
if arg.length == 0
|
175
|
+
arg[0] == 1
|
176
|
+
end
|
177
|
+
system "tput cub #{arg[0]}"
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.save
|
181
|
+
system "tput sc"
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.restore
|
185
|
+
system "tput rc"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class Win
|
190
|
+
def initialize
|
191
|
+
@row = 1
|
192
|
+
@col = 1
|
193
|
+
@width = 10
|
194
|
+
@height = 5
|
195
|
+
@text = ""
|
196
|
+
@border = :single
|
197
|
+
@fg = :default
|
198
|
+
@bg = :bg_default
|
199
|
+
@bordercolor = :default
|
200
|
+
end
|
201
|
+
|
202
|
+
def width=(width)
|
203
|
+
@width = width
|
204
|
+
end
|
205
|
+
|
206
|
+
def width
|
207
|
+
@width
|
208
|
+
end
|
209
|
+
|
210
|
+
def height=(height)
|
211
|
+
@height = height
|
212
|
+
end
|
213
|
+
|
214
|
+
def height
|
215
|
+
@height
|
216
|
+
end
|
217
|
+
|
218
|
+
def text=(text)
|
219
|
+
@text = text
|
220
|
+
end
|
221
|
+
|
222
|
+
def text
|
223
|
+
@text
|
224
|
+
end
|
225
|
+
|
226
|
+
def border=(arg)
|
227
|
+
if not arg.is_a?(Array)
|
228
|
+
arg = [arg]
|
229
|
+
end
|
230
|
+
if arg.length > 0
|
231
|
+
@border = arg[0]
|
232
|
+
end
|
233
|
+
if arg.length > 1
|
234
|
+
@bordercolor = arg[1]
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def border
|
239
|
+
[@border, @bordercolor]
|
240
|
+
end
|
241
|
+
|
242
|
+
def position=(arg)
|
243
|
+
if not arg.is_a?(Array)
|
244
|
+
arg = [arg]
|
245
|
+
end
|
246
|
+
if arg.length > 0
|
247
|
+
@row = arg[0]
|
248
|
+
end
|
249
|
+
if arg.length > 1
|
250
|
+
@col = arg[1]
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def position
|
255
|
+
[@row, @col]
|
256
|
+
end
|
257
|
+
|
258
|
+
def fg=(fg)
|
259
|
+
@fg = fg
|
260
|
+
end
|
261
|
+
|
262
|
+
def fg
|
263
|
+
@fg
|
264
|
+
end
|
265
|
+
|
266
|
+
def bg=(bg)
|
267
|
+
@bg = bg
|
268
|
+
end
|
269
|
+
|
270
|
+
def bg
|
271
|
+
@bg
|
272
|
+
end
|
273
|
+
|
274
|
+
def drawBorder
|
275
|
+
single = {
|
276
|
+
:upper_left => "\u250C",
|
277
|
+
:upper_right => "\u2510",
|
278
|
+
:lower_left => "\u2514",
|
279
|
+
:lower_right => "\u2518",
|
280
|
+
:horizontal => "\u2500",
|
281
|
+
:vertical => "\u2502"
|
282
|
+
}
|
283
|
+
bold = {
|
284
|
+
:upper_left => "\u250D",
|
285
|
+
:upper_right => "\u2511",
|
286
|
+
:lower_left => "\u2515",
|
287
|
+
:lower_right => "\u2519",
|
288
|
+
:horizontal => "\u2501",
|
289
|
+
:vertical => "\u2503"
|
290
|
+
}
|
291
|
+
double = {
|
292
|
+
:upper_left => "\u2554",
|
293
|
+
:upper_right => "\u2557",
|
294
|
+
:lower_left => "\u255A",
|
295
|
+
:lower_right => "\u255D",
|
296
|
+
:horizontal => "\u2550",
|
297
|
+
:vertical => "\u2551"
|
298
|
+
}
|
299
|
+
round = {
|
300
|
+
:upper_left => "\u256D",
|
301
|
+
:upper_right => "\u256E",
|
302
|
+
:lower_left => "\u2570",
|
303
|
+
:lower_right => "\u256F",
|
304
|
+
:horizontal => "\u2500",
|
305
|
+
:vertical => "\u2502"
|
306
|
+
}
|
307
|
+
mapper = case @border
|
308
|
+
when :single then single
|
309
|
+
when :bold then bold
|
310
|
+
when :double then double
|
311
|
+
when :round then round
|
312
|
+
else single
|
313
|
+
end
|
314
|
+
(mapper[:upper_left] + mapper[:horizontal] * (@width - 2) + mapper[:upper_right]).color(:normal, @bordercolor, @bg).printAt @row, @col
|
315
|
+
(mapper[:lower_left] + mapper[:horizontal] * (@width - 2) + mapper[:lower_right]).color(:normal, @bordercolor, @bg).printAt @row + @height - 1, @col
|
316
|
+
0.upto(@height - 3) do |i|
|
317
|
+
(mapper[:vertical] + " " * (@width - 2) + mapper[:vertical]).color(:normal, @bordercolor, @bg).printAt @row + i + 1, @col
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def draw
|
322
|
+
text = @text.split("\n")
|
323
|
+
0.upto(text.length - 1) do |i|
|
324
|
+
if @border != :none
|
325
|
+
text[i] = text[i][0, @width - 2]
|
326
|
+
else
|
327
|
+
text[i] = text[i][0, @width]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
if @border == :none
|
331
|
+
0.upto((text.length > @height ? @height : text.length) - 1) do |i|
|
332
|
+
text[i].color(:normal, @fg, @bg).printAt @row + i , @col
|
333
|
+
end
|
334
|
+
else
|
335
|
+
drawBorder
|
336
|
+
0.upto(@text.length > @height - 3 ? @height - 3 : text.length) do |i|
|
337
|
+
text[i].to_s.color(:normal, @fg, @bg).printAt @row + i + 1, @col + 1
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
data/lib/rudy/aws/ec2.rb
CHANGED
@@ -14,10 +14,46 @@ module Rudy::AWS
|
|
14
14
|
@aws.describe_images_by_owner('self') || []
|
15
15
|
end
|
16
16
|
|
17
|
+
# +id+ AMI ID to deregister (ami-XXXXXXX)
|
18
|
+
# Returns true when successful. Otherwise throws an exception.
|
19
|
+
def deregister(id)
|
20
|
+
@aws.deregister_image(id)
|
21
|
+
end
|
22
|
+
|
23
|
+
# +path+ the S3 path to the manifest (bucket/file.manifest.xml)
|
24
|
+
# Returns the AMI ID when successful, otherwise throws an exception.
|
25
|
+
def register(path)
|
26
|
+
@aws.register_image(path)
|
27
|
+
end
|
17
28
|
end
|
29
|
+
class Snapshots
|
30
|
+
include Rudy::AWS::ObjectBase
|
31
|
+
|
32
|
+
def list
|
33
|
+
@aws.describe_snapshots || []
|
34
|
+
end
|
35
|
+
|
36
|
+
def create(vol_id)
|
37
|
+
@aws.create_snapshot(vol_id)
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy(snap_id)
|
41
|
+
@aws.delete_snapshot(snap_id)
|
42
|
+
end
|
43
|
+
|
44
|
+
def exists?(id)
|
45
|
+
list.each do |v|
|
46
|
+
return true if v[:aws_id] === id
|
47
|
+
end
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
18
53
|
class Volumes
|
19
54
|
include Rudy::AWS::ObjectBase
|
20
55
|
|
56
|
+
|
21
57
|
def list
|
22
58
|
list = @aws.describe_volumes() || []
|
23
59
|
list.select { |v| v[:aws_status] != "deleting" }
|
@@ -27,10 +63,18 @@ module Rudy::AWS
|
|
27
63
|
@aws.attach_volume(vol_id, inst_id, device)
|
28
64
|
end
|
29
65
|
|
66
|
+
def detach(vol_id)
|
67
|
+
@aws.detach_volume(vol_id)
|
68
|
+
end
|
69
|
+
|
30
70
|
def create(zone, size, snapshot=nil)
|
31
71
|
@aws.create_volume(snapshot, size, zone)
|
32
72
|
end
|
33
73
|
|
74
|
+
def destroy(vol_id)
|
75
|
+
@aws.delete_volume(vol_id)
|
76
|
+
end
|
77
|
+
|
34
78
|
def exists?(id)
|
35
79
|
list.each do |v|
|
36
80
|
return true if v[:aws_id] === id
|
@@ -38,6 +82,7 @@ module Rudy::AWS
|
|
38
82
|
false
|
39
83
|
end
|
40
84
|
|
85
|
+
|
41
86
|
end
|
42
87
|
|
43
88
|
class Instances
|
data/lib/rudy/aws/simpledb.rb
CHANGED
data/lib/rudy/aws.rb
CHANGED
@@ -20,6 +20,7 @@ module Rudy
|
|
20
20
|
attr_reader :addresses
|
21
21
|
attr_reader :groups
|
22
22
|
attr_reader :volumes
|
23
|
+
attr_reader :snapshots
|
23
24
|
attr_reader :aws
|
24
25
|
|
25
26
|
def initialize(access_key, secret_key)
|
@@ -28,6 +29,7 @@ module Rudy
|
|
28
29
|
@images = Rudy::AWS::EC2::Images.new(@aws)
|
29
30
|
@groups = Rudy::AWS::EC2::Groups.new(@aws)
|
30
31
|
@addresses = Rudy::AWS::EC2::Addresses.new(@aws)
|
32
|
+
@snapshots = Rudy::AWS::EC2::Snapshots.new(@aws)
|
31
33
|
@volumes = Rudy::AWS::EC2::Volumes.new(@aws)
|
32
34
|
end
|
33
35
|
|
@@ -51,6 +53,7 @@ module Rudy
|
|
51
53
|
|
52
54
|
def initialize(access_key, secret_key)
|
53
55
|
@aws = RightAws::SdbInterface.new(access_key, secret_key, {:logger => Logger.new(@@logger)})
|
56
|
+
@aws2 = AwsSdb::Service.new(:access_key_id => access_key, :secret_access_key => secret_key, :logger => Logger.new(@@logger))
|
54
57
|
@domains = Rudy::AWS::SimpleDB::Domains.new(@aws)
|
55
58
|
end
|
56
59
|
|
data/lib/rudy/command/base.rb
CHANGED
@@ -28,7 +28,7 @@ module Rudy
|
|
28
28
|
attr_reader :scm
|
29
29
|
|
30
30
|
attr_reader :rscripts
|
31
|
-
|
31
|
+
attr_reader :domains
|
32
32
|
attr_reader :machine_images
|
33
33
|
|
34
34
|
def init
|
@@ -81,8 +81,9 @@ module Rudy
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
@sdb = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
|
85
84
|
@ec2 = Rudy::AWS::EC2.new(@access_key, @secret_key)
|
85
|
+
# RightAws::SdbInterface is on thin ice. No select support! Or sort!
|
86
|
+
@sdb = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
|
86
87
|
#@s3 = Rudy::AWS::SimpleDB.new(@access_key, @secret_key)
|
87
88
|
end
|
88
89
|
|
@@ -90,7 +91,7 @@ module Rudy
|
|
90
91
|
# not have a valid keypair configured. (See: EC2_KEYPAIR_*)
|
91
92
|
def check_keys
|
92
93
|
raise "No SSH key provided for #{keypairname}!" unless has_keypair?(keypairname)
|
93
|
-
raise "SSH key provided but cannot be found! (#{keypairpath})" unless File.exists?(keypairpath)
|
94
|
+
raise "SSH key provided but cannot be found! (#{keypairname}: #{keypairpath})" unless File.exists?(keypairpath)
|
94
95
|
end
|
95
96
|
|
96
97
|
def has_pem_keys?
|
@@ -157,10 +158,8 @@ module Rudy
|
|
157
158
|
raise "#{disk.device} is already in use on #{machine[:aws_instance_id]}! (Try umounting it)"
|
158
159
|
end
|
159
160
|
|
160
|
-
new_volume = false
|
161
161
|
if !disk.awsid || (disk.awsid && !@ec2.volumes.exists?(disk.awsid))
|
162
162
|
disk = Rudy::MetaData::Disk.update_volume(@sdb, @ec2, disk, machine)
|
163
|
-
new_volume = true
|
164
163
|
end
|
165
164
|
|
166
165
|
raise "Unknown error creating volume! #{disk.awsid}" unless disk && disk.awsid
|
@@ -171,11 +170,16 @@ module Rudy
|
|
171
170
|
|
172
171
|
@user = "root"
|
173
172
|
|
174
|
-
if
|
173
|
+
if disk.raw_volume
|
175
174
|
puts "Creating the filesystem (mkfs.ext3 -F #{disk.device})"
|
176
175
|
capture(:stdout) do
|
177
176
|
ssh machine[:dns_name], keypairpath, user, "mkfs.ext3 -F #{disk.device}"
|
178
177
|
end
|
178
|
+
|
179
|
+
puts "Saving disk metadata"
|
180
|
+
disk.raw_volume = false
|
181
|
+
Rudy::MetaData::Disk.save(@sdb, disk)
|
182
|
+
|
179
183
|
sleep 2
|
180
184
|
end
|
181
185
|
|
@@ -193,26 +197,25 @@ module Rudy
|
|
193
197
|
# +cmd+ is the name of the command current running.
|
194
198
|
def print_header(cmd=nil)
|
195
199
|
title = "RUDY v#{Rudy::VERSION}"
|
196
|
-
|
197
|
-
|
198
|
-
|
200
|
+
now_utc = Time.now.utc
|
201
|
+
criteria = []
|
202
|
+
[:zone, :environment, :role, :position].each do |n|
|
203
|
+
val = instance_variable_get("@#{n}")
|
204
|
+
criteria << "#{n.to_s.color :normal}:#{val.color :bright}"
|
205
|
+
end
|
206
|
+
puts '%s -- %s' % [title, criteria.join(", ")]
|
207
|
+
puts 'UTC: %s' % now_utc.strftime("%Y-%m-%d %H:%M:%S")
|
208
|
+
puts
|
209
|
+
|
199
210
|
if (@environment == "prod")
|
200
|
-
|
211
|
+
msg = %q(=======================================================
|
201
212
|
=======================================================
|
202
213
|
!!!!!!!!! YOU ARE PLAYING WITH PRODUCTION !!!!!!!!!
|
203
214
|
=======================================================
|
204
|
-
=======================================================
|
205
|
-
|
206
|
-
|
207
|
-
end
|
208
|
-
|
209
|
-
criteria = []
|
210
|
-
[:zone, :environment, :role, :position].each do |n|
|
211
|
-
val = instance_variable_get("@#{n}")
|
212
|
-
criteria << "[#{n} = #{val}]"
|
215
|
+
=======================================================)
|
216
|
+
puts msg.color(:bright, :red, :bg_white)
|
217
|
+
puts
|
213
218
|
end
|
214
|
-
puts criteria.join(" and ")
|
215
|
-
puts
|
216
219
|
|
217
220
|
end
|
218
221
|
|
@@ -220,6 +223,16 @@ module Rudy
|
|
220
223
|
|
221
224
|
end
|
222
225
|
|
226
|
+
# Returns a hash of info for the requested machine. If the requested machine
|
227
|
+
# is not running, it will raise an exception.
|
228
|
+
def find_current_machine
|
229
|
+
machine_list = @ec2.instances.list(machine_group)
|
230
|
+
machine = machine_list.values.first # NOTE: Only one machine per group, for now...
|
231
|
+
raise "There's no machine running in #{machine_group}" unless machine
|
232
|
+
raise "The primary machine in #{machine_group} is not in a running state" unless machine[:aws_state] == 'running'
|
233
|
+
|
234
|
+
machine
|
235
|
+
end
|
223
236
|
|
224
237
|
|
225
238
|
def group_metadata(env=@environment, role=@role)
|
@@ -242,17 +255,28 @@ module Rudy
|
|
242
255
|
|
243
256
|
def print_image(img)
|
244
257
|
puts '-'*60
|
245
|
-
puts "Image: #{img[:
|
258
|
+
puts "Image: #{img[:aws_id]}"
|
246
259
|
img.each_pair do |key, value|
|
247
260
|
printf(" %22s: %s#{$/}", key, value) if value
|
248
261
|
end
|
249
262
|
puts
|
250
263
|
end
|
251
264
|
|
252
|
-
def print_disk(disk)
|
265
|
+
def print_disk(disk, backups=[])
|
253
266
|
puts '-'*60
|
254
267
|
puts "Disk: #{disk.name}"
|
255
268
|
puts disk.to_s
|
269
|
+
puts "#{backups.size} most recent backups:", backups.collect { |back| "#{back.nice_time} (#{back.awsid})" }
|
270
|
+
puts
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
def print_volume(vol, disk)
|
275
|
+
puts '-'*60
|
276
|
+
puts "Volume: #{vol[:aws_id]} (disk: #{disk.name})"
|
277
|
+
vol.each_pair do |key, value|
|
278
|
+
printf(" %22s: %s#{$/}", key, value) if value
|
279
|
+
end
|
256
280
|
puts
|
257
281
|
end
|
258
282
|
|