Class: ClusterBomb::BombShell

Inherits:
Object
  • Object
show all
Includes:
Dispatcher, History, Shawties
Defined in:
lib/cluster_bomb/bomb_shell.rb

Constant Summary collapse

WELCOME =
"Welcome to the BombShell v 0.2.1, Cowboy\n:help -- quick help"

Constants included from Shawties

Shawties::SHAWTIES_FILE_NAME

Constants included from Dispatcher

Dispatcher::COMMANDS, Dispatcher::COMMAND_AUTOCOMPLETE

Constants included from History

History::HISTORY_FILE

Instance Method Summary collapse

Methods included from Shawties

#define_shawtie, #get_shawtie, #load_shawties!, #save_shawties, #shawtie_names, #shawties_list

Methods included from Dispatcher

#dir_completion_proc, #dir_list, #dispatcher_completion_proc, #init_autocomplete, #process_cmd, #secondary_completion_proc

Methods included from History

#libedit?, #load_history, #save_history

Constructor Details

#initialize(bomb) ⇒ BombShell

Returns a new instance of BombShell.



14
15
16
17
18
19
20
# File 'lib/cluster_bomb/bomb_shell.rb', line 14

def initialize(bomb)
  @bomb=bomb
  @bomb.interactive=true
  @stty_save = `stty -g`.chomp
  # Default settings
  @roles=[]
end

Instance Method Details

#disconnect(p) ⇒ Object



125
126
127
# File 'lib/cluster_bomb/bomb_shell.rb', line 125

def disconnect(p)
  @bomb.disconnect!
end

#exec(p) ⇒ Object



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/cluster_bomb/bomb_shell.rb', line 170

def exec(p)
  return if p=='shell'
  task_name = p.split(' ').first unless p.nil?
  if task_name.nil? || !@bomb.valid_task?(task_name.to_sym)
    puts "Task missing or not found"
    self.list(nil)
    return
  end
  # @bomb.clear_env!
  roles=@roles
  opts = @bomb.get_task(task_name.to_sym).options || {}
  roles = opts[:roles] if opts[:roles] && opts[:sticky_roles]
  self.process_task_args(p)
  puts "NOTE Using sticky roles defined on task: #{roles.inspect}" if opts[:sticky_roles]
  @bomb.clear
  begin
    unless roles.empty?
      @bomb.exec(task_name.to_sym, {:roles=>roles}) 
    else
      @bomb.exec(task_name.to_sym) 
    end
  rescue Exception=>e
    puts "ERROR: #{e.message}"
    # p e.backtrace
  end
  # Cheesy -- clear out enviroment variables passed in with task to prevent accidental reuse
  @bomb.clear_env!
end

#help(p = nil) ⇒ Object



278
279
280
281
282
283
284
285
286
287
# File 'lib/cluster_bomb/bomb_shell.rb', line 278

def help(p=nil)
  puts "Anything entered at the shell prompt will be executed on the current set of remote servers. "
  puts "Anything preceded by a : will be interpreted as a cluster_bomb command"
  puts "Available cluster_bomb commands:"
  Dispatcher::COMMANDS.each do |cr|
    puts "    #{cr[:name]} - #{cr[:description]}"
  end
  puts ":<nnn> will re-execute a command from the history"
  puts "Use the bang (!) to execute something in the local shell. ex !ls -la"
end

#history(p) ⇒ Object



129
130
131
132
133
134
135
136
137
# File 'lib/cluster_bomb/bomb_shell.rb', line 129

def history(p)
  Readline::HISTORY.to_a.each_with_index do |h,i|
    if p.nil?
      puts "   #{i}: #{h}"
    else
      puts "   #{i}: #{h}" if h.match(p)
    end
  end
end

#host_list(p) ⇒ Object



289
290
291
292
293
294
# File 'lib/cluster_bomb/bomb_shell.rb', line 289

def host_list(p)
  Logging.puts "Roles in use: #{@roles.join(',')}"
  hl = @bomb.hosts.collect {|h| h.name}
  Logging.puts "#{hl.length} active hosts"
  Logging.puts "#{hl.join(',')}"
