Class: Indocker::SshSession
- Inherits:
-
Object
- Object
- Indocker::SshSession
- Defined in:
- lib/indocker/ssh_session.rb
Defined Under Namespace
Classes: ExecResult
Constant Summary collapse
- LOCALHOST =
'localhost'.freeze
- JUMP_CONTROL_PERSIST =
'600'.freeze
Instance Attribute Summary collapse
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#jump_host ⇒ Object
readonly
Returns the value of attribute jump_host.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#user ⇒ Object
readonly
Returns the value of attribute user.
Class Method Summary collapse
-
.for(server, logger:) ⇒ Object
Opens a session to a server as it is defined in the configuration, including its jump host, if any.
-
.jump_proxy_command(jump_host) ⇒ Object
Builds the tunnel to a jump host, shared by Net::SSH sessions and by rsync.
-
.warm_jump_host(jump_host, logger:) ⇒ Object
Opens the shared connection to a jump host up front.
Instance Method Summary collapse
- #close ⇒ Object
- #exec!(command) ⇒ Object
-
#initialize(host:, user:, port:, logger:, jump_host: nil) ⇒ SshSession
constructor
A new instance of SshSession.
- #local? ⇒ Boolean
Constructor Details
#initialize(host:, user:, port:, logger:, jump_host: nil) ⇒ SshSession
Returns a new instance of SshSession.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/indocker/ssh_session.rb', line 86 def initialize(host:, user:, port:, logger:, jump_host: nil) @host = host @user = user @port = port @jump_host = jump_host @logger = logger if host != LOCALHOST require 'net/ssh' = { port: @port, non_interactive: true, timeout: Indocker.ssh_connect_timeout, keepalive: true, keepalive_interval: 10, keepalive_maxcount: 3, } if @jump_host require 'net/ssh/proxy/command' [:proxy] = Net::SSH::Proxy::Command.new(self.class.jump_proxy_command(@jump_host)) end @ssh = Net::SSH.start(@host, @user, ) end end |
Instance Attribute Details
#host ⇒ Object (readonly)
Returns the value of attribute host.
23 24 25 |
# File 'lib/indocker/ssh_session.rb', line 23 def host @host end |
#jump_host ⇒ Object (readonly)
Returns the value of attribute jump_host.
23 24 25 |
# File 'lib/indocker/ssh_session.rb', line 23 def jump_host @jump_host end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
23 24 25 |
# File 'lib/indocker/ssh_session.rb', line 23 def logger @logger end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
23 24 25 |
# File 'lib/indocker/ssh_session.rb', line 23 def port @port end |
#user ⇒ Object (readonly)
Returns the value of attribute user.
23 24 25 |
# File 'lib/indocker/ssh_session.rb', line 23 def user @user end |
Class Method Details
.for(server, logger:) ⇒ Object
Opens a session to a server as it is defined in the configuration, including its jump host, if any. Every remote operation goes through here, so a server reached through a jump host is reached that way by binaries sync, container deploys and the deployment checker alike.
76 77 78 79 80 81 82 83 84 |
# File 'lib/indocker/ssh_session.rb', line 76 def self.for(server, logger:) new( host: server.host, user: server.user, port: server.port, jump_host: server.jump_host, logger: logger ) end |
.jump_proxy_command(jump_host) ⇒ Object
Builds the tunnel to a jump host, shared by Net::SSH sessions and by rsync.
Net::SSH::Proxy::Jump would build a bare ssh -W: a jump host that asks for a password
hangs the deploy, and probing a server whose sshd is still starting prints
"channel 0: open failed" from the ssh client on every retry.
Multiplexing matters even more. A deploy syncs and deploys to every server in parallel, and each of those opens its own tunnel; sshd drops everything above MaxStartups (10 by default), which surfaces as "connection closed by remote host" on random servers. Sharing one connection to the jump host keeps the whole fan-out down to a single tunnel.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/indocker/ssh_session.rb', line 35 def self.jump_proxy_command(jump_host) require 'digest' host = jump_host port = nil if host =~ /\A(.+):(\d+)\z/ host = $1 port = $2 end control_path = "/tmp/indocker-jump-#{Digest::MD5.hexdigest(jump_host)[0, 10]}" command = [ "ssh -q", "-o BatchMode=yes", "-o ConnectTimeout=#{Indocker.ssh_connect_timeout}", "-o ControlMaster=auto", "-o ControlPath=#{control_path}", "-o ControlPersist=#{JUMP_CONTROL_PERSIST}", ] command << "-p #{port}" if port command << "-W %h:%p" command << host command.join(' ') end |
.warm_jump_host(jump_host, logger:) ⇒ Object
Opens the shared connection to a jump host up front. Without it the first wave of parallel operations races to create the master connection and opens a burst of them.
66 67 68 69 70 |
# File 'lib/indocker/ssh_session.rb', line 66 def self.warm_jump_host(jump_host, logger:) command = jump_proxy_command(jump_host).sub("-W %h:%p ", "") Indocker::Shell.command("#{command} true", logger) end |
Instance Method Details
#close ⇒ Object
126 127 128 129 130 |
# File 'lib/indocker/ssh_session.rb', line 126 def close if @ssh @ssh.close end end |
#exec!(command) ⇒ Object
118 119 120 121 122 123 124 |
# File 'lib/indocker/ssh_session.rb', line 118 def exec!(command) if local? exec_locally!(command) else exec_remotely!(command) end end |
#local? ⇒ Boolean
114 115 116 |
# File 'lib/indocker/ssh_session.rb', line 114 def local? !@ssh end |