We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Windows: remote docker context via SSH fails on Windows
This bug-causing code was added in PR #2691 

https://github.com/aiordache/docker-py/blob/f5531a94e1096f4c8456264f6511dfe89e1c825e/docker/transport/sshconn.py#L50

causes docker-compose fail on all commands when using a remote context using SSH on windows.

SDK Version: 4.4.0
OS version: Windows 10 2004 Build 19041.630
Docker-compose version: 1.28.0-rc1

<details>
  <summary>docker version</summary>

```
Client:
 Version:           19.03.12
 API version:       1.40
 Go version:        go1.13.12
 Git commit:        0ed913b8-
 Built:             07/28/2020 16:36:03
 OS/Arch:           windows/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.14
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       5eb3275d40
  Built:            Tue Dec  1 19:18:53 2020
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.3.9
  GitCommit:        ea765aba0d05254012b0b9e595e995c09186427f
 runc:
  Version:          1.0.0-rc10
  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
```

</details>


<details>
  <summary>docker-compose version</summary>

```
docker-compose version
docker-compose version 1.28.0-rc1, build 1f469474
docker-py version: 4.4.0
CPython version: 3.9.0
OpenSSL version: OpenSSL 1.1.1g  21 Apr 2020
```

</details>


<details>
  <summary>Error thrown</summary>

```
PS C:\Users\....\Documents\Projects\....> $env:COMPOSE_PARAMIKO_SSH="0" && docker-compose ps
Traceback (most recent call last):
  File "docker\api\client.py", line 214, in _retrieve_server_version
  File "docker\api\daemon.py", line 181, in version
  File "docker\utils\decorators.py", line 46, in inner
  File "docker\api\client.py", line 237, in _get
  File "requests\sessions.py", line 543, in get
  File "requests\sessions.py", line 530, in request
  File "requests\sessions.py", line 643, in send
  File "requests\adapters.py", line 439, in send
  File "urllib3\connectionpool.py", line 670, in urlopen
  File "urllib3\connectionpool.py", line 392, in _make_request
  File "http\client.py", line 1255, in request
  File "http\client.py", line 1301, in _send_request
  File "http\client.py", line 1250, in endheaders
  File "http\client.py", line 1010, in _send_output
  File "http\client.py", line 950, in send
  File "docker\transport\sshconn.py", line 105, in connect
  File "docker\transport\sshconn.py", line 45, in connect
  File "subprocess.py", line 778, in __init__
ValueError: preexec_fn is not supported on Windows platforms

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "docker-compose", line 3, in <module>
  File "compose\cli\main.py", line 68, in main
  File "compose\cli\main.py", line 124, in perform_command
  File "compose\cli\command.py", line 60, in project_from_options
  File "compose\cli\command.py", line 152, in get_project
  File "compose\cli\docker_client.py", line 41, in get_client
  File "compose\cli\docker_client.py", line 170, in docker_client
  File "docker\api\client.py", line 197, in __init__
  File "docker\api\client.py", line 221, in _retrieve_server_version
docker.errors.DockerException: Error while fetching server API version: preexec_fn is not supported on Windows platforms
[580] Failed to execute script docker-compose
```

</details>