end

#list(p) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/cluster_bomb/bomb_shell.rb', line 155

def list(p)
  tg = {}
  @bomb.task_list.each do |t|
    tg[t.group] ||=[]
    tg[t.group] << t
  end
  puts "Available tasks (usually autocompletable):"
  tg.to_a.sort{|a,b|a[0]<=>b[0] }.each do |ta|
    puts "  #{ta[0]}"
    t_sorted = ta[1].sort {|a,b| a.name.to_s <=> b.name.to_s}
    t_sorted.each do |t|
      puts "    [#{t.name}] - #{t.description}"
    end
  end
end

#loopObject



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/cluster_bomb/bomb_shell.rb', line 22

def loop
  puts WELCOME
  load_history @bomb.configuration.max_history
  init_autocomplete
  load_shawties!
  while true
    cmd = read_line
    break if cmd.nil?
    next if cmd.empty?
    # See if we're repezating a command
    if m = cmd.match(/^:(\d+)$/)
      cmd = Readline::HISTORY[m[1].to_i]
      next if cmd.nil?
      puts cmd
      Readline::HISTORY.pop
      Readline::HISTORY.push(cmd)
    end
    Logging.log(cmd)
    break if !process_input(cmd)
  end
  save_history
  puts "Exiting..."
  Logging.log_disable
end

#process_input(buf, reprocess = false) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/cluster_bomb/bomb_shell.rb', line 47

def process_input(buf, reprocess=false)
  if buf.index(':')==0
    return false if !process_cmd(buf[1..-1])
  elsif buf.index('!')==0
    self.shell(buf)
  elsif buf.index('\\')==0 && !reprocess
    self.shawtie(buf)
  else
    begin
      run(buf)
    rescue Exception => e
      puts "Exception on run command: #{e.message}"
      puts e.backtrace
    end
  end
  return true      
end

#process_task_args(p) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/cluster_bomb/bomb_shell.rb', line 199

def process_task_args(p)
  arg_keys=[]
  args=p.split(' ')
  return [] if args.length <=1
  args[1..-1].each do |kv|
    pair = kv.split('=')
    if pair.length == 2
      k = pair[0].strip.to_sym
      arg_keys << k
      @bomb.set(k,pair[1].strip)
    end
  end
  arg_keys # Return these so we can clear them when the task is done
end

#read_lineObject



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/cluster_bomb/bomb_shell.rb', line 101

def read_line
  total_hosts = @bomb.hosts.length
  total_connected_hosts = 0
  @bomb.hosts.each {|h| total_connected_hosts +=1 if h.connected}
  begin
    sm = @bomb.sudo_mode ? '[SUDO] ' : ''
    line = Readline.readline("#{sm}(#{@bomb.username}) #{total_connected_hosts}/#{total_hosts}> ", true)
    if line =~ /^\s*$/ || Readline::HISTORY.to_a[-2] == line
        Readline::HISTORY.pop
    end        
  rescue Interrupt => e
    system('stty', @stty_save)
    return nil
  end
  return line if line.nil? # Ctrl-d
  line.strip!
  return line
end

#run(cmd, host_list = nil) ⇒ Object



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/cluster_bomb/bomb_shell.rb', line 303

def run(cmd, host_list=nil)
  @bomb.clear
  opts={}
  opts[:hosts]=host_list if host_list
  opts[:sudo] = true if @sudo_mode
  @bomb.run(cmd, opts) do |r|
    if r.exception
      puts "#{r.name} => EXCEPTION: #{r.exception.message}" 
    else
      output = r.console
      ll = output.length + r.name.length + 3
      if ll > @bomb.configuration.screen_width.to_i || output.index('\n')
        Logging.puts "=== #{r.name} ==="
        Logging.puts output
      else
        Logging.puts "#{r.name} => #{output}" 
      end
    end
  end
end

#set(p) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/cluster_bomb/bomb_shell.rb', line 138

