Class: Sunshine::Shell
Overview
The Shell class handles local input, output and execution to the shell.
Direct Known Subclasses
Constant Summary collapse
- LOCAL_USER =
`whoami`.chomp
- LOCAL_HOST =
`hostname`.chomp
Class Attribute Summary collapse
-
.sudo_failed_matcher ⇒ Object
The message to match in stderr to determine logging in has failed.
-
.sudo_prompt_matcher ⇒ Object
The message to match in stderr to determine a password is required.
Instance Attribute Summary collapse
-
#env ⇒ Object
Returns the value of attribute env.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#input ⇒ Object
readonly
Returns the value of attribute input.
-
#mutex ⇒ Object
readonly
Returns the value of attribute mutex.
-
#output ⇒ Object
readonly
Returns the value of attribute output.
-
#password ⇒ Object
readonly
Returns the value of attribute password.
-
#pid ⇒ Object
readonly
Returns the value of attribute pid.
-
#sudo ⇒ Object
Returns the value of attribute sudo.
-
#timeout ⇒ Object
Returns the value of attribute timeout.
-
#user ⇒ Object
readonly
Returns the value of attribute user.
Instance Method Summary collapse
-
#==(shell) ⇒ Object
Checks for equality.
-
#agree(*args, &block) ⇒ Object
Prompt the user to agree.
-
#ask(*args, &block) ⇒ Object
Prompt the user for input.
-
#call(cmd, options = {}, &block) ⇒ Object
Execute a command on the local system and return the output.
-
#choose(&block) ⇒ Object
Prompt the user to make a choice.
-
#close ⇒ Object
Close the output IO.
-
#connect ⇒ Object
Returns true.
-
#connected? ⇒ Boolean
Returns true.
-
#disconnect ⇒ Object
Returns true.
-
#download(from_path, to_path, options = {}, &block) ⇒ Object
(also: #upload)
Copies a file.
-
#env_cmd(cmd, env_hash = @env) ⇒ Object
Build an env command if an env_hash is passed.
-
#execute(cmd) ⇒ Object
Execute a command with open4 and loop until the process exits.
-
#expand_path(path) ⇒ Object
Expands the path.
-
#file?(filepath) ⇒ Boolean
Checks if file exists.
-
#idle?(start_time = @cmd_activity, max_time = @idle_time) ⇒ Boolean
Checks if shell is still receiving data.
-
#initialize(output = $stdout, options = {}) ⇒ Shell
constructor
A new instance of Shell.
-
#make_file(filepath, content, options = {}) ⇒ Object
Write a file.
-
#os_name ⇒ Object
Get the name of the OS.
-
#prompt_for_password ⇒ Object
Prompt the user for a password.
-
#quote_cmd(cmd) ⇒ Object
Wrap command in quotes and escape as needed.
-
#sh_cmd(cmd) ⇒ Object
Build an sh -c command.
-
#sudo_cmd(cmd, sudo_val = nil) ⇒ Object
Build a command with sudo.
-
#symlink(target, symlink_name) ⇒ Object
Force symlinking a directory.
-
#sync ⇒ Object
Synchronize a block with the current mutex if it exists.
-
#system(cmd, options = nil) ⇒ Object
Returns true if command was run successfully, otherwise returns false.
-
#timed_out?(start_time = @cmd_activity, max_time = @timeout) ⇒ Boolean
Checks if timeout occurred.
-
#tty!(cmd = nil) ⇒ Object
Start an interactive shell with preset permissions and env.
-
#update_activity ⇒ Object
Update the time of the last command activity.
-
#with_mutex(mutex) ⇒ Object
Execute a block while setting the shell’s mutex.
-
#with_session ⇒ Object
Runs the passed block within a connection session.
-
#write(str) ⇒ Object
(also: #<<)
Write string to stdout (by default).
Constructor Details
#initialize(output = $stdout, options = {}) ⇒ Shell
Returns a new instance of Shell.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/sunshine/shell.rb', line 31 def initialize output = $stdout, ={} @output = output $stdin.sync @input = HighLine.new $stdin @user = LOCAL_USER @host = LOCAL_HOST @sudo = [:sudo] @env = [:env] || {} @password = [:password] @timeout = [:timeout] || Sunshine.timeout @idle_time = [:idle_after] || 1 @mutex = @pid = nil end |
Class Attribute Details
.sudo_failed_matcher ⇒ Object
The message to match in stderr to determine logging in has failed. Defaults to:
/^Sorry, try again./
17 18 19 |
# File 'lib/sunshine/shell.rb', line 17 def sudo_failed_matcher @sudo_failed_matcher end |
.sudo_prompt_matcher ⇒ Object
The message to match in stderr to determine a password is required. Defaults to:
/^Password:/
22 23 24 |
# File 'lib/sunshine/shell.rb', line 22 def sudo_prompt_matcher @sudo_prompt_matcher end |
Instance Attribute Details
#env ⇒ Object
Returns the value of attribute env.
29 30 31 |
# File 'lib/sunshine/shell.rb', line 29 def env @env end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
28 29 30 |
# File 'lib/sunshine/shell.rb', line 28 def host @host end |
#input ⇒ Object (readonly)
Returns the value of attribute input.
28 29 30 |
# File 'lib/sunshine/shell.rb', line 28 def input @input end |
#mutex ⇒ Object (readonly)
Returns the value of attribute mutex.
28 29 30 |
# File 'lib/sunshine/shell.rb', line 28 def mutex @mutex end |
#output ⇒ Object (readonly)
Returns the value of attribute output.
28 29 30 |
# File 'lib/sunshine/shell.rb', line 28 def output @output end |
#password ⇒ Object (readonly)
Returns the value of attribute password.
28 29 30 |
# File 'lib/sunshine/shell.rb', line 28 def password @password end |
#pid ⇒ Object (readonly)
Returns the value of attribute pid.
28 29 30 |
# File 'lib/sunshine/shell.rb', line 28 def pid @pid end |
#sudo ⇒ Object
Returns the value of attribute sudo.
29 30 31 |
# File 'lib/sunshine/shell.rb', line 29 def sudo @sudo end |
#timeout ⇒ Object
Returns the value of attribute timeout.
29 30 31 |
# File 'lib/sunshine/shell.rb', line 29 def timeout @timeout end |
#user ⇒ Object (readonly)
Returns the value of attribute user.
28 29 30 |
# File 'lib/sunshine/shell.rb', line 28 def user @user end |
Instance Method Details
#==(shell) ⇒ Object
Checks for equality
54 55 56 |
# File 'lib/sunshine/shell.rb', line 54 def == shell @host == shell.host && @user == shell.user rescue false end |
#agree(*args, &block) ⇒ Object
Prompt the user to agree.
70 71 72 |
# File 'lib/sunshine/shell.rb', line 70 def agree(*args, &block) sync{ @input.agree(*args, &block) } end |
#ask(*args, &block) ⇒ Object
Prompt the user for input.
62 63 64 |
# File 'lib/sunshine/shell.rb', line 62 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.
78 79 80 81 82 |
# File 'lib/sunshine/shell.rb', line 78 def call cmd, ={}, &block Sunshine.logger.info @host, "Running: #{cmd}" do execute sudo_cmd(cmd, ), &block end end |
#choose(&block) ⇒ Object
Prompt the user to make a choice.
88 89 90 |
# File 'lib/sunshine/shell.rb', line 88 def choose &block sync{ @input.choose(&block) } end |
#close ⇒ Object
Close the output IO. (Required by the Logger class)
96 97 98 |
# File 'lib/sunshine/shell.rb', line 96 def close @output.close end |
#connect ⇒ Object
Returns true. Compatibility method with RemoteShell.
104 105 106 |
# File 'lib/sunshine/shell.rb', line 104 def connect true end |
#connected? ⇒ Boolean
Returns true. Compatibility method with RemoteShell.
112 113 114 |
# File 'lib/sunshine/shell.rb', line 112 def connected? true end |
#disconnect ⇒ Object
Returns true. Compatibility method with RemoteShell.
120 121 122 |
# File 'lib/sunshine/shell.rb', line 120 def disconnect true end |
#download(from_path, to_path, options = {}, &block) ⇒ Object Also known as: upload
Copies a file. Compatibility method with RemoteShell.
128 129 130 131 132 |
# File 'lib/sunshine/shell.rb', line 128 def download from_path, to_path, ={}, &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
223 224 225 226 227 228 229 |
# File 'lib/sunshine/shell.rb', line 223 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.
354 355 356 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/sunshine/shell.rb', line 354 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 = :out if stream == out stream_name = :err if stream == err stream_name = :inn if stream == inn Sunshine.logger.send log_methods[stream], "#{@host}:#{stream_name}", data # 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 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 @pid = nil end |
#expand_path(path) ⇒ Object
Expands the path. Compatibility method with RemoteShell.
140 141 142 |
# File 'lib/sunshine/shell.rb', line 140 def path File. path end |
#file?(filepath) ⇒ Boolean
Checks if file exists. Compatibility method with RemoteShell.
148 149 150 |
# File 'lib/sunshine/shell.rb', line 148 def file? filepath File.file? filepath end |
#idle?(start_time = @cmd_activity, max_time = @idle_time) ⇒ Boolean
Checks if shell is still receiving data.
173 174 175 |
# File 'lib/sunshine/shell.rb', line 173 def idle? start_time=@cmd_activity, max_time=@idle_time timed_out? start_time, max_time end |
#make_file(filepath, content, options = {}) ⇒ Object
Write a file. Compatibility method with RemoteShell.
196 197 198 |
# File 'lib/sunshine/shell.rb', line 196 def make_file filepath, content, ={} File.open(filepath, "w+"){|f| f.write(content)} end |
#os_name ⇒ Object
Get the name of the OS
204 205 206 |
# File 'lib/sunshine/shell.rb', line 204 def os_name @os_name ||= call("uname -s").strip.downcase end |
#prompt_for_password ⇒ Object
Prompt the user for a password
212 213 214 215 216 217 |
# File 'lib/sunshine/shell.rb', line 212 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.
235 236 237 238 |
# File 'lib/sunshine/shell.rb', line 235 def quote_cmd cmd cmd = [*cmd].join(" ") "'#{cmd.gsub(/'/){|s| "'\\''"}}'" end |
#sh_cmd(cmd) ⇒ Object
Build an sh -c command
244 245 246 |
# File 'lib/sunshine/shell.rb', line 244 def sh_cmd cmd ["sh", "-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
259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/sunshine/shell.rb', line 259 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 |
#symlink(target, symlink_name) ⇒ Object
Force symlinking a directory.
277 278 279 |
# File 'lib/sunshine/shell.rb', line 277 def symlink target, symlink_name call "ln -sfT #{target} #{symlink_name}" rescue false end |
#sync ⇒ Object
Synchronize a block with the current mutex if it exists.
285 286 287 288 289 290 291 |
# File 'lib/sunshine/shell.rb', line 285 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.
297 298 299 |
# File 'lib/sunshine/shell.rb', line 297 def system cmd, =nil call(cmd, ) && true rescue false end |
#timed_out?(start_time = @cmd_activity, max_time = @timeout) ⇒ Boolean
Checks if timeout occurred.
156 157 158 159 |
# File 'lib/sunshine/shell.rb', line 156 def timed_out? start_time=@cmd_activity, max_time=@timeout return unless max_time Time.now.to_f - start_time.to_f > max_time end |
#tty!(cmd = nil) ⇒ Object
Start an interactive shell with preset permissions and env. Optionally pass a command to be run first.
182 183 184 185 186 187 188 189 190 |
# File 'lib/sunshine/shell.rb', line 182 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_activity ⇒ Object
Update the time of the last command activity
165 166 167 |
# File 'lib/sunshine/shell.rb', line 165 def update_activity @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.
307 308 309 310 311 |
# File 'lib/sunshine/shell.rb', line 307 def with_mutex mutex old_mutex, @mutex = @mutex, mutex yield @mutex = old_mutex end |
#with_session ⇒ Object
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.
321 322 323 324 325 326 327 328 |
# File 'lib/sunshine/shell.rb', line 321 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).
334 335 336 |
# File 'lib/sunshine/shell.rb', line 334 def write str @output.write str end |