Class: Nutshell::Shell

Inherits:
Object
  • Object
show all
Includes:
Open4
Defined in:
lib/nutshell/shell.rb

Overview

The Shell class handles local input, output and execution to the shell.

Direct Known Subclasses

RemoteShell

Constant Summary collapse

LOCAL_USER =
`whoami`.chomp
LOCAL_HOST =
`hostname`.chomp

Class Attribute Summary collapse

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output = $stdout, options = {}) ⇒ Shell

Returns a new instance of Shell.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/nutshell/shell.rb', line 33

def initialize output=$stdout, options={}
  @output = output

  $stdin.sync
  @input = HighLine.new $stdin

  @user = LOCAL_USER
  @host = LOCAL_HOST

  @sudo     = options[:sudo]
  @env      = options[:env] || {}
  @password = options[:password]

  @timeout = options[:timeout] || Nutshell.timeout

  @cmd_activity = nil

  @mutex = nil
end

Class Attribute Details

.sudo_failed_matcherObject

The message to match in stderr to determine logging in has failed. Defaults to:

/^Sorry, try again./


18
19
20
# File 'lib/nutshell/shell.rb', line 18

def sudo_failed_matcher
  @sudo_failed_matcher
end

.sudo_prompt_matcherObject

The message to match in stderr to determine a password is required. Defaults to:

/^Password:/


23
24
25
# File 'lib/nutshell/shell.rb', line 23

def sudo_prompt_matcher
  @sudo_prompt_matcher
end

Instance Attribute Details

#envObject

Returns the value of attribute env.



31
32
33
# File 'lib/nutshell/shell.rb', line 31

def env
  @env
end

#hostObject (readonly)

Returns the value of attribute host.



30
31
32
# File 'lib/nutshell/shell.rb', line 30

def host
  @host
end

#inputObject (readonly)

Returns the value of attribute input.



30
31
32
# File 'lib/nutshell/shell.rb', line 30

def input
  @input
end

#mutexObject (readonly)

Returns the value of attribute mutex.



30
31
32
# File 'lib/nutshell/shell.rb', line 30

def mutex
  @mutex
end

#outputObject (readonly)

Returns the value of attribute output.



30
31
32
# File 'lib/nutshell/shell.rb', line 30

def output
  @output
end

#passwordObject (readonly)

Returns the value of attribute password.



30
31
32
# File 'lib/nutshell/shell.rb', line 30

def password
  @password
end

#sudoObject

Returns the value of attribute sudo.



31
32
33
# File 'lib/nutshell/shell.rb', line 31

def sudo
  @sudo
end

#timeoutObject

Returns the value of attribute timeout.



31
32
33
# File 'lib/nutshell/shell.rb', line 31

def timeout
  @timeout
end

#userObject (readonly)

Returns the value of attribute user.



30
31
32
# File 'lib/nutshell/shell.rb', line 30

def user
  @user
end

Instance Method Details

#==(shell) ⇒ Object

Checks for equality



57
58
59
# File 'lib/nutshell/shell.rb', line 57

def == shell
  @host == shell.host && @user == shell.user rescue false
end

#agree(*args, &block) ⇒ Object

Prompt the user to agree.



73
74
75
# File 'lib/nutshell/shell.rb', line 73

def agree(*args, &block)
  sync{ @input.agree(*args, &block) }
end

#ask(*args, &block) ⇒ Object

Prompt the user for input.



65
66
67
# File 'lib/nutshell/shell.rb', line 65

def ask(*args, &block)
  sync{ @input.ask(*args, &block) }
end

#call(cmd, options = {}, &block) ⇒ Object

Execute a command on the local system and return the output.



81
82
83
# File 'lib/nutshell/shell.rb', line 81

def call cmd, options={}, &block
  execute sudo_cmd(cmd, options), &block
end

#choose(&block) ⇒ Object

Prompt the user to make a choice.



89
90
91
# File 'lib/nutshell/shell.rb', line 89

def choose(&block)
  sync{ @input.choose(&block) }
end

#closeObject

Close the output IO. (Required by the Logger class)



97
98
99
# File 'lib/nutshell/shell.rb', line 97

