Skip to content

Commit

Permalink
Alternative SSH conn that binds the remote docker socket locally
Browse files Browse the repository at this point in the history
Signed-off-by: aiordache <anca.iordache@docker.com>
  • Loading branch information
aiordache committed Oct 23, 2020
1 parent 1cb8896 commit e414591
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 2 deletions.
15 changes: 13 additions & 2 deletions docker/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
except ImportError:
pass

try:
from ..transport import SSHClientAdapter
except ImportError:
pass


class APIClient(
requests.Session,
Expand Down Expand Up @@ -161,11 +166,17 @@ def __init__(self, base_url=None, version=None,
)
self.mount('http+docker://', self._custom_adapter)
self.base_url = 'http+docker://localnpipe'
elif base_url.startswith('ssh://') and use_ssh_client:
self._custom_adapter = SSHClientAdapter(
base_url, timeout, pool_connections=num_pools
)
self.mount('http+docker://', self._custom_adapter)
self._unmount('http://', 'https://')
self.base_url = 'http+docker://localhost'
elif base_url.startswith('ssh://'):
try:
self._custom_adapter = SSHHTTPAdapter(
base_url, timeout, pool_connections=num_pools,
shell_out=use_ssh_client
base_url, timeout, pool_connections=num_pools
)
except NameError:
raise DockerException(
Expand Down
5 changes: 5 additions & 0 deletions docker/transport/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@
from .sshconn import SSHHTTPAdapter
except ImportError:
pass

try:
from .sshtunnel import SSHClientAdapter
except ImportError:
pass
82 changes: 82 additions & 0 deletions docker/transport/sshtunnel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import os
import signal
import subprocess
import tempfile
import time

try:
import requests.packages.urllib3 as urllib3
except ImportError:
import urllib3

from .. import constants

from docker.transport.basehttpadapter import BaseHTTPAdapter
from .unixconn import UnixHTTPConnectionPool


RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer


class SSHClientAdapter(BaseHTTPAdapter):
def __init__(self, socket_url, timeout=60,
pool_connections=constants.DEFAULT_NUM_POOLS):
self.ssh_host = socket_url.lstrip('ssh://')
self.ssh_port = None
if ':' in self.ssh_host:
self.ssh_host, self.ssh_port = self.ssh_host.split(':')
self.timeout = timeout
self.socket_path = None

self.__create_ssh_tunnel()

self.pools = RecentlyUsedContainer(
pool_connections, dispose_func=lambda p: p.close()
)
super(SSHClientAdapter, self).__init__()

def __create_ssh_tunnel(self):
self.temp_dir = tempfile.TemporaryDirectory()
self.socket_path = os.path.join(self.temp_dir.name, "docker.sock")

port = '' if not self.ssh_port else '-p {}'.format(self.ssh_port)
# bind remote engine socket locally to a temporary file
args = [
'ssh',
'-NL',
'{}:/var/run/docker.sock'.format(self.socket_path),
self.ssh_host,
port
]
self.proc = subprocess.Popen(
' '.join(args),
shell=True,
preexec_fn=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))
count = .0
while not os.path.exists(self.socket_path):
time.sleep(.1)
count = count + 0.1
if count > self.timeout:
raise Exception("Failed to connect via SSH")

def get_connection(self, url, proxies=None):
with self.pools.lock:
pool = self.pools.get(url)
if pool:
return pool

pool = UnixHTTPConnectionPool(
url, self.socket_path, self.timeout
)
self.pools[url] = pool

return pool

def request_url(self, request, proxies):
return request.path_url

def close(self):
super(SSHClientAdapter, self).close()
if self.proc:
self.proc.terminate()
self.proc.wait()

0 comments on commit e414591

Please sign in to comment.