Class: Sunshine::RemoteShell

Inherits:
Shell
  • Object
show all
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

Attributes inherited from Shell

#env, #input, #mutex, #output, #password, #sudo

Class Method Summary collapse

Instance Method Summary collapse

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, options={}
  super $stdout, options

  @host, @user = host.split("@").reverse

  @user ||= options[:user]

  @rsync_flags = ["-azP"]
  @rsync_flags.concat [*options[:rsync_flags]] if options[:rsync_flags]

  @ssh_flags = [
    "-o ControlMaster=auto",
    "-o ControlPath=~/.ssh/sunshine-%r@%h:%p"
  ]
  @ssh_flags.concat ["-l", @user] if @user
  @ssh_flags.concat [*options[:ssh_flags]] if options[:ssh_flags]

  @pid, @inn, @out, @err = nil

  self.class.register self
end

Instance Attribute Details

#hostObject (readonly)

Returns the value of attribute host.



41
42
43
# File 'lib/sunshine/remote_shell.rb', line 41

def host
  @host
end

#rsync_flagsObject

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_flagsObject

Returns the value of attribute ssh_flags.



42
43
44
# File 'lib/sunshine/remote_shell.rb', line 42

def ssh_flags
  @ssh_flags
end

#userObject (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_allObject

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, options={}, &block
  Sunshine.logger.info @host, "Running: #{command_str}" do
    execute ssh_cmd(command_str, options), &block
  end
end

#connectObject

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 , :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, )
  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

Returns:

  • (Boolean)


126
127
128
# File 'lib/sunshine/remote_shell.rb', line 126

def connected?
  Process.kill(0, @pid) && @pid rescue false
end

#disconnectObject

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, options={}, &block
  from_path = "#{@host}:#{from_path}"
  Sunshine.logger.info @host, "Downloading #{from_path} -> #{to_path}" do
    execute rsync_cmd(from_path, to_path, options), &block
  end
end

#expand_path(path) ⇒ Object

Expand a path:

shell.expand_path "~user/thing"
#=> "/home/user/thing"


163
164
165
166
167
# File 'lib/sunshine/remote_shell.rb', line 163

def expand_path 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

Returns:

  • (Boolean)


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, options={}

  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, options

  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, options={}, &block
  to_path = "#{@host}:#{to_path}"
  Sunshine.logger.info @host, "Uploading #{from_path} -> #{to_path}" do
    execute rsync_cmd(from_path, to_path, options), &block
  end
end