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.
Constant Summary collapse
- LOGIN_LOOP =
The loop to keep the ssh connection open.
"echo ok; echo ready; "+ "for (( ; ; )); do kill -0 $PPID && sleep 10 || exit; done;"
- LOGIN_TIMEOUT =
30
Constants inherited from Shell
Shell::LOCAL_HOST, Shell::LOCAL_USER
Instance Attribute Summary collapse
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#parent_pid ⇒ Object
readonly
Returns the value of attribute parent_pid.
-
#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, #pid, #sudo, #timeout
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
-
#build_remote_cmd(cmd, options = {}) ⇒ Object
Builds an ssh command with permissions, env, etc.
-
#build_rsync_flags(options) ⇒ Object
Figure out which rsync flags to use.
-
#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.
-
#rsync_cmd(from_path, to_path, options = {}) ⇒ Object
Creates an rsync command.
-
#ssh_cmd(cmd, options = nil) ⇒ Object
Wraps the command in an ssh call.
-
#tty!(cmd = nil) ⇒ Object
Start an interactive shell with preset permissions and env.
-
#upload(from_path, to_path, options = {}, &block) ⇒ Object
Uploads a file via rsync.
Methods inherited from Shell
#==, #agree, #ask, #choose, #close, #env_cmd, #execute, #idle?, #os_name, #prompt_for_password, #quote_cmd, #sh_cmd, #sudo_cmd, #symlink, #sync, #system, #timed_out?, #update_activity, #with_mutex, #with_session, #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.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/sunshine/remote_shell.rb', line 55 def initialize host, ={} super $stdout, @host, @user = host.split("@").reverse @user ||= [:user] @rsync_flags = ["-azrP"] @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] @parent_pid = nil self.class.register self end |
Instance Attribute Details
#host ⇒ Object (readonly)
Returns the value of attribute host.
40 41 42 |
# File 'lib/sunshine/remote_shell.rb', line 40 def host @host end |
#parent_pid ⇒ Object (readonly)
Returns the value of attribute parent_pid.
40 41 42 |
# File 'lib/sunshine/remote_shell.rb', line 40 def parent_pid @parent_pid end |
#rsync_flags ⇒ Object
Returns the value of attribute rsync_flags.
41 42 43 |
# File 'lib/sunshine/remote_shell.rb', line 41 def rsync_flags @rsync_flags end |
#ssh_flags ⇒ Object
Returns the value of attribute ssh_flags.
41 42 43 |
# File 'lib/sunshine/remote_shell.rb', line 41 def ssh_flags @ssh_flags end |
#user ⇒ Object (readonly)
Returns the value of attribute user.
40 41 42 |
# File 'lib/sunshine/remote_shell.rb', line 40 def user @user end |
Class Method Details
.disconnect_all ⇒ Object
Closes all remote shell connections.
25 26 27 28 |
# File 'lib/sunshine/remote_shell.rb', line 25 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.
35 36 37 |
# File 'lib/sunshine/remote_shell.rb', line 35 def self.register remote_shell (@remote_shells ||= []) << remote_shell end |
Instance Method Details
#build_remote_cmd(cmd, options = {}) ⇒ Object
Builds an ssh command with permissions, env, etc.
210 211 212 213 214 215 |
# File 'lib/sunshine/remote_shell.rb', line 210 def build_remote_cmd cmd, ={} cmd = sh_cmd cmd cmd = env_cmd cmd cmd = sudo_cmd cmd, cmd = ssh_cmd cmd, end |
#build_rsync_flags(options) ⇒ Object
Figure out which rsync flags to use.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/sunshine/remote_shell.rb', line 232 def build_rsync_flags flags = @rsync_flags.dup remote_rsync = 'rsync' rsync_sudo = sudo_cmd remote_rsync, unless rsync_sudo == remote_rsync flags << "--rsync-path='#{ rsync_sudo.join(" ") }'" end flags << "-e \"ssh #{@ssh_flags.join(' ')}\"" if @ssh_flags flags.concat [*[:flags]] if [:flags] flags end |
#call(command_str, options = {}, &block) ⇒ Object
Runs a command via SSH. Optional block is passed the stream(stderr, stdout) and string data.
82 83 84 85 86 |
# File 'lib/sunshine/remote_shell.rb', line 82 def call command_str, ={}, &block Sunshine.logger.info @host, "Running: #{command_str}" do execute build_remote_cmd(command_str, ), &block end end |
#connect ⇒ Object
Connect to host via SSH and return process pid
92 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 121 122 |
# File 'lib/sunshine/remote_shell.rb', line 92 def connect return true if connected? cmd = ssh_cmd quote_cmd(LOGIN_LOOP), :sudo => false @parent_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 rescue nil out.close rescue nil err.close rescue nil @parent_pid end |
#connected? ⇒ Boolean
Check if SSH session is open and returns process pid
128 129 130 |
# File 'lib/sunshine/remote_shell.rb', line 128 def connected? Process.kill(0, @parent_pid) && @parent_pid rescue false end |
#disconnect ⇒ Object
Disconnect from host
136 137 138 139 |
# File 'lib/sunshine/remote_shell.rb', line 136 def disconnect kill_process @parent_pid, "HUP" rescue nil @parent_pid = nil end |
#download(from_path, to_path, options = {}, &block) ⇒ Object
Download a file via rsync
145 146 147 148 149 150 |
# File 'lib/sunshine/remote_shell.rb', line 145 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"
158 159 160 161 162 |
# File 'lib/sunshine/remote_shell.rb', line 158 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
168 169 170 |
# File 'lib/sunshine/remote_shell.rb', line 168 def file? filepath self.system "test -f #{filepath}" end |
#make_file(filepath, content, options = {}) ⇒ Object
Create a file remotely
194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/sunshine/remote_shell.rb', line 194 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 |
#rsync_cmd(from_path, to_path, options = {}) ⇒ Object
Creates an rsync command.
253 254 255 256 |
# File 'lib/sunshine/remote_shell.rb', line 253 def rsync_cmd from_path, to_path, ={} cmd = ["rsync", build_rsync_flags(), from_path, to_path] cmd.flatten.compact.join(" ") end |
#ssh_cmd(cmd, options = nil) ⇒ Object
Wraps the command in an ssh call.
262 263 264 265 266 267 268 |
# File 'lib/sunshine/remote_shell.rb', line 262 def ssh_cmd cmd, =nil ||= {} flags = [*[:flags]].concat @ssh_flags ["ssh", flags, @host, cmd].flatten.compact end |
#tty!(cmd = nil) ⇒ Object
Start an interactive shell with preset permissions and env. Optionally pass a command to be run first.
177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/sunshine/remote_shell.rb', line 177 def tty! cmd=nil sync do cmd = [cmd, "sh -il"].compact.join " && " cmd = quote_cmd cmd pid = fork do exec \ ssh_cmd(sudo_cmd(env_cmd(cmd)), :flags => "-t").to_a.join(" ") end Process.waitpid pid end end |
#upload(from_path, to_path, options = {}, &block) ⇒ Object
Uploads a file via rsync.
221 222 223 224 225 226 |
# File 'lib/sunshine/remote_shell.rb', line 221 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 |