Class: Nutshell::RemoteShell
- Defined in:
- lib/nutshell/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/nutshell-%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.
-
#pid ⇒ Object
readonly
Returns the value of attribute 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, #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, #os_name, #prompt_for_password, #quote_cmd, #session, #sh_cmd, #sudo_cmd, #symlink, #sync, #system, #timed_out?, #update_timeout, #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.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/nutshell/remote_shell.rb', line 56 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/nutshell-%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/nutshell/remote_shell.rb', line 41 def host @host end |
#pid ⇒ Object (readonly)
Returns the value of attribute pid.
41 42 43 |
# File 'lib/nutshell/remote_shell.rb', line 41 def pid @pid end |
#rsync_flags ⇒ Object
Returns the value of attribute rsync_flags.
42 43 44 |
# File 'lib/nutshell/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/nutshell/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/nutshell/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/nutshell/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/nutshell/remote_shell.rb', line 36 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.
209 210 211 212 213 214 |
# File 'lib/nutshell/remote_shell.rb', line 209 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.
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/nutshell/remote_shell.rb', line 229 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.
83 84 85 |
# File 'lib/nutshell/remote_shell.rb', line 83 def call command_str, ={}, &block execute build_remote_cmd(command_str, ), &block end |
#connect ⇒ Object
Connect to host via SSH and return process pid
91 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 |
# File 'lib/nutshell/remote_shell.rb', line 91 def connect return true if connected? cmd = ssh_cmd quote_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 ready && 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
124 125 126 |
# File 'lib/nutshell/remote_shell.rb', line 124 def connected? Process.kill(0, @pid) && @pid rescue false end |
#disconnect ⇒ Object
Disconnect from host
132 133 134 135 136 137 138 139 140 |
# File 'lib/nutshell/remote_shell.rb', line 132 def disconnect @inn.close rescue nil @out.close rescue nil @err.close rescue nil kill_process @pid, "HUP" rescue nil @pid = nil end |
#download(from_path, to_path, options = {}, &block) ⇒ Object
Download a file via rsync
146 147 148 149 |
# File 'lib/nutshell/remote_shell.rb', line 146 def download from_path, to_path, ={}, &block from_path = "#{@host}:#{from_path}" execute rsync_cmd(from_path, to_path, ), &block end |
#expand_path(path) ⇒ Object
Expand a path:
shell. "~user/thing"
#=> "/home/user/thing"
157 158 159 160 161 |
# File 'lib/nutshell/remote_shell.rb', line 157 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
167 168 169 |
# File 'lib/nutshell/remote_shell.rb', line 167 def file? filepath self.system "test -f #{filepath}" end |
#make_file(filepath, content, options = {}) ⇒ Object
Create a file remotely
193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/nutshell/remote_shell.rb', line 193 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.
250 251 252 253 |
# File 'lib/nutshell/remote_shell.rb', line 250 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.
259 260 261 262 263 264 265 |
# File 'lib/nutshell/remote_shell.rb', line 259 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.
176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/nutshell/remote_shell.rb', line 176 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
220 221 222 223 |
# File 'lib/nutshell/remote_shell.rb', line 220 def upload from_path, to_path, ={}, &block to_path = "#{@host}:#{to_path}" execute rsync_cmd(from_path, to_path, ), &block end |