require 'deployml/shell' require 'addressable/uri' module DeploYML # # Represents a shell running on a remote server. # class RemoteShell include Shell # The URI of the remote shell attr_reader :uri # The history of the Remote Shell attr_reader :history # # Initializes a remote shell session. # # @param [Addressable::URI, String] uri # The URI of the host to connect to. # # @yield [session] # If a block is given, it will be passed the new remote shell session. # # @yieldparam [ShellSession] session # The remote shell session. # def initialize(uri,&block) case uri when Addressable::URI @uri = uri else @uri = Addressable::URI.parse(uri.to_s) end @history = [] super(&block) replay if block end # # Enqueues a program to be ran in the session. # # @param [String] program # The name or path of the program to run. # # @param [Array] args # Additional arguments for the program. # def run(program,*args) @history << [program, *args] end # # Enqueues an `echo` command to be ran in the session. # # @param [String] message # The message to echo. # def echo(message) run 'echo', message end # # Enqueues a directory change for the session. # # @param [String] path # The path of the new current working directory to use. # # @yield [] # If a block is given, then the directory will be changed back after # the block has returned. # def cd(path) @history << ['cd', path] if block_given? yield @history << ['cd', '-'] end end # # Joins the command history together with ` && `, to form a # single command. # # @return [String] # A single command string. # def join @history.map { |command| command.map { |word| shellescape(word.to_s) }.join(' ') }.join(' && ') end # # Converts the URI to one compatible with SSH. # # @return [String] # The SSH compatible URI. # def ssh_uri new_uri = @uri.host new_uri = "#{@uri.user}@#{new_uri}" if @uri.user return new_uri end # # Starts a SSH session with the destination server. # # @param [Array] args # Additional arguments to pass to SSH. # def ssh(*args) options = [] # Add the -p option if an alternate destination port is given if @uri.port options += ['-p', @uri.port.to_s] end # append the SSH URI options << ssh_uri # append the additional arguments args.each { |arg| options << arg.to_s } return system('ssh',*options) end # # Replays the command history on the remote server. # def replay ssh(self.join) unless @history.empty? end protected # # Escapes a string so that it can be safely used in a Bourne shell # command line. # # Note that a resulted string should be used unquoted and is not # intended for use in double quotes nor in single quotes. # # @param [String] str # The string to escape. # # @return [String] # The shell-escaped string. # # @example # open("| grep #{Shellwords.escape(pattern)} file") { |pipe| # # ... # } # # @note Vendored from `shellwords.rb` line 72 from Ruby 1.9.2. # def shellescape(str) # An empty argument will be skipped, so return empty quotes. return "''" if str.empty? str = str.dup # Process as a single byte sequence because not all shell # implementations are multibyte aware. str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1") # A LF cannot be escaped with a backslash because a backslash + LF # combo is regarded as line continuation and simply ignored. str.gsub!(/\n/, "'\n'") return str end end end