Skip to content

Commit e414591

Browse files
author
aiordache
committed
Alternative SSH conn that binds the remote docker socket locally
Signed-off-by: aiordache <[email protected]>
1 parent 1cb8896 commit e414591

File tree

3 files changed

+100
-2
lines changed

3 files changed

+100
-2
lines changed

docker/api/client.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
except ImportError:
4444
pass
4545

46+
try:
47+
from ..transport import SSHClientAdapter
48+
except ImportError:
49+
pass
50+
4651

4752
class APIClient(
4853
requests.Session,
@@ -161,11 +166,17 @@ def __init__(self, base_url=None, version=None,
161166
)
162167
self.mount('http+docker://', self._custom_adapter)
163168
self.base_url = 'http+docker://localnpipe'
169+
elif base_url.startswith('ssh://') and use_ssh_client:
170+
self._custom_adapter = SSHClientAdapter(
171+
base_url, timeout, pool_connections=num_pools
172+
)
173+
self.mount('http+docker://', self._custom_adapter)
174+
self._unmount('http://', 'https://')
175+
self.base_url = 'http+docker://localhost'
164176
elif base_url.startswith('ssh://'):
165177
try:
166178
self._custom_adapter = SSHHTTPAdapter(
167-
base_url, timeout, pool_connections=num_pools,
168-
shell_out=use_ssh_client
179+
base_url, timeout, pool_connections=num_pools
169180
)
170181
except NameError:
171182
raise DockerException(

docker/transport/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@
1111
from .sshconn import SSHHTTPAdapter
1212
except ImportError:
1313
pass
14+
15+
try:
16+
from .sshtunnel import SSHClientAdapter
17+
except ImportError:
18+
pass

docker/transport/sshtunnel.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import os
2+
import signal
3+
import subprocess
4+
import tempfile
5+
import time
6+
7+
try:
8+
import requests.packages.urllib3 as urllib3
9+
except ImportError:
10+
import urllib3
11+
12+
from .. import constants
13+
14+
from docker.transport.basehttpadapter import BaseHTTPAdapter
15+
from .unixconn import UnixHTTPConnectionPool
16+
17+
18+
RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer
19+
20+
21+
class SSHClientAdapter(BaseHTTPAdapter):
22+
def __init__(self, socket_url, timeout=60,
23+
pool_connections=constants.DEFAULT_NUM_POOLS):
24+
self.ssh_host = socket_url.lstrip('ssh://')
25+
self.ssh_port = None
26+
if ':' in self.ssh_host:
27+
self.ssh_host, self.ssh_port = self.ssh_host.split(':')
28+
self.timeout = timeout
29+
self.socket_path = None
30+
31+
self.__create_ssh_tunnel()
32+
33+
self.pools = RecentlyUsedContainer(
34+
pool_connections, dispose_func=lambda p: p.close()
35+
)
36+
super(SSHClientAdapter, self).__init__()
37+
38+
def __create_ssh_tunnel(self):
39+
self.temp_dir = tempfile.TemporaryDirectory()
40+
self.socket_path = os.path.join(self.temp_dir.name, "docker.sock")
41+
42+
port = '' if not self.ssh_port else '-p {}'.format(self.ssh_port)
43+
# bind remote engine socket locally to a temporary file
44+
args = [
45+
'ssh',
46+
'-NL',
47+
'{}:/var/run/docker.sock'.format(self.socket_path),
48+
self.ssh_host,
49+
port
50+
]
51+
self.proc = subprocess.Popen(
52+
' '.join(args),
53+
shell=True,
54+
preexec_fn=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))
55+
count = .0
56+
while not os.path.exists(self.socket_path):
57+
time.sleep(.1)
58+
count = count + 0.1
59+
if count > self.timeout:
60+
raise Exception("Failed to connect via SSH")
61+
62+
def get_connection(self, url, proxies=None):
63+
with self.pools.lock:
64+
pool = self.pools.get(url)
65+
if pool:
66+
return pool
67+
68+
pool = UnixHTTPConnectionPool(
69+
url, self.socket_path, self.timeout
70+
)
71+
self.pools[url] = pool
72+
73+
return pool
74+
75+
def request_url(self, request, proxies):
76+
return request.path_url
77+
78+
def close(self):
79+
super(SSHClientAdapter, self).close()
80+
if self.proc:
81+
self.proc.terminate()
82+
self.proc.wait()

0 commit comments

Comments
 (0)