Class: Sunshine::Shell

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

Overview

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

Direct Known Subclasses

RemoteShell

Defined Under Namespace

Classes: TimeoutError

Constant Summary collapse

TIMEOUT =

Time to wait with no activity until giving up on a command.

120
LOCAL_USER =
`whoami`.chomp
LOCAL_HOST =
`hostname`.chomp
SUDO_FAILED =
/^Sorry, try again./
SUDO_PROMPT =
/^Password:/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Returns a new instance of Shell.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/sunshine/shell.rb', line 25

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]

  @cmd_activity = nil

  @mutex = nil
end

Instance Attribute Details

#envObject

Returns the value of attribute env.



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

def env
  @env
end

#hostObject (readonly)

Returns the value of attribute host.



22
23
24
# File 'lib/sunshine/shell.rb', line 22

def host
  @host
end

#inputObject (readonly)

Returns the value of attribute input.



22
23
24
# File 'lib/sunshine/shell.rb', line 22

def input
  @input
end

#mutexObject (readonly)

Returns the value of attribute mutex.



22
23
24
# File 'lib/sunshine/shell.rb', line 22

def mutex
  @mutex
end

#outputObject (readonly)

Returns the value of attribute output.



22
23
24
# File 'lib/sunshine/shell.rb', line 22

def output
  @output
end

#passwordObject (readonly)

Returns the value of attribute password.



22
23
24
# File 'lib/sunshine/shell.rb', line 22

def password
  @password
end

#sudoObject

Returns the value of attribute sudo.



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

def sudo
  @sudo
end

#userObject (readonly)

Returns the value of attribute user.



22
23
24
# File 'lib/sunshine/shell.rb', line 22

def user
  @user
end

Instance Method Details

#==(shell) ⇒ Object

Checks for equality



47
48
49
# File 'lib/sunshine/shell.rb', line 47

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

#agree(*args, &block) ⇒ Object

Prompt the user to agree.



63
64
65
# File 'lib/sunshine/shell.rb', line 63

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

#ask(*args, &block) ⇒ Object

Prompt the user for input.



55
56
57
# File 'lib/sunshine/shell.rb', line 55

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.



71
72
73
# File 'lib/sunshine/shell.rb', line 71

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

#closeObject

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



79
80
81
# File 'lib/sunshine/shell.rb', line 79

def close
  @output.close
end

#connectObject

Returns true. Compatibility method with RemoteShell.



87
88
89
# File 'lib/sunshine/shell.rb', line 87

def connect
  true
end

#connected?Boolean

Returns true. Compatibility method with RemoteShell.

Returns:

  • (Boolean)


95
96
97
# File 'lib/sunshine/shell.rb', line 95

def connected?
  true
end

#disconnectObject

Returns true. Compatibility method with RemoteShell.



103
104
105
# File 'lib/sunshine/shell.rb', line 103

def disconnect
  true
end

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

Copies a file. Compatibility method with RemoteShell.



111
112
113
114
115
# File 'lib/sunshine/shell.rb', line 111

def download from_path, to_path, options={}, &block
  Sunshine.logger.info @host, "Copying #{from_path} -> #{to_path}" do
    FileUtils.cp_r from_path, to_path
  end
end

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

Build an env command if an env_hash is passed



165
166
167
168
169
170
171
# File 'lib/sunshine/shell.rb', line 165

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]
  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.



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/sunshine/shell.rb', line 280

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

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

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


    # User blocks should run with sync threads to avoid badness.
    sync do
      Sunshine.logger.send log_methods[stream],
        "#{@host}:#{stream_name}", data

      yield(stream_name, data, inn) if block_given?
    end


    if password_required?(stream_name, data) then

      kill_process(pid) unless Sunshine.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.



123
124
125
# File 'lib/sunshine/shell.rb', line 123

def expand_path path
  File.expand_path path
end

#file?(filepath) ⇒ Boolean

Checks if file exists. Compatibility method with RemoteShell.

Returns:

  • (Boolean)


131
132
133
# File 'lib/sunshine/shell.rb', line 131

def file? filepath
  File.file? filepath
end

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

Write a file. Compatibility method with RemoteShell.



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

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

#os_nameObject

Get the name of the OS



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

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

#prompt_for_passwordObject

Prompt the user for a password



155
156
157
158
159
# File 'lib/sunshine/shell.rb', line 155

def prompt_for_password
  @password = ask("#{@user}@#{@host} Password:") do |q|
    q.echo = false
  end
end

#sh_cmd(string) ⇒ Object

Build an sh -c command



177
178
179
180
181
# File 'lib/sunshine/shell.rb', line 177

def sh_cmd string
  string = string.gsub(/'/){|s| "'\\''"}

  ["sh", "-c", "'#{string}'"]
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



194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/sunshine/shell.rb', line 194

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.



212
213
214
# File 'lib/sunshine/shell.rb', line 212

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.



220
221
222
223
224
225
226
# File 'lib/sunshine/shell.rb', line 220

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

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

Checks if timeout occurred.

Returns:

  • (Boolean)


232
233
234
# File 'lib/sunshine/shell.rb', line 232

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

#update_timeoutObject

Update the time of the last command activity



240
241
242
# File 'lib/sunshine/shell.rb', line 240

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.



250
251
252
253
254
# File 'lib/sunshine/shell.rb', line 250

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

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

Write string to stdout (by default).



260
261
262
# File 'lib/sunshine/shell.rb', line 260

def write str
  @output.write str
end