def set(p)
  return if p.nil? || p=='shell'
  parts = p.split('=')
  if parts.length > 2
    puts "syntax: set <name>=<value"
  end
  k = parts[0].strip.to_sym
  unless @bomb.configuration.valid_key? k
    puts "Unknown configuration setting #{k}"
    return
  end
  if parts.length == 2
    @bomb.configuration.set(k,parts[1].strip)
  else
    @bomb.configuration.set(k,nil)
  end
end

#shawtie(cmd) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/cluster_bomb/bomb_shell.rb', line 65

def shawtie(cmd)
  if cmd.index(/^\\d /) == 0
    m = cmd.match(/^\\d +(\w+) +(\d+)/)
    if m
      sdef = Readline::HISTORY[m[2].to_i]
      if sdef
        puts "Defined short #{m[1]} :: #{sdef}"
        define_shawtie(m[1],sdef)
      end
    else
      m = cmd.match(/^\\d +(\w+) +(.+)$/)   
      if m.nil?
        m = cmd.match(/^\\d +(\w+)/)
        define_shawtie(m[1],nil)
        puts "Undefed short #{m[1]}"            
      else
        define_shawtie(m[1],m[2])
        puts "Defined short #{m[1]} - [#{m[2]}]"            
      end
    end
  elsif cmd.index(/^\\l$/) == 0
    shawties_list
  else
    m=cmd.match(/\\(\w+)/)
    if !m
      shawties_list
    else
      sdef = get_shawtie(m[1])
      if sdef
        puts "Run short: #{m[1]} :: #{sdef}"
        self.process_input(sdef)
      end
    end
  end
end

#shell(cmd) ⇒ Object



120
121
122
123
# File 'lib/cluster_bomb/bomb_shell.rb', line 120

def shell(cmd)
  cmdline=cmd[1..-1].strip
  puts `#{cmdline}`
end

#sudo(p) ⇒ Object



300
301
302
# File 'lib/cluster_bomb/bomb_shell.rb', line 300

def sudo(p)
  @bomb.sudo_mode = !@bomb.sudo_mode
end

#switch(p) ⇒ Object



296
297
298
299
# File 'lib/cluster_bomb/bomb_shell.rb', line 296

def switch(p)
  return if p.empty?
  @bomb.switch_user(p)
end

#upload(p) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/cluster_bomb/bomb_shell.rb', line 265

def upload(p)
  source, dest = p.split(' ') unless p.nil?
  if source.nil? || dest.nil? || p.nil?
    puts "syntax: upload sourcepath destpath (no wildcards)"
    return true
  end
  begin 
    @bomb.upload(source, dest)
  rescue Exception => e
    puts "ERROR: #{e.message}"
  end
end

#use(p) ⇒ Object



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/cluster_bomb/bomb_shell.rb', line 214

def use(p)
  unless p.nil?
    match = p.strip.match(/(.*?) +(.*)/)
    if match
      host_list = match[1]
      cmd=match[2].strip
    else
      host_list = p  
    end
    hosts=host_list.split(',').collect{|r|r.strip}
  else
    puts("Host list argument required")
    return
  end
  @roles=[] # Nil this out so future commands hit this host
  begin 
    self.run(cmd, hosts)
  rescue Exception => e
    puts "ERROR: #{e.message}"
  end      
end

#with(p) ⇒ Object



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/cluster_bomb/bomb_shell.rb', line 236

def with(p)
  unless p.nil?
    match = p.strip.match(/(.*?) +(.*)/)
    if match
      role_list = match[1]
      cmd=match[2].strip
    else
      role_list = p  
    end
    roles=role_list.split(',').collect{|r|r.strip.to_sym}
    bad_role=roles.detect{|r| !@bomb.valid_role? r }        
  end      
  if p.nil? || bad_role
    p.nil? ? puts("Role argument required") : puts("Unknown role: #{bad_role}")
    puts "Available roles:"
    @bomb.role_list.each do |r|
      puts "    #{r[:name]} (#{r[:hostnames].length} hosts)"
    end     
    return
  end
  @roles=roles
  begin 
    @bomb.reconnect!(@roles)
    run cmd if cmd
  rescue Exception => e
    puts "ERROR: #{e.message}"
  end
end