Skip to content

Commit a11dcd8

Browse files
committed
Add support for connecting through SSH tunnels
2 parents 4bb5051 + 686ce98 commit a11dcd8

File tree

15 files changed

+1138
-422
lines changed

15 files changed

+1138
-422
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ docs/_build/
5252

5353
# PyBuilder
5454
target/
55+
56+
.DS_Store

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
A python client for [fleet](https://github.com/coreos/fleet)
55

6-
Full documentation available at [python-fleet.readthedocs.org](http://python-fleet.readthedocs.org/en/latest/). Source for the documentation is in ``fleet/v1/docs/``
6+
Full documentation available at [python-fleet.readthedocs.org](http://python-fleet.readthedocs.org/en/latest/). Source for the documentation is in [fleet/v1/docs/](fleet/v1/docs)
77

88
## Install
99

@@ -43,6 +43,8 @@ The [fleet API documentation](https://github.com/coreos/fleet/blob/master/Docume
4343

4444
python-fleet will attempt to retrieve and parse this document when it is instantiated. Should any error occur during this process ``ValueError`` will be raised.
4545

46+
python-fleet supports connecting through SSH tunnels. See the [full Client documentation](fleet/v1/docs/client.md) for additional information on configuring SSH tunnels.
47+
4648
from __future__ import print_function
4749

4850
# connect to fleet over tcp
@@ -52,6 +54,13 @@ python-fleet will attempt to retrieve and parse this document when it is instant
5254
print('Unable to discover fleet: {0}'.format(exc))
5355
raise SystemExit
5456

57+
# or via an ssh tunnel
58+
try:
59+
fleet_client = fleet.Client('http://127.0.0.1:49153', ssh_tunnel='198.51.100.23')
60+
except ValueError as exc:
61+
print('Unable to discover fleet: {0}'.format(exc))
62+
raise SystemExit
63+
5564
# or over a unix domain socket
5665
try:
5766
fleet_client = fleet.Client('http+unix://%2Fvar%2Frun%2Ffleet.sock')

fleet/http/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
from .unix_socket import * # NOQA
1+
from .unix_socket import * # NOQA
2+
from .ssh_tunnel import * # NOQA

fleet/http/ssh_tunnel.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
try: # pragma: no cover
2+
# python 2
3+
import httplib
4+
except ImportError: # pragma: no cover
5+
# python 3
6+
import http.client as httplib
7+
8+
import httplib2 # NOQA
9+
import sys
10+
11+
try: # pragma: no cover
12+
# python 2
13+
import urllib
14+
unquote = urllib.unquote
15+
except AttributeError: # pragma: no cover
16+
# python 3
17+
import urllib.parse
18+
unquote = urllib.parse.unquote
19+
20+
21+
class SSHTunnelProxyInfo(httplib2.ProxyInfo):
22+
def __init__(self, sock):
23+
"""A data structure for passing a socket to an httplib.HTTPConnection
24+
25+
Args:
26+
sock (socket-like): A connected socket or socket-like object.
27+
28+
"""
29+
30+
self.sock = sock
31+
32+
33+
class HTTPOverSSHTunnel(httplib.HTTPConnection):
34+
"""
35+
A hack for httplib2 that expects proxy_info to be a socket already connected
36+
to our target, rather than having to call connect() ourselves. This is used
37+
to provide basic SSH Tunnelling support.
38+
"""
39+
40+
def __init__(self, host, port=None, strict=None, timeout=None, proxy_info=None):
41+
"""
42+
Setup an HTTP connection over an already connected socket.
43+
44+
Args:
45+
host: ignored (exists for compatibility with parent)
46+
post: ignored (exists for compatibility with parent)
47+
strict: ignored (exists for compatibility with parent)
48+
timeout: ignored (exists for compatibility with parent)
49+
50+
proxy_info (SSHTunnelProxyInfo): A SSHTunnelProxyInfo instance.
51+
52+
"""
53+
54+
# do the needful
55+
httplib.HTTPConnection.__init__(self, host, port)
56+
57+
# looks like the python2 and python3 versions of httplib differ
58+
# python2, executables any callables and returns the result as proxy_info
59+
# python3 passes the callable directly to this function :(
60+
if hasattr(proxy_info, '__call__'):
61+
proxy_info = proxy_info(None)
62+
63+
# make sure we have a validate socket before we stash it
64+
if not proxy_info or not isinstance(proxy_info, SSHTunnelProxyInfo) or not proxy_info.sock:
65+
raise ValueError('This Connection must be suppplied an SSHTunnelProxyInfo via the proxy_info arg')
66+
67+
# keep it
68+
self.sock = proxy_info.sock
69+
70+
def connect(self): # pragma: no cover
71+
"""Do nothing"""
72+
# we don't need to connect, this functions job is to make sure
73+
# self.sock exists and is connected. We did that in __init__
74+
# This is just here to keep other code in the parent from fucking
75+
# with our already connected socket :)
76+
pass
77+
78+
# Add our module to httplib2 via sorta monkey patching
79+
# When a request is made, the class responsible for the scheme is looked up in this dict
80+
# So we inject our schemes and capture the SSH tunnel requests
81+
sys.modules['httplib2'].SCHEME_TO_CONNECTION['ssh+http'] = HTTPOverSSHTunnel
82+
sys.modules['httplib2'].SCHEME_TO_CONNECTION['ssh+http+unix'] = HTTPOverSSHTunnel

fleet/http/unix_socket.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,6 @@ def connect(self):
5757
raise socket.error(msg)
5858

5959
# Add our module to httplib2 via sorta monkey patching
60+
# When a request is made, the class responsible for the scheme is looked up in this dict
61+
# So we inject our schemes and capture the Unix domain requests
6062
sys.modules['httplib2'].SCHEME_TO_CONNECTION['http+unix'] = UnixConnectionWithTimeout

0 commit comments

Comments
 (0)