--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `docker/transport/sshconn.py`
Content:
```
1 import paramiko
2 import requests.adapters
3 import six
4 import logging
5 import os
6 import signal
7 import socket
8 import subprocess
9 
10 from docker.transport.basehttpadapter import BaseHTTPAdapter
11 from .. import constants
12 
13 if six.PY3:
14     import http.client as httplib
15 else:
16     import httplib
17 
18 try:
19     import requests.packages.urllib3 as urllib3
20 except ImportError:
21     import urllib3
22 
23 RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer
24 
25 
26 class SSHSocket(socket.socket):
27     def __init__(self, host):
28         super(SSHSocket, self).__init__(
29             socket.AF_INET, socket.SOCK_STREAM)
30         self.host = host
31         self.port = None
32         if ':' in host:
33             self.host, self.port = host.split(':')
34         self.proc = None
35 
36     def connect(self, **kwargs):
37         port = '' if not self.port else '-p {}'.format(self.port)
38         args = [
39             'ssh',
40             '-q',
41             self.host,
42             port,
43             'docker system dial-stdio'
44         ]
45         self.proc = subprocess.Popen(
46             ' '.join(args),
47             shell=True,
48             stdout=subprocess.PIPE,
49             stdin=subprocess.PIPE,
50             preexec_fn=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))
51 
52     def _write(self, data):
53         if not self.proc or self.proc.stdin.closed:
54             raise Exception('SSH subprocess not initiated.'
55                             'connect() must be called first.')
56         written = self.proc.stdin.write(data)
57         self.proc.stdin.flush()
58         return written
59 
60     def sendall(self, data):
61         self._write(data)
62 
63     def send(self, data):
64         return self._write(data)
65 
66     def recv(self, n):
67         if not self.proc:
68             raise Exception('SSH subprocess not initiated.'
69                             'connect() must be called first.')
70         return self.proc.stdout.read(n)
71 
72     def makefile(self, mode):
73         if not self.proc:
74             self.connect()
75         if six.PY3:
76             self.proc.stdout.channel = self
77 
78         return self.proc.stdout
79 
80     def close(self):
81         if not self.proc or self.proc.stdin.closed:
82             return
83         self.proc.stdin.write(b'\n\n')
84         self.proc.stdin.flush()
85         self.proc.terminate()
86 
87 
88 class SSHConnection(httplib.HTTPConnection, object):
89     def __init__(self, ssh_transport=None, timeout=60, host=None):
90         super(SSHConnection, self).__init__(
91             'localhost', timeout=timeout
92         )
93         self.ssh_transport = ssh_transport
94         self.timeout = timeout
95         self.ssh_host = host
96 
97     def connect(self):
98         if self.ssh_transport:
99             sock = self.ssh_transport.open_session()
100             sock.settimeout(self.timeout)
101             sock.exec_command('docker system dial-stdio')
102         else:
103             sock = SSHSocket(self.ssh_host)
104             sock.settimeout(self.timeout)
105             sock.connect()
106 
107         self.sock = sock
108 
109 
110 class SSHConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
111     scheme = 'ssh'
112 
113     def __init__(self, ssh_client=None, timeout=60, maxsize=10, host=None):
114         super(SSHConnectionPool, self).__init__(
115             'localhost', timeout=timeout, maxsize=maxsize
116         )
117         self.ssh_transport = None
118         self.timeout = timeout
119         if ssh_client:
120             self.ssh_transport = ssh_client.get_transport()
121         self.ssh_host = host
122         self.ssh_port = None
123         if ':' in host:
124             self.ssh_host, self.ssh_port = host.split(':')
125 
126     def _new_conn(self):
127         return SSHConnection(self.ssh_transport, self.timeout, self.ssh_host)
128 
129     # When re-using connections, urllib3 calls fileno() on our
130     # SSH channel instance, quickly overloading our fd limit. To avoid this,
131     # we override _get_conn
132     def _get_conn(self, timeout):
133         conn = None
134         try:
135             conn = self.pool.get(block=self.block, timeout=timeout)
136 
137         except AttributeError:  # self.pool is None
138             raise urllib3.exceptions.ClosedPoolError(self, "Pool is closed.")
139 
140         except six.moves.queue.Empty:
141             if self.block:
142                 raise urllib3.exceptions.EmptyPoolError(
143                     self,
144                     "Pool reached maximum size and no more "
145                     "connections are allowed."
146                 )
147             pass  # Oh well, we'll create a new connection then
148 
149         return conn or self._new_conn()
150 
151 
152 class SSHHTTPAdapter(BaseHTTPAdapter):
153 
154     __attrs__ = requests.adapters.HTTPAdapter.__attrs__ + [
155         'pools', 'timeout', 'ssh_client', 'ssh_params', 'max_pool_size'
156     ]
157 
158     def __init__(self, base_url, timeout=60,
159                  pool_connections=constants.DEFAULT_NUM_POOLS,
160                  max_pool_size=constants.DEFAULT_MAX_POOL_SIZE,
161                  shell_out=True):
162         self.ssh_client = None
163         if not shell_out:
164             self._create_paramiko_client(base_url)
165             self._connect()
166 
167         self.ssh_host = base_url.lstrip('ssh://')
168         self.timeout = timeout
169         self.max_pool_size = max_pool_size
170         self.pools = RecentlyUsedContainer(
171             pool_connections, dispose_func=lambda p: p.close()
172         )
173         super(SSHHTTPAdapter, self).__init__()
174 
175     def _create_paramiko_client(self, base_url):
176         logging.getLogger("paramiko").setLevel(logging.WARNING)
177         self.ssh_client = paramiko.SSHClient()
178         base_url = six.moves.urllib_parse.urlparse(base_url)
179         self.ssh_params = {
180             "hostname": base_url.hostname,
181             "port": base_url.port,
182             "username": base_url.username
183             }
184         ssh_config_file = os.path.expanduser("~/.ssh/config")
185         if os.path.exists(ssh_config_file):
186             conf = paramiko.SSHConfig()
187             with open(ssh_config_file) as f:
188                 conf.parse(f)
189             host_config = conf.lookup(base_url.hostname)
190             self.ssh_conf = host_config
191             if 'proxycommand' in host_config:
192                 self.ssh_params["sock"] = paramiko.ProxyCommand(
193                     self.ssh_conf['proxycommand']
194                 )
195             if 'hostname' in host_config:
196                 self.ssh_params['hostname'] = host_config['hostname']
197             if base_url.port is None and 'port' in host_config:
198                 self.ssh_params['port'] = self.ssh_conf['port']
199             if base_url.username is None and 'user' in host_config:
200                 self.ssh_params['username'] = self.ssh_conf['user']
201 
202         self.ssh_client.load_system_host_keys()
203         self.ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())
204 
205     def _connect(self):
206         if self.ssh_client:
207             self.ssh_client.connect(**self.ssh_params)
208 
209     def get_connection(self, url, proxies=None):
210         if not self.ssh_client:
211             return SSHConnectionPool(
212                 ssh_client=self.ssh_client,
213                 timeout=self.timeout,
214                 maxsize=self.max_pool_size,
215                 host=self.ssh_host
216             )
217         with self.pools.lock:
218             pool = self.pools.get(url)
219             if pool:
220                 return pool
221 
222             # Connection is closed try a reconnect
223             if self.ssh_client and not self.ssh_client.get_transport():
224                 self._connect()
225 
226             pool = SSHConnectionPool(
227                 ssh_client=self.ssh_client,
228                 timeout=self.timeout,
229                 maxsize=self.max_pool_size,
230                 host=self.ssh_host
231             )
232             self.pools[url] = pool
233 
234         return pool
235 
236     def close(self):
237         super(SSHHTTPAdapter, self).close()
238         if self.ssh_client:
239             self.ssh_client.close()
240
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/docker/transport/sshconn.py b/docker/transport/sshconn.py
--- a/docker/transport/sshconn.py
+++ b/docker/transport/sshconn.py
@@ -42,12 +42,17 @@
             port,
             'docker system dial-stdio'
         ]
+
+        preexec_func = None
+        if not constants.IS_WINDOWS_PLATFORM:
+            preexec_func = lambda: signal.signal(signal.SIGINT, signal.SIG_IGN)
+
         self.proc = subprocess.Popen(
             ' '.join(args),
             shell=True,
             stdout=subprocess.PIPE,
             stdin=subprocess.PIPE,
-            preexec_fn=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))
+            preexec_fn=preexec_func)
 
     def _write(self, data):
         if not self.proc or self.proc.stdin.closed:

{"golden_diff": "diff --git a/docker/transport/sshconn.py b/docker/transport/sshconn.py\n--- a/docker/transport/sshconn.py\n+++ b/docker/transport/sshconn.py\n@@ -42,12 +42,17 @@\n             port,\n             'docker system dial-stdio'\n         ]\n+\n+        preexec_func = None\n+        if not constants.IS_WINDOWS_PLATFORM:\n+            preexec_func = lambda: signal.signal(signal.SIGINT, signal.SIG_IGN)\n+\n         self.proc = subprocess.Popen(\n             ' '.join(args),\n             shell=True,\n             stdout=subprocess.PIPE,\n             stdin=subprocess.PIPE,\n-            preexec_fn=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))\n+            preexec_fn=preexec_func)\n \n     def _write(self, data):\n         if not self.proc or self.proc.stdin.closed:\n", "issue": "Windows: remote docker context via SSH fails on Windows\nThis bug-causing code was added in PR #2691 \r\n\r\nhttps://github.com/aiordache/docker-py/blob/f5531a94e1096f4c8456264f6511dfe89e1c825e/docker/transport/sshconn.py#L50\r\n\r\ncauses docker-compose fail on all commands when using a remote context using SSH on windows.\r\n\r\nSDK Version: 4.4.0\r\nOS version: Windows 10 2004 Build 19041.630\r\nDocker-compose version: 1.28.0-rc1\r\n\r\n<details>\r\n  <summary>docker version</summary>\r\n\r\n```\r\nClient:\r\n Version:           19.03.12\r\n API version:       1.40\r\n Go version:        go1.13.12\r\n Git commit:        0ed913b8-\r\n Built:             07/28/2020 16:36:03\r\n OS/Arch:           windows/amd64\r\n Experimental:      false\r\n\r\nServer: Docker Engine - Community\r\n Engine:\r\n  Version:          19.03.14\r\n  API version:      1.40 (minimum version 1.12)\r\n  Go version:       go1.13.15\r\n  Git commit:       5eb3275d40\r\n  Built:            Tue Dec  1 19:18:53 2020\r\n  OS/Arch:          linux/amd64\r\n  Experimental:     false\r\n containerd:\r\n  Version:          1.3.9\r\n  GitCommit:        ea765aba0d05254012b0b9e595e995c09186427f\r\n runc:\r\n  Version:          1.0.0-rc10\r\n  GitCommit:        dc9208a3303feef5b3839f4323d9beb36df0a9dd\r\n docker-init:\r\n  Version:          0.18.0\r\n  GitCommit:        fec3683\r\n```\r\n\r\n</details>\r\n\r\n\r\n<details>\r\n  <summary>docker-compose version</summary>\r\n\r\n```\r\ndocker-compose version\r\ndocker-compose version 1.28.0-rc1, build 1f469474\r\ndocker-py version: 4.4.0\r\nCPython version: 3.9.0\r\nOpenSSL version: OpenSSL 1.1.1g  21 Apr 2020\r\n```\r\n\r\n</details>\r\n\r\n\r\n<details>\r\n  <summary>Error thrown</summary>\r\n\r\n```\r\nPS C:\\Users\\....\\Documents\\Projects\\....> $env:COMPOSE_PARAMIKO_SSH=\"0\" && docker-compose ps\r\nTraceback (most recent call last):\r\n  File \"docker\\api\\client.py\", line 214, in _retrieve_server_version\r\n  File \"docker\\api\\daemon.py\", line 181, in version\r\n  File \"docker\\utils\\decorators.py\", line 46, in inner\r\n  File \"docker\\api\\client.py\", line 237, in _get\r\n  File \"requests\\sessions.py\", line 543, in get\r\n  File \"requests\\sessions.py\", line 530, in request\r\n  File \"requests\\sessions.py\", line 643, in send\r\n  File \"requests\\adapters.py\", line 439, in send\r\n  File \"urllib3\\connectionpool.py\", line 670, in urlopen\r\n  File \"urllib3\\connectionpool.py\", line 392, in _make_request\r\n  File \"http\\client.py\", line 1255, in request\r\n  File \"http\\client.py\", line 1301, in _send_request\r\n  File \"http\\client.py\", line 1250, in endheaders\r\n  File \"http\\client.py\", line 1010, in _send_output\r\n  File \"http\\client.py\", line 950, in send\r\n  File \"docker\\transport\\sshconn.py\", line 105, in connect\r\n  File \"docker\\transport\\sshconn.py\", line 45, in connect\r\n  File \"subprocess.py\", line 778, in __init__\r\nValueError: preexec_fn is not supported on Windows platforms\r\n\r\nDuring handling of the above exception, another exception occurred:\r\n\r\nTraceback (most recent call last):\r\n  File \"docker-compose\", line 3, in <module>\r\n  File \"compose\\cli\\main.py\", line 68, in main\r\n  File \"compose\\cli\\main.py\", line 124, in perform_command\r\n  File \"compose\\cli\\command.py\", line 60, in project_from_options\r\n  File \"compose\\cli\\command.py\", line 152, in get_project\r\n  File \"compose\\cli\\docker_client.py\", line 41, in get_client\r\n  File \"compose\\cli\\docker_client.py\", line 170, in docker_client\r\n  File \"docker\\api\\client.py\", line 197, in __init__\r\n  File \"docker\\api\\client.py\", line 221, in _retrieve_server_version\r\ndocker.errors.DockerException: Error while fetching server API version: preexec_fn is not supported on Windows platforms\r\n[580] Failed to execute script docker-compose\r\n```\r\n\r\n</details>\r\n\r\n\r\n\n", "before_files": [{"content": "import paramiko\nimport requests.adapters\nimport six\nimport logging\nimport os\nimport signal\nimport socket\nimport subprocess\n\nfrom docker.transport.basehttpadapter import BaseHTTPAdapter\nfrom .. import constants\n\nif six.PY3:\n    import http.client as httplib\nelse:\n    import httplib\n\ntry:\n    import requests.packages.urllib3 as urllib3\nexcept ImportError:\n    import urllib3\n\nRecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer\n\n\nclass SSHSocket(socket.socket):\n    def __init__(self, host):\n        super(SSHSocket, self).__init__(\n            socket.AF_INET, socket.SOCK_STREAM)\n        self.host = host\n        self.port = None\n        if ':' in host:\n            self.host, self.port = host.split(':')\n        self.proc = None\n\n    def connect(self, **kwargs):\n        port = '' if not self.port else '-p {}'.format(self.port)\n        args = [\n            'ssh',\n            '-q',\n            self.host,\n            port,\n            'docker system dial-stdio'\n        ]\n        self.proc = subprocess.Popen(\n            ' '.join(args),\n            shell=True,\n            stdout=subprocess.PIPE,\n            stdin=subprocess.PIPE,\n            preexec_fn=lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))\n\n    def _write(self, data):\n        if not self.proc or self.proc.stdin.closed:\n            raise Exception('SSH subprocess not initiated.'\n                            'connect() must be called first.')\n        written = self.proc.stdin.write(data)\n        self.proc.stdin.flush()\n        return written\n\n    def sendall(self, data):\n        self._write(data)\n\n    def send(self, data):\n        return self._write(data)\n\n    def recv(self, n):\n        if not self.proc:\n            raise Exception('SSH subprocess not initiated.'\n                            'connect() must be called first.')\n        return self.proc.stdout.read(n)\n\n    def makefile(self, mode):\n        if not self.proc:\n            self.connect()\n        if six.PY3:\n            self.proc.stdout.channel = self\n\n        return self.proc.stdout\n\n    def close(self):\n        if not self.proc or self.proc.stdin.closed:\n            return\n        self.proc.stdin.write(b'\\n\\n')\n        self.proc.stdin.flush()\n        self.proc.terminate()\n\n\nclass SSHConnection(httplib.HTTPConnection, object):\n    def __init__(self, ssh_transport=None, timeout=60, host=None):\n        super(SSHConnection, self).__init__(\n            'localhost', timeout=timeout\n        )\n        self.ssh_transport = ssh_transport\n        self.timeout = timeout\n        self.ssh_host = host\n\n    def connect(self):\n        if self.ssh_transport:\n            sock = self.ssh_transport.open_session()\n            sock.settimeout(self.timeout)\n            sock.exec_command('docker system dial-stdio')\n        else:\n            sock = SSHSocket(self.ssh_host)\n            sock.settimeout(self.timeout)\n            sock.connect()\n\n        self.sock = sock\n\n\nclass SSHConnectionPool(urllib3.connectionpool.HTTPConnectionPool):\n    scheme = 'ssh'\n\n    def __init__(self, ssh_client=None, timeout=60, maxsize=10, host=None):\n        super(SSHConnectionPool, self).__init__(\n            'localhost', timeout=timeout, maxsize=maxsize\n        )\n        self.ssh_transport = None\n        self.timeout = timeout\n        if ssh_client:\n            self.ssh_transport = ssh_client.get_transport()\n        self.ssh_host = host\n        self.ssh_port = None\n        if ':' in host:\n            self.ssh_host, self.ssh_port = host.split(':')\n\n    def _new_conn(self):\n        return SSHConnection(self.ssh_transport, self.timeout, self.ssh_host)\n\n    # When re-using connections, urllib3 calls fileno() on our\n    # SSH channel instance, quickly overloading our fd limit. To avoid this,\n    # we override _get_conn\n    def _get_conn(self, timeout):\n        conn = None\n        try:\n            conn = self.pool.get(block=self.block, timeout=timeout)\n\n        except AttributeError:  # self.pool is None\n            raise urllib3.exceptions.ClosedPoolError(self, \"Pool is closed.\")\n\n        except six.moves.queue.Empty:\n            if self.block:\n                raise urllib3.exceptions.EmptyPoolError(\n                    self,\n                    \"Pool reached maximum size and no more \"\n                    \"connections are allowed.\"\n                )\n            pass  # Oh well, we'll create a new connection then\n\n        return conn or self._new_conn()\n\n\nclass SSHHTTPAdapter(BaseHTTPAdapter):\n\n    __attrs__ = requests.adapters.HTTPAdapter.__attrs__ + [\n        'pools', 'timeout', 'ssh_client', 'ssh_params', 'max_pool_size'\n    ]\n\n    def __init__(self, base_url, timeout=60,\n                 pool_connections=constants.DEFAULT_NUM_POOLS,\n                 max_pool_size=constants.DEFAULT_MAX_POOL_SIZE,\n                 shell_out=True):\n        self.ssh_client = None\n        if not shell_out:\n            self._create_paramiko_client(base_url)\n            self._connect()\n\n        self.ssh_host = base_url.lstrip('ssh://')\n        self.timeout = timeout\n        self.max_pool_size = max_pool_size\n        self.pools = RecentlyUsedContainer(\n            pool_connections, dispose_func=lambda p: p.close()\n        )\n        super(SSHHTTPAdapter, self).__init__()\n\n    def _create_paramiko_client(self, base_url):\n        logging.getLogger(\"paramiko\").setLevel(logging.WARNING)\n        self.ssh_client = paramiko.SSHClient()\n        base_url = six.moves.urllib_parse.urlparse(base_url)\n        self.ssh_params = {\n            \"hostname\": base_url.hostname,\n            \"port\": base_url.port,\n            \"username\": base_url.username\n            }\n        ssh_config_file = os.path.expanduser(\"~/.ssh/config\")\n        if os.path.exists(ssh_config_file):\n            conf = paramiko.SSHConfig()\n            with open(ssh_config_file) as f:\n                conf.parse(f)\n            host_config = conf.lookup(base_url.hostname)\n            self.ssh_conf = host_config\n            if 'proxycommand' in host_config:\n                self.ssh_params[\"sock\"] = paramiko.ProxyCommand(\n                    self.ssh_conf['proxycommand']\n                )\n            if 'hostname' in host_config:\n                self.ssh_params['hostname'] = host_config['hostname']\n            if base_url.port is None and 'port' in host_config:\n                self.ssh_params['port'] = self.ssh_conf['port']\n            if base_url.username is None and 'user' in host_config:\n                self.ssh_params['username'] = self.ssh_conf['user']\n\n        self.ssh_client.load_system_host_keys()\n        self.ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())\n\n    def _connect(self):\n        if self.ssh_client:\n            self.ssh_client.connect(**self.ssh_params)\n\n    def get_connection(self, url, proxies=None):\n        if not self.ssh_client:\n            return SSHConnectionPool(\n                ssh_client=self.ssh_client,\n                timeout=self.timeout,\n                maxsize=self.max_pool_size,\n                host=self.ssh_host\n            )\n        with self.pools.lock:\n            pool = self.pools.get(url)\n            if pool:\n                return pool\n\n            # Connection is closed try a reconnect\n            if self.ssh_client and not self.ssh_client.get_transport():\n                self._connect()\n\n            pool = SSHConnectionPool(\n                ssh_client=self.ssh_client,\n                timeout=self.timeout,\n                maxsize=self.max_pool_size,\n                host=self.ssh_host\n            )\n            self.pools[url] = pool\n\n        return pool\n\n    def close(self):\n        super(SSHHTTPAdapter, self).close()\n        if self.ssh_client:\n            self.ssh_client.close()\n", "path": "docker/transport/sshconn.py"}], "after_files": [{"content": "import paramiko\nimport requests.adapters\nimport six\nimport logging\nimport os\nimport signal\nimport socket\nimport subprocess\n\nfrom docker.transport.basehttpadapter import BaseHTTPAdapter\nfrom .. import constants\n\nif six.PY3:\n    import http.client as httplib\nelse:\n    import httplib\n\ntry:\n    import requests.packages.urllib3 as urllib3\nexcept ImportError:\n    import urllib3\n\nRecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer\n\n\nclass SSHSocket(socket.socket):\n    def __init__(self, host):\n        super(SSHSocket, self).__init__(\n            socket.AF_INET, socket.SOCK_STREAM)\n        self.host = host\n        self.port = None\n        if ':' in host:\n            self.host, self.port = host.split(':')\n        self.proc = None\n\n    def connect(self, **kwargs):\n        port = '' if not self.port else '-p {}'.format(self.port)\n        args = [\n            'ssh',\n            '-q',\n            self.host,\n            port,\n            'docker system dial-stdio'\n        ]\n\n        preexec_func = None\n        if not constants.IS_WINDOWS_PLATFORM:\n            preexec_func = lambda: signal.signal(signal.SIGINT, signal.SIG_IGN)\n\n        self.proc = subprocess.Popen(\n            ' '.join(args),\n            shell=True,\n            stdout=subprocess.PIPE,\n            stdin=subprocess.PIPE,\n            preexec_fn=preexec_func)\n\n    def _write(self, data):\n        if not self.proc or self.proc.stdin.closed:\n            raise Exception('SSH subprocess not initiated.'\n                            'connect() must be called first.')\n        written = self.proc.stdin.write(data)\n        self.proc.stdin.flush()\n        return written\n\n    def sendall(self, data):\n        self._write(data)\n\n    def send(self, data):\n        return self._write(data)\n\n    def recv(self, n):\n        if not self.proc:\n            raise Exception('SSH subprocess not initiated.'\n                            'connect() must be called first.')\n        return self.proc.stdout.read(n)\n\n    def makefile(self, mode):\n        if not self.proc:\n            self.connect()\n        if six.PY3:\n            self.proc.stdout.channel = self\n\n        return self.proc.stdout\n\n    def close(self):\n        if not self.proc or self.proc.stdin.closed:\n            return\n        self.proc.stdin.write(b'\\n\\n')\n        self.proc.stdin.flush()\n        self.proc.terminate()\n\n\nclass SSHConnection(httplib.HTTPConnection, object):\n    def __init__(self, ssh_transport=None, timeout=60, host=None):\n        super(SSHConnection, self).__init__(\n            'localhost', timeout=timeout\n        )\n        self.ssh_transport = ssh_transport\n        self.timeout = timeout\n        self.ssh_host = host\n\n    def connect(self):\n        if self.ssh_transport:\n            sock = self.ssh_transport.open_session()\n            sock.settimeout(self.timeout)\n            sock.exec_command('docker system dial-stdio')\n        else:\n            sock = SSHSocket(self.ssh_host)\n            sock.settimeout(self.timeout)\n            sock.connect()\n\n        self.sock = sock\n\n\nclass SSHConnectionPool(urllib3.connectionpool.HTTPConnectionPool):\n    scheme = 'ssh'\n\n    def __init__(self, ssh_client=None, timeout=60, maxsize=10, host=None):\n        super(SSHConnectionPool, self).__init__(\n            'localhost', timeout=timeout, maxsize=maxsize\n        )\n        self.ssh_transport = None\n        self.timeout = timeout\n        if ssh_client:\n            self.ssh_transport = ssh_client.get_transport()\n        self.ssh_host = host\n        self.ssh_port = None\n        if ':' in host:\n            self.ssh_host, self.ssh_port = host.split(':')\n\n    def _new_conn(self):\n        return SSHConnection(self.ssh_transport, self.timeout, self.ssh_host)\n\n    # When re-using connections, urllib3 calls fileno() on our\n    # SSH channel instance, quickly overloading our fd limit. To avoid this,\n    # we override _get_conn\n    def _get_conn(self, timeout):\n        conn = None\n        try:\n            conn = self.pool.get(block=self.block, timeout=timeout)\n\n        except AttributeError:  # self.pool is None\n            raise urllib3.exceptions.ClosedPoolError(self, \"Pool is closed.\")\n\n        except six.moves.queue.Empty:\n            if self.block:\n                raise urllib3.exceptions.EmptyPoolError(\n                    self,\n                    \"Pool reached maximum size and no more \"\n                    \"connections are allowed.\"\n                )\n            pass  # Oh well, we'll create a new connection then\n\n        return conn or self._new_conn()\n\n\nclass SSHHTTPAdapter(BaseHTTPAdapter):\n\n    __attrs__ = requests.adapters.HTTPAdapter.__attrs__ + [\n        'pools', 'timeout', 'ssh_client', 'ssh_params', 'max_pool_size'\n    ]\n\n    def __init__(self, base_url, timeout=60,\n                 pool_connections=constants.DEFAULT_NUM_POOLS,\n                 max_pool_size=constants.DEFAULT_MAX_POOL_SIZE,\n                 shell_out=True):\n        self.ssh_client = None\n        if not shell_out:\n            self._create_paramiko_client(base_url)\n            self._connect()\n\n        self.ssh_host = base_url.lstrip('ssh://')\n        self.timeout = timeout\n        self.max_pool_size = max_pool_size\n        self.pools = RecentlyUsedContainer(\n            pool_connections, dispose_func=lambda p: p.close()\n        )\n        super(SSHHTTPAdapter, self).__init__()\n\n    def _create_paramiko_client(self, base_url):\n        logging.getLogger(\"paramiko\").setLevel(logging.WARNING)\n        self.ssh_client = paramiko.SSHClient()\n        base_url = six.moves.urllib_parse.urlparse(base_url)\n        self.ssh_params = {\n            \"hostname\": base_url.hostname,\n            \"port\": base_url.port,\n            \"username\": base_url.username\n            }\n        ssh_config_file = os.path.expanduser(\"~/.ssh/config\")\n        if os.path.exists(ssh_config_file):\n            conf = paramiko.SSHConfig()\n            with open(ssh_config_file) as f:\n                conf.parse(f)\n            host_config = conf.lookup(base_url.hostname)\n            self.ssh_conf = host_config\n            if 'proxycommand' in host_config:\n                self.ssh_params[\"sock\"] = paramiko.ProxyCommand(\n                    self.ssh_conf['proxycommand']\n                )\n            if 'hostname' in host_config:\n                self.ssh_params['hostname'] = host_config['hostname']\n            if base_url.port is None and 'port' in host_config:\n                self.ssh_params['port'] = self.ssh_conf['port']\n            if base_url.username is None and 'user' in host_config:\n                self.ssh_params['username'] = self.ssh_conf['user']\n\n        self.ssh_client.load_system_host_keys()\n        self.ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())\n\n    def _connect(self):\n        if self.ssh_client:\n            self.ssh_client.connect(**self.ssh_params)\n\n    def get_connection(self, url, proxies=None):\n        if not self.ssh_client:\n            return SSHConnectionPool(\n                ssh_client=self.ssh_client,\n                timeout=self.timeout,\n                maxsize=self.max_pool_size,\n                host=self.ssh_host\n            )\n        with self.pools.lock:\n            pool = self.pools.get(url)\n            if pool:\n                return pool\n\n            # Connection is closed try a reconnect\n            if self.ssh_client and not self.ssh_client.get_transport():\n                self._connect()\n\n            pool = SSHConnectionPool(\n                ssh_client=self.ssh_client,\n                timeout=self.timeout,\n                maxsize=self.max_pool_size,\n                host=self.ssh_host\n            )\n            self.pools[url] = pool\n\n        return pool\n\n    def close(self):\n        super(SSHHTTPAdapter, self).close()\n        if self.ssh_client:\n            self.ssh_client.close()\n", "path": "docker/transport/sshconn.py"}]}