def close
  @output.close
end

#connectObject

Returns true. Compatibility method with RemoteShell.



105
106
107
# File 'lib/nutshell/shell.rb', line 105

def connect
  true
end

#connected?Boolean

Returns true. Compatibility method with RemoteShell.

Returns:

  • (Boolean)


113
114
115
# File 'lib/nutshell/shell.rb', line 113

def connected?
  true
end

#disconnectObject

Returns true. Compatibility method with RemoteShell.



121
122
123
# File 'lib/nutshell/shell.rb', line 121

def disconnect
  true
end

#download(from_path, to_path, options = {}, &block) ⇒ Object Also known as: upload

Copies a file. Compatibility method with RemoteShell.



129
130
131
# File 'lib/nutshell/shell.rb', line 129

def download from_path, to_path, options={}, &block
  FileUtils.cp_r from_path, to_path
end

#env_cmd(cmd, env_hash = @env) ⇒ Object

Build an env command if an env_hash is passed



197
198
199
200
201
202
203
# File 'lib/nutshell/shell.rb', line 197

def env_cmd cmd, env_hash=@env
  if env_hash && !env_hash.empty?
    env_vars = env_hash.map{|e| e.join("=")}
    cmd = ["env", env_vars, cmd].flatten
  end
  cmd
end

#execute(cmd) ⇒ Object

Execute a command with open4 and loop until the process exits. The cmd argument may be a string or an array. If a block is passed, it will be called when data is received and passed the stream type and stream string value:

shell.execute "test -s 'blah' && echo 'true'" do |stream, str|
  stream    #=> :stdout
  string    #=> 'true'
end

The method returns the output from the stdout stream by default, and raises a CmdError if the exit status of the command is not zero.



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/nutshell/shell.rb', line 357

def execute cmd
  cmd = [cmd] unless Array === cmd
  pid, inn, out, err = popen4(*cmd)

  inn.sync = true
  log_methods = {out => :debug, err => :error}

  status = nil

  result, status = process_streams(pid, out, err) do |stream, data|
    stream_name = :out if stream == out
    stream_name = :err if stream == err
    stream_name = :inn if stream == inn


    # User blocks should run with sync threads to avoid badness.
    sync do
      yield(stream_name, data, inn) if block_given?
    end


    if password_required?(stream_name, data) then
      kill_process(pid) unless Nutshell.interactive?
      send_password_to_stream(inn, data)
    end
  end

  raise_command_failed(status, cmd) unless status.success?

  result[out].join.chomp

ensure
  inn.close rescue nil
  out.close rescue nil
  err.close rescue nil
end

#expand_path(path) ⇒ Object

Expands the path. Compatibility method with RemoteShell.



139
140
141
# File 'lib/nutshell/shell.rb', line 139

def expand_path path
  File.expand_path path
end

#file?(filepath) ⇒ Boolean

Checks if file exists. Compatibility method with RemoteShell.

Returns:

  • (Boolean)


147
148
149
# File 'lib/nutshell/shell.rb', line 147

def file? filepath
  File.file? filepath
end

#make_file(filepath, content, options = {}) ⇒ Object

Write a file. Compatibility method with RemoteShell.



170
171
172
# File 'lib/nutshell/shell.rb', line 170

def make_file filepath, content, options={}
  File.open(filepath, "w+"){|f| f.write(content)}
end

#os_nameObject

Get the name of the OS



178
179
180
# File 'lib/nutshell/shell.rb', line 178

def os_name
  @os_name ||= call("uname -s").strip.downcase
end

#prompt_for_passwordObject

Prompt the user for a password



186
187
188
189
190
191
# File 'lib/nutshell/shell.rb', line 186

def prompt_for_password
  host_info = [@user, @host].compact.join("@")
  @password = ask("#{host_info} Password:") do |q|
    q.echo = false
  end
end

#quote_cmd(cmd) ⇒ Object

Wrap command in quotes and escape as needed.



209
210
211
212
# File 'lib/nutshell/shell.rb', line 209

def quote_cmd cmd
  cmd = [*cmd].join(" ")
  "'#{cmd.gsub(/'/){|s| "'\\''"}}'"
