solutious-rudy 0.9.0 → 0.9.1
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 +61 -4
- data/README.rdoc +91 -53
- data/Rakefile +0 -92
- data/Rudyfile +15 -25
- data/bin/rudy +52 -41
- data/examples/gem-test.rb +92 -0
- data/lib/rudy.rb +15 -7
- data/lib/rudy/aws.rb +2 -2
- data/lib/rudy/aws/ec2.rb +2 -2
- data/lib/rudy/aws/ec2/instance.rb +3 -3
- data/lib/rudy/aws/ec2/volume.rb +4 -4
- data/lib/rudy/cli/aws/ec2/candy.rb +13 -13
- data/lib/rudy/cli/base.rb +10 -4
- data/lib/rudy/cli/config.rb +13 -3
- data/lib/rudy/cli/disks.rb +1 -1
- data/lib/rudy/cli/execbase.rb +5 -2
- data/lib/rudy/cli/machines.rb +231 -30
- data/lib/rudy/cli/networks.rb +34 -0
- data/lib/rudy/cli/routines.rb +1 -1
- data/lib/rudy/cli/status.rb +60 -0
- data/lib/rudy/config.rb +42 -14
- data/lib/rudy/exceptions.rb +5 -1
- data/lib/rudy/global.rb +29 -13
- data/lib/rudy/huxtable.rb +2 -2
- data/lib/rudy/machines.rb +2 -2
- data/lib/rudy/metadata/disk.rb +2 -1
- data/lib/rudy/routines.rb +3 -3
- data/lib/rudy/routines/base.rb +7 -4
- data/lib/rudy/routines/handlers/disks.rb +16 -6
- data/lib/rudy/routines/handlers/group.rb +5 -3
- data/lib/rudy/routines/handlers/host.rb +14 -16
- data/lib/rudy/routines/handlers/script.rb +2 -2
- data/lib/rudy/routines/handlers/user.rb +4 -0
- data/lib/rudy/routines/reboot.rb +26 -9
- data/lib/rudy/routines/shutdown.rb +4 -0
- data/lib/rudy/routines/startup.rb +3 -2
- data/lib/rudy/utils.rb +23 -9
- data/rudy.gemspec +10 -29
- data/tryouts/10_require_time/10_rudy_tryouts.rb +1 -1
- data/tryouts/{misc/console_tryout.rb → exploration/console.rb} +0 -0
- data/tryouts/{misc/usage_tryout.rb → exploration/machine.rb} +0 -0
- data/tryouts/failer +1 -1
- metadata +8 -70
- data/tryouts/misc/disks_tryout.rb +0 -48
- data/tryouts/misc/drydock_tryout.rb +0 -48
- data/tryouts/misc/nested_methods.rb +0 -103
- data/tryouts/misc/session_tryout.rb +0 -46
- data/tryouts/misc/tryouts.rb +0 -33
data/lib/rudy/cli/base.rb
CHANGED
@@ -30,10 +30,16 @@ module Rudy::CLI
|
|
30
30
|
STDERR.puts ex.backtrace if @@global.verbose > 0
|
31
31
|
exit 81
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
@@global.nocolor ? String.disable_color : String.enable_color
|
35
|
-
@@global.
|
36
|
-
|
35
|
+
@@global.auto ? Annoy.enable_skip : Annoy.disable_skip
|
36
|
+
|
37
|
+
# ANSI codes look like garbage in DOS
|
38
|
+
if Rudy.sysinfo.os.to_s == 'win32'
|
39
|
+
String.disable_color
|
40
|
+
raise Rudy::Error, 'Ruby 1.9 is not supported (yet)' if Rudy.sysinfo.ruby == [1,9,1]
|
41
|
+
end
|
42
|
+
|
37
43
|
unless @@global.accesskey && @@global.secretkey
|
38
44
|
STDERR.puts "No AWS credentials. Check your configs!"
|
39
45
|
STDERR.puts "Try: rudy init"
|
@@ -51,7 +57,7 @@ module Rudy::CLI
|
|
51
57
|
gcopy.secretkey = "[HIDDEN]"
|
52
58
|
puts "# GLOBALS: ", gcopy.dump(format)
|
53
59
|
end
|
54
|
-
|
60
|
+
|
55
61
|
Rudy::Metadata.connect @@global.accesskey, @@global.secretkey, @@global.region
|
56
62
|
Rudy::AWS::EC2.connect @@global.accesskey, @@global.secretkey, @@global.region
|
57
63
|
end
|
data/lib/rudy/cli/config.rb
CHANGED
@@ -21,7 +21,7 @@ module Rudy
|
|
21
21
|
#
|
22
22
|
# It will return the most specific configuration available. If the
|
23
23
|
# attribute isn'e found it will check each parent for the same attribute.
|
24
|
-
#
|
24
|
+
# e.g. if [prod][app][ami] is not available, it will check [prod][ami]
|
25
25
|
# and then [ami].
|
26
26
|
#
|
27
27
|
# # Display all configuration
|
@@ -61,7 +61,11 @@ module Rudy
|
|
61
61
|
types.each do |conftype|
|
62
62
|
puts "# #{conftype.to_s.upcase}"
|
63
63
|
next unless @@config[conftype] # Nothing to output
|
64
|
-
|
64
|
+
if conftype == :accounts
|
65
|
+
skey = @@config[conftype][:aws][:secretkey]
|
66
|
+
@@config[conftype][:aws][:secretkey] = hide_secret_key(skey)
|
67
|
+
end
|
68
|
+
|
65
69
|
puts @@config[conftype].to_hash.send(outform)
|
66
70
|
end
|
67
71
|
end
|
@@ -76,10 +80,16 @@ module Rudy
|
|
76
80
|
end
|
77
81
|
gtmp = @@global.clone
|
78
82
|
gtmp.format = "yaml" if gtmp.format == :s || gtmp.format == :string
|
79
|
-
gtmp.
|
83
|
+
gtmp.secretkey = hide_secret_key(gtmp.secretkey)
|
80
84
|
puts gtmp.dump(gtmp.format)
|
81
85
|
end
|
82
86
|
|
87
|
+
private
|
88
|
+
def hide_secret_key(skey)
|
89
|
+
skey = skey.to_s
|
90
|
+
"%s%s%s" % [skey[0], '.'*18, skey[-1]]
|
91
|
+
end
|
92
|
+
|
83
93
|
end
|
84
94
|
end
|
85
95
|
end
|
data/lib/rudy/cli/disks.rb
CHANGED
@@ -25,7 +25,7 @@ module Rudy
|
|
25
25
|
seen << d.name
|
26
26
|
puts @@global.verbose > 0 ? d.inspect : d.dump(@@global.format)
|
27
27
|
if @option.backups
|
28
|
-
d.
|
28
|
+
d.backups.each_with_index do |b, index|
|
29
29
|
puts ' %s' % b.name
|
30
30
|
##break if @option.all.nil? && index >= 2 # display only 3, unless all
|
31
31
|
end
|
data/lib/rudy/cli/execbase.rb
CHANGED
@@ -34,10 +34,13 @@ module Rudy::CLI
|
|
34
34
|
global :c, :cert, String, "AWS Private Certificate (cert-****.pem)"
|
35
35
|
global :f, :format, String, "Output format"
|
36
36
|
global :n, :nocolor, "Disable output colors"
|
37
|
-
global :
|
38
|
-
global :Y, :yes, "Assume a correct answer to confirmation questions"
|
37
|
+
global :Y, :auto, "Skip interactive confirmation"
|
39
38
|
global :q, :quiet, "Run with less output"
|
40
39
|
global :O, :offline, "Be cool about the internet being down"
|
40
|
+
global :C, :config, String, "Specify another configuration file to read (e.g. #{Rudy::CONFIG_FILE})" do |val|
|
41
|
+
@configs ||= []
|
42
|
+
@configs << val
|
43
|
+
end
|
41
44
|
global :v, :verbose, "Increase verbosity of output (e.g. -v or -vv or -vvv)" do
|
42
45
|
@verbose ||= 0
|
43
46
|
@verbose += 1
|
data/lib/rudy/cli/machines.rb
CHANGED
@@ -4,6 +4,7 @@ module Rudy
|
|
4
4
|
module CLI
|
5
5
|
class Machines < Rudy::CLI::CommandBase
|
6
6
|
|
7
|
+
|
7
8
|
def machines
|
8
9
|
# Rudy::Machines.list takes two optional args for adding or
|
9
10
|
# removing metadata attributes to modify the select query.
|
@@ -15,15 +16,10 @@ module Rudy
|
|
15
16
|
|
16
17
|
mlist = Rudy::Machines.list(fields, less) || []
|
17
18
|
if mlist.empty?
|
18
|
-
|
19
|
-
puts "No machines running"
|
20
|
-
else
|
21
|
-
puts "No machines running in #{current_machine_group}"
|
22
|
-
puts "Try: rudy machines --all"
|
23
|
-
end
|
19
|
+
raise( NoMachines, @option.all ? nil : current_group_name)
|
24
20
|
end
|
25
21
|
mlist.each do |m|
|
26
|
-
puts @@global.verbose > 0 ? m.
|
22
|
+
puts @@global.verbose > 0 ? m.to_yaml : "#{m.name}: #{m.dns_public}"
|
27
23
|
end
|
28
24
|
end
|
29
25
|
|
@@ -44,6 +40,157 @@ module Rudy
|
|
44
40
|
|
45
41
|
end
|
46
42
|
|
43
|
+
def associate_machines_valid?
|
44
|
+
@mlist = Rudy::Machines.list || []
|
45
|
+
@alist = Rudy::AWS::EC2::Addresses.list || []
|
46
|
+
@alist_used = @alist.select { |a| a.associated? }
|
47
|
+
@alist_unused = @alist.select { |a| !a.associated? }
|
48
|
+
@alist_unused.collect! { |a| a.ipaddress }
|
49
|
+
@alist_instids = @alist_used.collect { |a| a.instid }
|
50
|
+
@mlist_static = @mlist.select do |m|
|
51
|
+
@alist_instids.member?(m.instid)
|
52
|
+
end
|
53
|
+
|
54
|
+
unless @@global.force
|
55
|
+
unless @mlist_static.empty?
|
56
|
+
msg = "Some machines already have static IP addresses: #{$/}"
|
57
|
+
msg << @mlist_static.collect { |m| "#{m.name}: #{m.dns_public}" }.join($/)
|
58
|
+
raise Rudy::Error, msg
|
59
|
+
end
|
60
|
+
|
61
|
+
if !@argv.empty? && @mlist.size > @argv.size
|
62
|
+
msg = "You supplied #{@argv.size} addresses for #{@mlist.size} "
|
63
|
+
msg << "machines. Try: rudy --force machines -S #{@argv.join(' ')}"
|
64
|
+
raise Rudy::Error, msg
|
65
|
+
end
|
66
|
+
|
67
|
+
if @alist_unused.size > 0 && @alist_unused.size < @mlist.size
|
68
|
+
msg = "There are only #{@alist_unused.size} available addresses for "
|
69
|
+
msg << "#{@mlist.size} machines. Try: rudy --force machines -S #{@argv.join(' ')}"
|
70
|
+
raise Rudy::Error, msg
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
@argv.each do |address|
|
75
|
+
unless Rudy::AWS::EC2::Addresses.exists?(address)
|
76
|
+
raise "#{address} is not allocated to you"
|
77
|
+
end
|
78
|
+
if Rudy::AWS::EC2::Addresses.associated?(address)
|
79
|
+
raise "#{address} is already associated!"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@alist_unused = @argv unless @argv.empty?
|
84
|
+
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def associate_machines
|
89
|
+
|
90
|
+
puts "Assigning static IP addresses for:"
|
91
|
+
puts @mlist.collect { |m| m.name }
|
92
|
+
|
93
|
+
execute_check(:medium)
|
94
|
+
|
95
|
+
@mlist.each do |m|
|
96
|
+
next if @mlist_static.member?(m)
|
97
|
+
address = @alist_unused.shift
|
98
|
+
address ||= Rudy::AWS::EC2::Addresses.create.ipaddress
|
99
|
+
puts "Associating #{address} to #{m.name} (#{m.instid})"
|
100
|
+
Rudy::AWS::EC2::Addresses.associate(address, m.instid)
|
101
|
+
sleep 2
|
102
|
+
m.refresh!
|
103
|
+
end
|
104
|
+
|
105
|
+
@alist = Rudy::AWS::EC2::Addresses.list || []
|
106
|
+
@alist_used = @alist.select { |a| a.associated? }
|
107
|
+
@alist_instids = @alist_used.collect { |a| a.instid }
|
108
|
+
@mlist_static = @mlist.select do |m|
|
109
|
+
@alist_instids.member?(m.instid)
|
110
|
+
end
|
111
|
+
|
112
|
+
unless @mlist_static.empty?
|
113
|
+
@mlist_static.each do |m|
|
114
|
+
puts "%s: %s" % [m.name, m.dns_public]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
def disassociate_machines_valid?
|
121
|
+
@mlist = Rudy::Machines.list || []
|
122
|
+
@alist = Rudy::AWS::EC2::Addresses.list || []
|
123
|
+
@alist_used = @alist.select { |a| a.associated? }
|
124
|
+
@alist_instids = @alist_used.collect { |a| a.instid }
|
125
|
+
@mlist_static = @mlist.select do |m|
|
126
|
+
@alist_instids.member?(m.instid)
|
127
|
+
end
|
128
|
+
raise NoMachines, current_group_name if @mlist.empty?
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def disassociate_machines
|
134
|
+
if @mlist_static.empty?
|
135
|
+
puts "No machines in #{current_group_name} have static IP addresses"
|
136
|
+
else
|
137
|
+
puts "The following machines will be updated:"
|
138
|
+
puts @mlist_static.collect { |m| m.name }
|
139
|
+
puts "NOTE: Unassigned IP addresses are not removed from your account"
|
140
|
+
execute_check(:medium)
|
141
|
+
@mlist_static.each do |m|
|
142
|
+
address = Resolv.getaddress m.dns_public
|
143
|
+
puts "Disassociating #{address} from #{m.name} (#{m.instid})"
|
144
|
+
Rudy::AWS::EC2::Addresses.disassociate(address)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def update_machines
|
150
|
+
fields, less = {}, []
|
151
|
+
less = Rudy::Metadata::COMMON_FIELDS if @option.all
|
152
|
+
mlist = Rudy::Machines.list(fields, less) || []
|
153
|
+
rset = Rye::Set.new(current_group_name, :parallel => @@global.parallel, :user => 'root')
|
154
|
+
os = current_machine_os
|
155
|
+
mlist.each do |m|
|
156
|
+
m.refresh!
|
157
|
+
rbox = Rye::Box.new(m.dns_public, :user => 'root')
|
158
|
+
rbox.add_key user_keypairpath('root')
|
159
|
+
rbox.nickname = m.name
|
160
|
+
rbox.stash = m
|
161
|
+
rset.add_boxes rbox
|
162
|
+
puts "Updating metadata"
|
163
|
+
if m.os.to_s != os.to_s
|
164
|
+
puts "os: #{os}"
|
165
|
+
m.os = os
|
166
|
+
end
|
167
|
+
m.save :replace
|
168
|
+
end
|
169
|
+
|
170
|
+
unless os.to_s == 'win32'
|
171
|
+
puts "Updating hostnames for #{current_group_name}"
|
172
|
+
Rudy::Routines::Handlers::Host.set_hostname rset
|
173
|
+
puts rset.hostname.flatten
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
def available_machines
|
179
|
+
fields, less = {}, []
|
180
|
+
less = Rudy::Metadata::COMMON_FIELDS if @option.all
|
181
|
+
mlist = Rudy::Machines.list(fields, less) || []
|
182
|
+
mlist.each do |m|
|
183
|
+
print "#{m.name}: "
|
184
|
+
m.refresh!
|
185
|
+
Rudy::Utils.waiter(2, 60, STDOUT, nil, 0) {
|
186
|
+
Rudy::Utils.service_available?(m.dns_public, 22)
|
187
|
+
}
|
188
|
+
available = Rudy::Utils.service_available?(m.dns_public, 22)
|
189
|
+
puts available ? 'up' : 'down'
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
47
194
|
|
48
195
|
def ssh
|
49
196
|
# TODO: Give this method a good look over
|
@@ -52,15 +199,16 @@ module Rudy
|
|
52
199
|
puts "No private key configured for #{current_machine_user} in #{current_machine_group}"
|
53
200
|
end
|
54
201
|
|
55
|
-
# Options to be sent to
|
56
|
-
|
202
|
+
# Options to be sent to Rye::Box
|
203
|
+
rye_opts = { :user => current_machine_user, :debug => nil }
|
57
204
|
if pkey
|
58
205
|
raise "Cannot find file #{pkey}" unless File.exists?(pkey)
|
59
|
-
|
60
|
-
|
206
|
+
if Rudy.sysinfo.os != :win32 && File.stat(pkey).mode != 33152
|
207
|
+
raise InsecureKeyPermissions, pkey
|
208
|
+
end
|
209
|
+
rye_opts[:keys] = pkey
|
61
210
|
end
|
62
|
-
|
63
|
-
|
211
|
+
|
64
212
|
# The user specified a command to run. We won't create an interactive
|
65
213
|
# session so we need to prepare the command and its arguments
|
66
214
|
if @argv.first
|
@@ -71,42 +219,95 @@ module Rudy
|
|
71
219
|
else
|
72
220
|
command, command_args = :interactive_ssh, @option.print.nil?
|
73
221
|
end
|
74
|
-
|
75
|
-
|
222
|
+
|
223
|
+
if command == :interactive_ssh && @global.parallel
|
224
|
+
raise "Cannot run interactive sessions in parallel"
|
225
|
+
end
|
226
|
+
|
76
227
|
checked = false
|
77
228
|
lt = Rudy::Machines.list
|
78
229
|
unless lt
|
79
230
|
puts "No machines running in #{current_machine_group}"
|
80
|
-
|
231
|
+
return
|
81
232
|
end
|
233
|
+
|
234
|
+
rset = Rye::Set.new(current_machine_group, :parallel => @global.parallel)
|
82
235
|
lt.each do |machine|
|
83
236
|
machine.refresh! # make sure we have the latest DNS info
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
237
|
+
rbox = Rye::Box.new(machine.dns_public, rye_opts)
|
238
|
+
rbox.nickname = machine.name
|
239
|
+
if command == :interactive_ssh
|
240
|
+
# Print header
|
241
|
+
if @@global.quiet
|
242
|
+
print "You are #{rye_opts[:user].to_s.bright}. " if !checked # only the 1st
|
243
|
+
else
|
244
|
+
puts machine_separator(machine.name, machine.instid)
|
245
|
+
puts "Connecting #{rye_opts[:user].to_s.bright}@#{machine.dns_public} "
|
246
|
+
puts
|
247
|
+
end
|
90
248
|
else
|
91
|
-
|
92
|
-
|
93
|
-
|
249
|
+
unless @global.parallel
|
250
|
+
rbox.pre_command_hook do |cmd,user,host,nickname|
|
251
|
+
print_command user, nickname, cmd
|
252
|
+
end
|
253
|
+
end
|
254
|
+
rbox.post_command_hook do |ret|
|
255
|
+
print_response ret
|
256
|
+
end
|
94
257
|
end
|
95
258
|
|
96
259
|
# Make sure we want to run this command on all instances
|
97
260
|
if !checked && command != :interactive_ssh
|
98
|
-
execute_check(:low) if
|
261
|
+
execute_check(:low) if rye_opts[:user] == "root"
|
99
262
|
checked = true
|
100
263
|
end
|
101
264
|
|
102
|
-
# Open the connection and run the command
|
103
|
-
|
104
|
-
|
105
|
-
|
265
|
+
# Open the connection and run the command
|
266
|
+
if command == :interactive_ssh
|
267
|
+
rbox.send(command, command_args)
|
268
|
+
else
|
269
|
+
rset.add_box rbox
|
270
|
+
end
|
106
271
|
end
|
272
|
+
|
273
|
+
rset.send(command, command_args) unless command == :interactive_ssh
|
274
|
+
|
107
275
|
end
|
108
276
|
|
277
|
+
|
278
|
+
private
|
279
|
+
# Returns a formatted string for printing command info
|
280
|
+
def print_command(user, host, cmd)
|
281
|
+
#return if @@global.parallel
|
282
|
+
cmd ||= ""
|
283
|
+
cmd, user = cmd.to_s, user.to_s
|
284
|
+
prompt = user == "root" ? "#" : "$"
|
285
|
+
li ("%s@%s%s %s" % [user, host, prompt, cmd.bright])
|
286
|
+
end
|
287
|
+
|
288
|
+
|
289
|
+
def print_response(rap)
|
290
|
+
# Non zero exit codes raise exceptions so
|
291
|
+
# the erorrs have already been handled.
|
292
|
+
return if rap.exit_code != 0
|
109
293
|
|
294
|
+
if @@global.parallel
|
295
|
+
cmd, user = cmd.to_s, user.to_s
|
296
|
+
prompt = user == "root" ? "#" : "$"
|
297
|
+
li "%s@%s%s %s%s%s" % [rap.box.user, rap.box.nickname, prompt, rap.cmd.bright, $/, rap.stdout.inspect]
|
298
|
+
unless rap.stderr.empty?
|
299
|
+
le "#{rap.box.nickname}: " << rap.stderr.join("#{rap.box.nickname}: ")
|
300
|
+
end
|
301
|
+
else
|
302
|
+
li ' ' << rap.stdout.join("#{$/} ") if !rap.stdout.empty?
|
303
|
+
colour = rap.exit_code != 0 ? :red : :normal
|
304
|
+
unless rap.stderr.empty?
|
305
|
+
le (" STDERR " << '-'*38).color(colour).bright
|
306
|
+
le " " << rap.stderr.join("#{$/} ").color(colour)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
110
311
|
end
|
111
312
|
end
|
112
313
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Rudy
|
4
|
+
module CLI
|
5
|
+
class Networks < Rudy::CLI::CommandBase
|
6
|
+
|
7
|
+
def networks
|
8
|
+
name = current_group_name
|
9
|
+
Rudy::AWS::EC2::Groups.list(name).each do |group|
|
10
|
+
puts @@global.verbose > 0 ? group.inspect : group.dump(@@global.format)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_networks
|
15
|
+
Rudy::Routines::Handlers::Group.authorize rescue nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def local_networks
|
19
|
+
ea = Rudy::Utils::external_ip_address || ''
|
20
|
+
ia = Rudy::Utils::internal_ip_address || ''
|
21
|
+
if @global.quiet
|
22
|
+
puts ia unless @option.external && !@option.internal
|
23
|
+
puts ea unless @option.internal && !@option.external
|
24
|
+
else
|
25
|
+
puts "%10s: %s" % ['Internal', ia] unless @option.external && !@option.internal
|
26
|
+
puts "%10s: %s" % ['External', ea] unless @option.internal && !@option.external
|
27
|
+
end
|
28
|
+
@global.quiet = true # don't print elapsed time
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
data/lib/rudy/cli/routines.rb
CHANGED
@@ -90,7 +90,7 @@ module Rudy; module CLI;
|
|
90
90
|
true
|
91
91
|
end
|
92
92
|
def shutdown
|
93
|
-
routine = fetch_routine_config(:shutdown)
|
93
|
+
routine = fetch_routine_config(:shutdown) rescue {}
|
94
94
|
|
95
95
|
puts "All machines in #{current_machine_group} will be shutdown".bright
|
96
96
|
if routine && routine.disks
|