cluster_bomb 0.2.6
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/cb +3 -0
- data/lib/cluster_bomb/actest.rb +63 -0
- data/lib/cluster_bomb/bomb.rb +256 -0
- data/lib/cluster_bomb/bomb_shell.rb +325 -0
- data/lib/cluster_bomb/cli.rb +82 -0
- data/lib/cluster_bomb/cluster.rb +298 -0
- data/lib/cluster_bomb/configuration.rb +83 -0
- data/lib/cluster_bomb/dispatcher.rb +110 -0
- data/lib/cluster_bomb/history.rb +42 -0
- data/lib/cluster_bomb/logging.rb +35 -0
- data/lib/cluster_bomb/roles.rb +56 -0
- data/lib/cluster_bomb/shawties.rb +46 -0
- data/lib/cluster_bomb/stdtasks.rb +16 -0
- metadata +122 -0
data/bin/cb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'readline'
|
2
|
+
|
3
|
+
class Test
|
4
|
+
@stty_save = `stty -g`.chomp
|
5
|
+
def run()
|
6
|
+
Readline.completion_proc=proc {|s| self.main_completion_proc(s)}
|
7
|
+
Readline.completer_word_break_characters = 7.chr
|
8
|
+
Readline.completion_case_fold = true
|
9
|
+
Readline.completion_append_character = ''
|
10
|
+
while(true) do
|
11
|
+
line = read_line
|
12
|
+
break if !line
|
13
|
+
end
|
14
|
+
end
|
15
|
+
def read_line
|
16
|
+
begin
|
17
|
+
line = Readline.readline("> ", true)
|
18
|
+
if line =~ /^\s*$/ || Readline::HISTORY.to_a[-2] == line
|
19
|
+
Readline::HISTORY.pop
|
20
|
+
end
|
21
|
+
rescue Interrupt => e
|
22
|
+
system('stty', @stty_save)
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
return line if line.nil? # Ctrl-d
|
26
|
+
line.strip!
|
27
|
+
return line
|
28
|
+
end
|
29
|
+
|
30
|
+
def main_completion_proc(s)
|
31
|
+
# No line buffer for mac, so no way to get command context
|
32
|
+
# Very lame with libedit. Will not return candidates, and
|
33
|
+
# We cannot get the current line so we can do context-based
|
34
|
+
# edits
|
35
|
+
cmds = ['foo','bar','plushy','food','bear','plum','ls','los','lsu']
|
36
|
+
ret=[]
|
37
|
+
tokens = s.split(' ')
|
38
|
+
if s =~ /^\w.* $/
|
39
|
+
tokens << ''
|
40
|
+
end
|
41
|
+
# Initial command only
|
42
|
+
if tokens.length <= 1
|
43
|
+
ret = cmds.grep(/^#{s}/)
|
44
|
+
else
|
45
|
+
if tokens[0]=='ls'
|
46
|
+
ret = foo_completion_proc(tokens)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
ret
|
50
|
+
end
|
51
|
+
def foo_completion_proc(tokens)
|
52
|
+
files=Dir.glob('*')
|
53
|
+
if tokens[1]==''
|
54
|
+
candidates=files
|
55
|
+
candidates
|
56
|
+
else
|
57
|
+
candidates = files.grep(/^#{tokens[1]}/)
|
58
|
+
candidates.collect {|c| "#{tokens[0]} #{c}"}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
Test.new.run
|
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'cluster_bomb/cluster.rb'
|
2
|
+
require 'cluster_bomb/roles.rb'
|
3
|
+
require 'cluster_bomb/configuration.rb'
|
4
|
+
require 'cluster_bomb/logging.rb'
|
5
|
+
|
6
|
+
# TODO: persistent configuration
|
7
|
+
# TODO: logging
|
8
|
+
module ClusterBomb
|
9
|
+
# Task Runner module
|
10
|
+
# Loads task files and runs tasks
|
11
|
+
# All tasks run within the context of the class implementing this module
|
12
|
+
module Bomb
|
13
|
+
include Roles
|
14
|
+
include Logging
|
15
|
+
class Task
|
16
|
+
attr_accessor :proc, :roles, :description, :sudo
|
17
|
+
attr_reader :group, :name, :filename
|
18
|
+
attr_reader :options
|
19
|
+
def initialize(name, group, filename=nil, filetime=nil, opts={})
|
20
|
+
@name=name
|
21
|
+
self.roles=[]
|
22
|
+
@filename=filename
|
23
|
+
@group = group
|
24
|
+
@filetime=filetime
|
25
|
+
@options=opts
|
26
|
+
@sudo = opts[:sudo]
|
27
|
+
end
|
28
|
+
def updated?
|
29
|
+
return false if !self.filename
|
30
|
+
File.stat(self.filename).mtime != @filetime
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Config
|
35
|
+
include Configuration
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :env, :auto_reload, :configuration,:interactive, :username, :sudo_mode
|
39
|
+
def initialize
|
40
|
+
@sudo_mode = false
|
41
|
+
@tasks||={}
|
42
|
+
@cluster||=nil
|
43
|
+
self.env={}
|
44
|
+
@reloading=false
|
45
|
+
@current_load_file=nil
|
46
|
+
@current_load_file_time=nil
|
47
|
+
self.auto_reload=true
|
48
|
+
@configuration = Config.new
|
49
|
+
@configuration.load!
|
50
|
+
@username = @configuration.username
|
51
|
+
raise "Unable to get a default user name. Exiting..." unless @username
|
52
|
+
@interactive=false
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
56
|
+
def interactive?
|
57
|
+
@interactive
|
58
|
+
end
|
59
|
+
|
60
|
+
def group(str)
|
61
|
+
@current_group=str
|
62
|
+
end
|
63
|
+
|
64
|
+
def desc(str)
|
65
|
+
@current_desription=str
|
66
|
+
end
|
67
|
+
|
68
|
+
def task(name, options={}, &task)
|
69
|
+
t = Task.new(name, @current_group, @current_load_file,@current_load_file_time, options )
|
70
|
+
raise "task #{t.name} is already defined" if @tasks[t.name] && !@reloading
|
71
|
+
@tasks[t.name]=t
|
72
|
+
t.proc = task
|
73
|
+
t.roles=options[:roles]
|
74
|
+
t.description=@current_desription
|
75
|
+
|
76
|
+
@current_description=''
|
77
|
+
end
|
78
|
+
def role_list
|
79
|
+
ret=[]
|
80
|
+
@roles.each do |k,v|
|
81
|
+
ret << {:name=>k, :hostnames=>v}
|
82
|
+
end
|
83
|
+
ret
|
84
|
+
end
|
85
|
+
def load_str(str)
|
86
|
+
begin
|
87
|
+
self.instance_eval(str)
|
88
|
+
rescue Exception => e
|
89
|
+
puts "Exception while loading: #{@current_load_file}"
|
90
|
+
raise e
|
91
|
+
end
|
92
|
+
ssh_options = @configuration.ssh_options(username)
|
93
|
+
@cluster = Cluster.new(username,ssh_options) unless @cluster
|
94
|
+
@current_load_file=nil
|
95
|
+
@current_load_file_time=nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def reload(fn)
|
99
|
+
@reloading=true
|
100
|
+
load(fn)
|
101
|
+
@reloading=false
|
102
|
+
end
|
103
|
+
|
104
|
+
def load(fn)
|
105
|
+
@current_group=fn.split('/').last
|
106
|
+
s = File.read(fn)
|
107
|
+
@current_load_file=fn
|
108
|
+
@current_load_file_time=File.stat(fn).mtime
|
109
|
+
self.load_str(s)
|
110
|
+
end
|
111
|
+
|
112
|
+
def set(name, value=nil)
|
113
|
+
self.env[name.to_sym]=value
|
114
|
+
code=<<-EODEF
|
115
|
+
def #{name}
|
116
|
+
self.env[:#{name}]
|
117
|
+
end
|
118
|
+
def #{name}=(rhs)
|
119
|
+
self.env[:#{name}]=rhs
|
120
|
+
end
|
121
|
+
EODEF
|
122
|
+
self.instance_eval(code)
|
123
|
+
end
|
124
|
+
|
125
|
+
def ensure_var(name, value=nil)
|
126
|
+
return if env.has_key? name.to_sym
|
127
|
+
set(name, value)
|
128
|
+
end
|
129
|
+
|
130
|
+
def exists?(attrname)
|
131
|
+
env.has_key? attrname.to_sym
|
132
|
+
end
|
133
|
+
|
134
|
+
def clear_env!
|
135
|
+
env.each_key do |k|
|
136
|
+
self.instance_eval("undef #{k.to_s}; undef #{k.to_s}=")
|
137
|
+
end
|
138
|
+
self.env={}
|
139
|
+
end
|
140
|
+
|
141
|
+
def switch_user(user)
|
142
|
+
if @configuration.has_ssh_options? user
|
143
|
+
ssh_options = @configuration.ssh_options(user)
|
144
|
+
else
|
145
|
+
ssh_options={}
|
146
|
+
end
|
147
|
+
@username = ssh_options[:user] || user
|
148
|
+
@cluster.credentials(@username, ssh_options)
|
149
|
+
@cluster.disconnect!
|
150
|
+
end
|
151
|
+
|
152
|
+
def exec(name, options={})
|
153
|
+
sudo_save = @sudo_mode
|
154
|
+
# @cluster.reset!
|
155
|
+
server_list=server_list_from_options(options)
|
156
|
+
t = @tasks[name]
|
157
|
+
raise "TASK NOT FOUND: #{name}" unless t
|
158
|
+
@sudo_mode = true if t.sudo # Turn it on if sudo is true
|
159
|
+
raise "Task not found: #{name}" if t.nil?
|
160
|
+
if self.auto_reload && t.updated?
|
161
|
+
puts "Reloading #{t.filename}"
|
162
|
+
reload(t.filename)
|
163
|
+
t = @tasks[name]
|
164
|
+
end
|
165
|
+
raise "Task not found: #{name}" if t.nil?
|
166
|
+
server_list = self.servers(t.roles) if server_list.empty?
|
167
|
+
# puts "CONNECTED: #{@cluster.connected?}"
|
168
|
+
@cluster.connect!(server_list) unless @cluster.connected? && (!options[:roles] && !options[:hosts])
|
169
|
+
raise "Task #{name} not found" unless t
|
170
|
+
t.proc.call
|
171
|
+
@sudo_mode = sudo_save
|
172
|
+
end
|
173
|
+
|
174
|
+
def download(remote, local, options={}, &task)
|
175
|
+
@cluster.download(remote, local, options={}, &task)
|
176
|
+
end
|
177
|
+
|
178
|
+
def upload(local, remote, options={}, &task)
|
179
|
+
files=[]
|
180
|
+
|
181
|
+
server_list=[]
|
182
|
+
if options[:roles]
|
183
|
+
server_list = self.servers(options[:roles])
|
184
|
+
elsif options[:hosts]
|
185
|
+
server_list=options[:hosts]
|
186
|
+
end
|
187
|
+
if !server_list.empty?
|
188
|
+
@cluster.reset!
|
189
|
+
@cluster.connect!(server_list)
|
190
|
+
end
|
191
|
+
|
192
|
+
if local.index('*')
|
193
|
+
files=Dir.glob(local)
|
194
|
+
else
|
195
|
+
files << local
|
196
|
+
end
|
197
|
+
files.each do |f|
|
198
|
+
next if File.stat(f).directory?
|
199
|
+
puts "Uploading: #{f}"
|
200
|
+
@cluster.upload(f, remote, options, &task)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def run(command, options={}, &task)
|
205
|
+
server_list=server_list_from_options(options)
|
206
|
+
if !server_list.empty?
|
207
|
+
@cluster.reset!
|
208
|
+
@cluster.connect!(server_list)
|
209
|
+
end
|
210
|
+
options[:sudo] = @sudo_mode unless options.has_key? :sudo
|
211
|
+
# use max_run_time environment variable if not passed in by caller
|
212
|
+
options[:max_run_time] = self.configuration.max_run_time unless options[:max_run_time]
|
213
|
+
@cluster.run(command, options, &task)
|
214
|
+
end
|
215
|
+
|
216
|
+
def server_list_from_options(options)
|
217
|
+
server_list=[]
|
218
|
+
if options[:roles]
|
219
|
+
server_list = self.servers(options[:roles])
|
220
|
+
elsif options[:hosts]
|
221
|
+
server_list=options[:hosts]
|
222
|
+
end
|
223
|
+
server_list
|
224
|
+
end
|
225
|
+
|
226
|
+
# primarily for shell use to change roles
|
227
|
+
def reconnect!(roles)
|
228
|
+
@cluster.reset!
|
229
|
+
@cluster.connect!(self.servers(roles))
|
230
|
+
end
|
231
|
+
|
232
|
+
def valid_task?(name)
|
233
|
+
@tasks[name] ? true : false
|
234
|
+
end
|
235
|
+
|
236
|
+
def get_task(name)
|
237
|
+
@tasks[name]
|
238
|
+
end
|
239
|
+
|
240
|
+
def disconnect!
|
241
|
+
@cluster.disconnect!
|
242
|
+
end
|
243
|
+
|
244
|
+
def task_list
|
245
|
+
ret=[]
|
246
|
+
@tasks.each{|k,v| ret << v }
|
247
|
+
ret
|
248
|
+
end
|
249
|
+
def clear
|
250
|
+
@cluster.clear!
|
251
|
+
end
|
252
|
+
def hosts
|
253
|
+
@cluster.hosts
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'cluster_bomb/history.rb'
|
3
|
+
require 'cluster_bomb/logging.rb'
|
4
|
+
require 'cluster_bomb/dispatcher.rb'
|
5
|
+
require 'cluster_bomb/shawties.rb'
|
6
|
+
# TODO: upgrade command history (persist, no repeats)
|
7
|
+
# TODO: settings
|
8
|
+
module ClusterBomb
|
9
|
+
class BombShell
|
10
|
+
include History
|
11
|
+
include Dispatcher
|
12
|
+
include Shawties
|
13
|
+
WELCOME="Welcome to the BombShell v 0.2.1, Cowboy\n:help -- quick help"
|
14
|
+
def initialize(bomb)
|
15
|
+
@bomb=bomb
|
16
|
+
@bomb.interactive=true
|
17
|
+
@stty_save = `stty -g`.chomp
|
18
|
+
# Default settings
|
19
|
+
@roles=[]
|
20
|
+
end
|
21
|
+
|
22
|
+
def loop
|
23
|
+
puts WELCOME
|
24
|
+
load_history @bomb.configuration.max_history
|
25
|
+
init_autocomplete
|
26
|
+
load_shawties!
|
27
|
+
while true
|
28
|
+
cmd = read_line
|
29
|
+
break if cmd.nil?
|
30
|
+
next if cmd.empty?
|
31
|
+
# See if we're repezating a command
|
32
|
+
if m = cmd.match(/^:(\d+)$/)
|
33
|
+
cmd = Readline::HISTORY[m[1].to_i]
|
34
|
+
next if cmd.nil?
|
35
|
+
puts cmd
|
36
|
+
Readline::HISTORY.pop
|
37
|
+
Readline::HISTORY.push(cmd)
|
38
|
+
end
|
39
|
+
Logging.log(cmd)
|
40
|
+
break if !process_input(cmd)
|
41
|
+
end
|
42
|
+
save_history
|
43
|
+
puts "Exiting..."
|
44
|
+
Logging.log_disable
|
45
|
+
end
|
46
|
+
|
47
|
+
def process_input(buf, reprocess=false)
|
48
|
+
if buf.index(':')==0
|
49
|
+
return false if !process_cmd(buf[1..-1])
|
50
|
+
elsif buf.index('!')==0
|
51
|
+
self.shell(buf)
|
52
|
+
elsif buf.index('\\')==0 && !reprocess
|
53
|
+
self.shawtie(buf)
|
54
|
+
else
|
55
|
+
begin
|
56
|
+
run(buf)
|
57
|
+
rescue Exception => e
|
58
|
+
puts "Exception on run command: #{e.message}"
|
59
|
+
puts e.backtrace
|
60
|
+
end
|
61
|
+
end
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
|
65
|
+
def shawtie(cmd)
|
66
|
+
if cmd.index(/^\\d /) == 0
|
67
|
+
m = cmd.match(/^\\d +(\w+) +(\d+)/)
|
68
|
+
if m
|
69
|
+
sdef = Readline::HISTORY[m[2].to_i]
|
70
|
+
if sdef
|
71
|
+
puts "Defined short #{m[1]} :: #{sdef}"
|
72
|
+
define_shawtie(m[1],sdef)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
m = cmd.match(/^\\d +(\w+) +(.+)$/)
|
76
|
+
if m.nil?
|
77
|
+
m = cmd.match(/^\\d +(\w+)/)
|
78
|
+
define_shawtie(m[1],nil)
|
79
|
+
puts "Undefed short #{m[1]}"
|
80
|
+
else
|
81
|
+
define_shawtie(m[1],m[2])
|
82
|
+
puts "Defined short #{m[1]} - [#{m[2]}]"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
elsif cmd.index(/^\\l$/) == 0
|
86
|
+
shawties_list
|
87
|
+
else
|
88
|
+
m=cmd.match(/\\(\w+)/)
|
89
|
+
if !m
|
90
|
+
shawties_list
|
91
|
+
else
|
92
|
+
sdef = get_shawtie(m[1])
|
93
|
+
if sdef
|
94
|
+
puts "Run short: #{m[1]} :: #{sdef}"
|
95
|
+
self.process_input(sdef)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def read_line
|
102
|
+
total_hosts = @bomb.hosts.length
|
103
|
+
total_connected_hosts = 0
|
104
|
+
@bomb.hosts.each {|h| total_connected_hosts +=1 if h.connected}
|
105
|
+
begin
|
106
|
+
sm = @bomb.sudo_mode ? '[SUDO] ' : ''
|
107
|
+
line = Readline.readline("#{sm}(#{@bomb.username}) #{total_connected_hosts}/#{total_hosts}> ", true)
|
108
|
+
if line =~ /^\s*$/ || Readline::HISTORY.to_a[-2] == line
|
109
|
+
Readline::HISTORY.pop
|
110
|
+
end
|
111
|
+
rescue Interrupt => e
|
112
|
+
system('stty', @stty_save)
|
113
|
+
return nil
|
114
|
+
end
|
115
|
+
return line if line.nil? # Ctrl-d
|
116
|
+
line.strip!
|
117
|
+
return line
|
118
|
+
end
|
119
|
+
|
120
|
+
def shell(cmd)
|
121
|
+
cmdline=cmd[1..-1].strip
|
122
|
+
puts `#{cmdline}`
|
123
|
+
end
|
124
|
+
|
125
|
+
def disconnect(p)
|
126
|
+
@bomb.disconnect!
|
127
|
+
end
|
128
|
+
|
129
|
+
def history(p)
|
130
|
+
Readline::HISTORY.to_a.each_with_index do |h,i|
|
131
|
+
if p.nil?
|
132
|
+
puts " #{i}: #{h}"
|
133
|
+
else
|
134
|
+
puts " #{i}: #{h}" if h.match(p)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
def set(p)
|
139
|
+
return if p.nil? || p=='shell'
|
140
|
+
parts = p.split('=')
|
141
|
+
if parts.length > 2
|
142
|
+
puts "syntax: set <name>=<value"
|
143
|
+
end
|
144
|
+
k = parts[0].strip.to_sym
|
145
|
+
unless @bomb.configuration.valid_key? k
|
146
|
+
puts "Unknown configuration setting #{k}"
|
147
|
+
return
|
148
|
+
end
|
149
|
+
if parts.length == 2
|
150
|
+
@bomb.configuration.set(k,parts[1].strip)
|
151
|
+
else
|
152
|
+
@bomb.configuration.set(k,nil)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
def list(p)
|
156
|
+
tg = {}
|
157
|
+
@bomb.task_list.each do |t|
|
158
|
+
tg[t.group] ||=[]
|
159
|
+
tg[t.group] << t
|
160
|
+
end
|
161
|
+
puts "Available tasks (usually autocompletable):"
|
162
|
+
tg.to_a.sort{|a,b|a[0]<=>b[0] }.each do |ta|
|
163
|
+
puts " #{ta[0]}"
|
164
|
+
t_sorted = ta[1].sort {|a,b| a.name.to_s <=> b.name.to_s}
|
165
|
+
t_sorted.each do |t|
|
166
|
+
puts " [#{t.name}] - #{t.description}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
def exec(p)
|
171
|
+
return if p=='shell'
|
172
|
+
task_name = p.split(' ').first unless p.nil?
|
173
|
+
if task_name.nil? || !@bomb.valid_task?(task_name.to_sym)
|
174
|
+
puts "Task missing or not found"
|
175
|
+
self.list(nil)
|
176
|
+
return
|
177
|
+
end
|
178
|
+
# @bomb.clear_env!
|
179
|
+
roles=@roles
|
180
|
+
opts = @bomb.get_task(task_name.to_sym).options || {}
|
181
|
+
roles = opts[:roles] if opts[:roles] && opts[:sticky_roles]
|
182
|
+
self.process_task_args(p)
|
183
|
+
puts "NOTE Using sticky roles defined on task: #{roles.inspect}" if opts[:sticky_roles]
|
184
|
+
@bomb.clear
|
185
|
+
begin
|
186
|
+
unless roles.empty?
|
187
|
+
@bomb.exec(task_name.to_sym, {:roles=>roles})
|
188
|
+
else
|
189
|
+
@bomb.exec(task_name.to_sym)
|
190
|
+
end
|
191
|
+
rescue Exception=>e
|
192
|
+
puts "ERROR: #{e.message}"
|
193
|
+
# p e.backtrace
|
194
|
+
end
|
195
|
+
# Cheesy -- clear out enviroment variables passed in with task to prevent accidental reuse
|
196
|
+
@bomb.clear_env!
|
197
|
+
end
|
198
|
+
|
199
|
+
def process_task_args(p)
|
200
|
+
arg_keys=[]
|
201
|
+
args=p.split(' ')
|
202
|
+
return [] if args.length <=1
|
203
|
+
args[1..-1].each do |kv|
|
204
|
+
pair = kv.split('=')
|
205
|
+
if pair.length == 2
|
206
|
+
k = pair[0].strip.to_sym
|
207
|
+
arg_keys << k
|
208
|
+
@bomb.set(k,pair[1].strip)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
arg_keys # Return these so we can clear them when the task is done
|
212
|
+
end
|
213
|
+
|
214
|
+
def use(p)
|
215
|
+
unless p.nil?
|
216
|
+
match = p.strip.match(/(.*?) +(.*)/)
|
217
|
+
if match
|
218
|
+
host_list = match[1]
|
219
|
+
cmd=match[2].strip
|
220
|
+
else
|
221
|
+
host_list = p
|
222
|
+
end
|
223
|
+
hosts=host_list.split(',').collect{|r|r.strip}
|
224
|
+
else
|
225
|
+
puts("Host list argument required")
|
226
|
+
return
|
227
|
+
end
|
228
|
+
@roles=[] # Nil this out so future commands hit this host
|
229
|
+
begin
|
230
|
+
self.run(cmd, hosts)
|
231
|
+
rescue Exception => e
|
232
|
+
puts "ERROR: #{e.message}"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def with(p)
|
237
|
+
unless p.nil?
|
238
|
+
match = p.strip.match(/(.*?) +(.*)/)
|
239
|
+
if match
|
240
|
+
role_list = match[1]
|
241
|
+
cmd=match[2].strip
|
242
|
+
else
|
243
|
+
role_list = p
|
244
|
+
end
|
245
|
+
roles=role_list.split(',').collect{|r|r.strip.to_sym}
|
246
|
+
bad_role=roles.detect{|r| !@bomb.valid_role? r }
|
247
|
+
end
|
248
|
+
if p.nil? || bad_role
|
249
|
+
p.nil? ? puts("Role argument required") : puts("Unknown role: #{bad_role}")
|
250
|
+
puts "Available roles:"
|
251
|
+
@bomb.role_list.each do |r|
|
252
|
+
puts " #{r[:name]} (#{r[:hostnames].length} hosts)"
|
253
|
+
end
|
254
|
+
return
|
255
|
+
end
|
256
|
+
@roles=roles
|
257
|
+
begin
|
258
|
+
@bomb.reconnect!(@roles)
|
259
|
+
run cmd if cmd
|
260
|
+
rescue Exception => e
|
261
|
+
puts "ERROR: #{e.message}"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def upload(p)
|
266
|
+
source, dest = p.split(' ') unless p.nil?
|
267
|
+
if source.nil? || dest.nil? || p.nil?
|
268
|
+
puts "syntax: upload sourcepath destpath (no wildcards)"
|
269
|
+
return true
|
270
|
+
end
|
271
|
+
begin
|
272
|
+
@bomb.upload(source, dest)
|
273
|
+
rescue Exception => e
|
274
|
+
puts "ERROR: #{e.message}"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def help(p=nil)
|
279
|
+
puts "Anything entered at the shell prompt will be executed on the current set of remote servers. "
|
280
|
+
puts "Anything preceded by a : will be interpreted as a cluster_bomb command"
|
281
|
+
puts "Available cluster_bomb commands:"
|
282
|
+
Dispatcher::COMMANDS.each do |cr|
|
283
|
+
puts " #{cr[:name]} - #{cr[:description]}"
|
284
|
+
end
|
285
|
+
puts ":<nnn> will re-execute a command from the history"
|
286
|
+
puts "Use the bang (!) to execute something in the local shell. ex !ls -la"
|
287
|
+
end
|
288
|
+
|
289
|
+
def host_list(p)
|
290
|
+
Logging.puts "Roles in use: #{@roles.join(',')}"
|
291
|
+
hl = @bomb.hosts.collect {|h| h.name}
|
292
|
+
Logging.puts "#{hl.length} active hosts"
|
293
|
+
Logging.puts "#{hl.join(',')}"
|
294
|
+
end
|
295
|
+
|
296
|
+
def switch(p)
|
297
|
+
return if p.empty?
|
298
|
+
@bomb.switch_user(p)
|
299
|
+
end
|
300
|
+
def sudo(p)
|
301
|
+
@bomb.sudo_mode = !@bomb.sudo_mode
|
302
|
+
end
|
303
|
+
def run(cmd, host_list=nil)
|
304
|
+
@bomb.clear
|
305
|
+
opts={}
|
306
|
+
opts[:hosts]=host_list if host_list
|
307
|
+
opts[:sudo] = true if @sudo_mode
|
308
|
+
@bomb.run(cmd, opts) do |r|
|
309
|
+
if r.exception
|
310
|
+
puts "#{r.name} => EXCEPTION: #{r.exception.message}"
|
311
|
+
else
|
312
|
+
output = r.console
|
313
|
+
ll = output.length + r.name.length + 3
|
314
|
+
if ll > @bomb.configuration.screen_width.to_i || output.index('\n')
|
315
|
+
Logging.puts "=== #{r.name} ==="
|
316
|
+
Logging.puts output
|
317
|
+
else
|
318
|
+
Logging.puts "#{r.name} => #{output}"
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end # Bombshell
|
324
|
+
end # Module
|
325
|
+
|