Class: Sunshine::RemoteShell
- Defined in:
- lib/sunshine/remote_shell.rb
Overview
Keeps an SSH connection open to a server the app will be deployed to. Deploy servers use the ssh command and support any ssh feature. By default, deploy servers use the ControlMaster feature to share socket connections, with the ControlPath = ~/.ssh/sunshine-%r%h:%p
Setting session-persistant environment variables is supported by accessing the @env attribute.
Defined Under Namespace
Classes: ConnectionError
Constant Summary collapse
- LOGIN_LOOP =
The loop to keep the ssh connection open.
"echo connected; echo ready; for (( ; ; )); do sleep 10; done"- LOGIN_TIMEOUT =
30
Constants inherited from Shell
Shell::LOCAL_HOST, Shell::LOCAL_USER, Shell::SUDO_FAILED, Shell::SUDO_PROMPT, Shell::TIMEOUT
Instance Attribute Summary collapse
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#rsync_flags ⇒ Object
Returns the value of attribute rsync_flags.
-
#ssh_flags ⇒ Object
Returns the value of attribute ssh_flags.
-
#user ⇒ Object
readonly
Returns the value of attribute user.
Attributes inherited from Shell
#env, #input, #mutex, #output, #password, #sudo
Class Method Summary collapse
-
.disconnect_all ⇒ Object
Closes all remote shell connections.
-
.register(remote_shell) ⇒ Object
Registers a remote shell for global access from the class.
Instance Method Summary collapse
-
#call(command_str, options = {}, &block) ⇒ Object
Runs a command via SSH.
-
#connect ⇒ Object
Connect to host via SSH and return process pid.
-
#connected? ⇒ Boolean
Check if SSH session is open and returns process pid.
-
#disconnect ⇒ Object
Disconnect from host.
-
#download(from_path, to_path, options = {}, &block) ⇒ Object
Download a file via rsync.
-
#expand_path(path) ⇒ Object
Expand a path: shell.expand_path “~user/thing” #=> “/home/user/thing”.
-
#file?(filepath) ⇒ Boolean
Checks if the given file exists.
-
#initialize(host, options = {}) ⇒ RemoteShell
constructor
Remote shells essentially need a host and optional user.
-
#make_file(filepath, content, options = {}) ⇒ Object
Create a file remotely.
-
#upload(from_path, to_path, options = {}, &block) ⇒ Object
Uploads a file via rsync.
Methods inherited from Shell
#==, #agree, #ask, #close, #env_cmd, #execute, #os_name, #prompt_for_password, #sh_cmd, #sudo_cmd, #symlink, #sync, #timed_out?, #update_timeout, #with_mutex, #write
Constructor Details
#initialize(host, options = {}) ⇒ RemoteShell
Remote shells essentially need a host and optional user. Typical instantiation is done through either of these methods:
RemoteShell.new "user@host"
RemoteShell.new "host", :user => "user"
The constructor also supports the following options:
- :env
-
hash - hash of environment variables to set for the ssh session
- :password
-
string - password for ssh login; if missing the deploy server
will attempt to prompt the user for a password.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/sunshine/remote_shell.rb', line 56 def initialize host, ={} super $stdout, @host, @user = host.split("@").reverse @user ||= [:user] @rsync_flags = ["-azP"] @rsync_flags.concat [*[:rsync_flags]] if [:rsync_flags] @ssh_flags = [ "-o ControlMaster=auto", "-o ControlPath=~/.ssh/sunshine-%r@%h:%p" ] @ssh_flags.concat ["-l", @user] if @user @ssh_flags.concat [*[:ssh_flags]] if [:ssh_flags] @pid, @inn, @out, @err = nil self.class.register self end |
Instance Attribute Details
#host ⇒ Object (readonly)
Returns the value of attribute host.
41 42 43 |
# File 'lib/sunshine/remote_shell.rb', line 41 def host @host end |
#rsync_flags ⇒ Object
Returns the value of attribute rsync_flags.
42 43 44 |
# File 'lib/sunshine/remote_shell.rb', line 42 def rsync_flags @rsync_flags end |
#ssh_flags ⇒ Object
Returns the value of attribute ssh_flags.
42 43 44 |
# File 'lib/sunshine/remote_shell.rb', line 42 def ssh_flags @ssh_flags end |
#user ⇒ Object (readonly)
Returns the value of attribute user.
41 42 43 |
# File 'lib/sunshine/remote_shell.rb', line 41 def user @user end |
Class Method Details
.disconnect_all ⇒ Object
Closes all remote shell connections.
26 27 28 29 |
# File 'lib/sunshine/remote_shell.rb', line 26 def self.disconnect_all return unless defined?(@remote_shells) @remote_shells.each{|rs| rs.disconnect} end |
.register(remote_shell) ⇒ Object
Registers a remote shell for global access from the class. Handled automatically on initialization.
36 37 38 |
# File 'lib/sunshine/remote_shell.rb', line 36 def self.register remote_shell (@remote_shells ||= []) << remote_shell end |
Instance Method Details
#call(command_str, options = {}, &block) ⇒ Object
Runs a command via SSH. Optional block is passed the stream(stderr, stdout) and string data
83 84 85 86 87 |
# File 'lib/sunshine/remote_shell.rb', line 83 def call command_str, ={}, &block Sunshine.logger.info @host, "Running: #{command_str}" do execute ssh_cmd(command_str, ), &block end end |
#connect ⇒ Object
Connect to host via SSH and return process pid
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/sunshine/remote_shell.rb', line 93 def connect return @pid if connected? cmd = ssh_cmd LOGIN_LOOP, :sudo => false @pid, @inn, @out, @err = popen4(cmd.join(" ")) @inn.sync = true data = "" ready = nil start_time = Time.now.to_i until ready || @out.eof? data << @out.readpartial(1024) ready = data =~ /ready/ raise TimeoutError if timed_out?(start_time, LOGIN_TIMEOUT) end unless connected? disconnect host_info = [@user, @host].compact.join("@") raise ConnectionError, "Can't connect to #{host_info}" end @inn.close @pid end |
#connected? ⇒ Boolean
Check if SSH session is open and returns process pid
126 127 128 |
# File 'lib/sunshine/remote_shell.rb', line 126 def connected? Process.kill(0, @pid) && @pid rescue false end |
#disconnect ⇒ Object
Disconnect from host
134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/sunshine/remote_shell.rb', line 134 def disconnect return unless connected? @inn.close rescue nil @out.close rescue nil @err.close rescue nil kill_process @pid, "HUP" @pid = nil end |
#download(from_path, to_path, options = {}, &block) ⇒ Object
Download a file via rsync
150 151 152 153 154 155 |
# File 'lib/sunshine/remote_shell.rb', line 150 def download from_path, to_path, ={}, &block from_path = "#{@host}:#{from_path}" Sunshine.logger.info @host, "Downloading #{from_path} -> #{to_path}" do execute rsync_cmd(from_path, to_path, ), &block end end |
#expand_path(path) ⇒ Object
Expand a path:
shell. "~user/thing"
#=> "/home/user/thing"
163 164 165 166 167 |
# File 'lib/sunshine/remote_shell.rb', line 163 def path dir = File.dirname path full_dir = call "cd #{dir} && pwd" File.join full_dir, File.basename(path) end |
#file?(filepath) ⇒ Boolean
Checks if the given file exists
173 174 175 |
# File 'lib/sunshine/remote_shell.rb', line 173 def file? filepath call("test -f #{filepath}") && true rescue false end |
#make_file(filepath, content, options = {}) ⇒ Object
Create a file remotely
181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/sunshine/remote_shell.rb', line 181 def make_file filepath, content, ={} temp_filepath = "#{TMP_DIR}/#{File.basename(filepath)}_#{Time.now.to_i}#{rand(10000)}" File.open(temp_filepath, "w+"){|f| f.write(content)} self.upload temp_filepath, filepath, File.delete(temp_filepath) end |
#upload(from_path, to_path, options = {}, &block) ⇒ Object
Uploads a file via rsync
197 198 199 200 201 202 |
# File 'lib/sunshine/remote_shell.rb', line 197 def upload from_path, to_path, ={}, &block to_path = "#{@host}:#{to_path}" Sunshine.logger.info @host, "Uploading #{from_path} -> #{to_path}" do execute rsync_cmd(from_path, to_path, ), &block end end |