end

#session {|_self| ... } ⇒ Object

Runs the given block within a session. Will not disconnect if previous session had been started.

Yields:

  • (_self)

Yield Parameters:



219
220
221
222
223
224
# File 'lib/nutshell/shell.rb', line 219

def session &block
  was_connected = connected?
  connect
  yield self if block_given?
  disconnect unless was_connected
end

#sh_cmd(cmd) ⇒ Object

Build an sh -c command



230
231
232
# File 'lib/nutshell/shell.rb', line 230

def sh_cmd cmd
  ["sh", "-i", "-c", quote_cmd(cmd)]
end

#sudo_cmd(cmd, sudo_val = nil) ⇒ Object

Build a command with sudo. If sudo_val is nil, it is considered to mean “pass-through” and the default shell sudo will be used. If sudo_val is false, the cmd will be returned unchanged. If sudo_val is true, the returned command will be prefaced with sudo -H If sudo_val is a String, the command will be prefaced with sudo -H -u string_value



245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/nutshell/shell.rb', line 245

def sudo_cmd cmd, sudo_val=nil
  sudo_val = sudo_val[:sudo] if Hash === sudo_val
  sudo_val = @sudo if sudo_val.nil?

  case sudo_val
  when true
    ["sudo", "-H", cmd].flatten
  when String
    ["sudo", "-H", "-u", sudo_val, cmd].flatten
  else
    cmd
  end
end

Force symlinking a directory.



263
264
265
# File 'lib/nutshell/shell.rb', line 263

def symlink target, symlink_name
  call "ln -sfT #{target} #{symlink_name}" rescue false
end

#syncObject

Synchronize a block with the current mutex if it exists.



271
272
273
274
275
276
277
# File 'lib/nutshell/shell.rb', line 271

def sync
  if @mutex
    @mutex.synchronize{ yield }
  else
    yield
  end
end

#system(cmd, options = nil) ⇒ Object

Returns true if command was run successfully, otherwise returns false.



283
284
285
# File 'lib/nutshell/shell.rb', line 283

def system cmd, options=nil
  call(cmd, options) && true rescue false
end

#timed_out?(start_time = @cmd_activity, max_time = @timeout) ⇒ Boolean

Checks if timeout occurred.

Returns:

  • (Boolean)


291
292
293
294
# File 'lib/nutshell/shell.rb', line 291

def timed_out? start_time=@cmd_activity, max_time=@timeout
  return unless max_time
  Time.now.to_i - start_time.to_i > max_time
end

#tty!(cmd = nil) ⇒ Object

Start an interactive shell with preset permissions and env. Optionally pass a command to be run first.



156
157
158
159
160
161
162
163
164
# File 'lib/nutshell/shell.rb', line 156

def tty! cmd=nil
  sync do
    cmd = [cmd, "sh -il"].compact.join " && "
    pid = fork do
      exec sudo_cmd(env_cmd(cmd)).to_a.join(" ")
    end
    Process.waitpid pid
  end
end

#update_timeoutObject

Update the time of the last command activity



300
301
302
# File 'lib/nutshell/shell.rb', line 300

def update_timeout
  @cmd_activity = Time.now
end

#with_mutex(mutex) ⇒ Object

Execute a block while setting the shell’s mutex. Sets the mutex to its original value on exit. Executing commands with a mutex is used for user prompts.



310
311
312
313
314
# File 'lib/nutshell/shell.rb', line 310

def with_mutex mutex
  old_mutex, @mutex = @mutex, mutex
  yield
  @mutex = old_mutex
end

#with_sessionObject

Runs the passed block within a connection session. If the shell is already connected, connecting and disconnecting is ignored; otherwise, the session method will ensure that the shell’s connection gets closed after the block has been executed.



324
325
326
327
328
329
330
331
# File 'lib/nutshell/shell.rb', line 324

def with_session
  prev_connection = connected?
  connect unless prev_connection

  yield

  disconnect unless prev_connection
end

#write(str) ⇒ Object Also known as: <<

Write string to stdout (by default).



337
338
339
# File 'lib/nutshell/shell.rb', line 337

def write str
  @output.write str
end