From 0c54087c697e9a72a70bc567e89ad2ff34e98067 Mon Sep 17 00:00:00 2001 From: Tanishq Dubey Date: Sat, 16 Nov 2019 15:10:33 -0500 Subject: [PATCH 1/3] better socket tunnel init --- harbormaster.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/harbormaster.py b/harbormaster.py index d77c1d7..50f76e4 100755 --- a/harbormaster.py +++ b/harbormaster.py @@ -12,6 +12,7 @@ dockerTunnel = None +dRunning = {} def createTunnel(port, user, host): logging.info(f'Creating tunnel for port {port} to {user}@{host}') @@ -25,7 +26,9 @@ def createTunnel(port, user, host): def main(args, spath): global dockerTunnel + global dRunning + # Not using createTunnel() here because this one off tunnel has slightly different syntax dockerTunnel = subprocess.Popen([ 'ssh', '-nNT', '-L', f'localhost:{args.p}:/var/run/docker.sock', @@ -35,17 +38,20 @@ def main(args, spath): # Get list of running containers logging.debug('Waiting for SSH to stabilize') - time.sleep(5) - dClient = docker.DockerClient(base_url=f'tcp://localhost:{args.p}') - logging.info(f'Remote docker engine connection established') + connected = False + while not connected: + try: + dClient = docker.DockerClient(base_url=f'tcp://localhost:{args.p}') + connected = True + logging.info(f'Remote docker engine connection established') + except: + logging.info(f'Waiting for tunnel to come up...') + time.sleep(1) + cList = dClient.containers.list() logging.debug(f'Found {len(cList)} running containers on connect') - dRunning = {} - """ - >>> x.attrs['NetworkSettings']['Ports'] - {'3000/tcp': [{'HostIp': '0.0.0.0', 'HostPort': '3000'}]} - """ + # Main Loop while True: cList = dClient.containers.list() tRunning = {} From 8adb0a11780ebcb3d4ccc4f9239705a886bd213e Mon Sep 17 00:00:00 2001 From: Tanishq Dubey Date: Sat, 16 Nov 2019 16:13:11 -0500 Subject: [PATCH 2/3] change to event based model, more clean up --- harbormaster.py | 66 ++++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/harbormaster.py b/harbormaster.py index 50f76e4..96fc2ea 100755 --- a/harbormaster.py +++ b/harbormaster.py @@ -36,45 +36,51 @@ def main(args, spath): ]) logging.info(f'Docker socket forwarding started to {args.user}@{args.host} on local port {args.p}') - # Get list of running containers logging.debug('Waiting for SSH to stabilize') connected = False while not connected: try: dClient = docker.DockerClient(base_url=f'tcp://localhost:{args.p}') + dClient.ping() connected = True - logging.info(f'Remote docker engine connection established') except: logging.info(f'Waiting for tunnel to come up...') time.sleep(1) + logging.info(f'Remote docker engine connection established') + + # Get list of running containers cList = dClient.containers.list() logging.debug(f'Found {len(cList)} running containers on connect') + for c in cList: + if c.id not in dRunning: + logging.info(f'Found existing container with ID {c.short_id} and name {c.name}') + cPortsRaw = c.attrs['NetworkSettings']['Ports'] + for _,v in cPortsRaw.items(): + if v: + for p in v: + port = p['HostPort'] + proc = createTunnel(port, args.user, args.host) + dRunning[c.id] = proc # Main Loop - while True: - cList = dClient.containers.list() - tRunning = {} - for c in cList: - if c.id not in dRunning: - logging.info(f'Found new container with ID {c.short_id} and name {c.name}') - cPortsRaw = c.attrs['NetworkSettings']['Ports'] - for k,v in cPortsRaw.items(): - if v: - for p in v: - port = p['HostPort'] - proc = createTunnel(port, args.user, args.host) - dRunning[c.id] = proc - tRunning[c.id] = proc - else: - tRunning[c.id] = "still running" - dead = {k: dRunning[k] for k in dRunning if k not in tRunning} - for k in dead: - logging.info(f'Closing tunnel {k}') - dRunning[k].terminate() - del dRunning[k] - time.sleep(1) - + for event in dClient.events(decode=True): + if event['Type'] == 'container' and event['status'] == 'start': + c = dClient.containers.get(event['id']) + logging.info(f'Got container start event for {c.name} ({c.short_id})') + cPortsRaw = c.attrs['NetworkSettings']['Ports'] + for _,v in cPortsRaw.items(): + if v: + for p in v: + port = p['HostPort'] + proc = createTunnel(port, args.user, args.host) + dRunning[c.id] = proc + if event['Type'] == 'container' and event['status'] == 'die': + c = dClient.containers.get(event['id']) + logging.info(f'Got container die event for {c.name} ({c.short_id})') + logging.info(f'Closing tunnel {event["id"]}') + dRunning[event['id']].terminate() + del dRunning[event['id']] def configfile(spath, port): hmsShellPrepend = [ @@ -116,9 +122,15 @@ def cleanfile(spath, port): def cleanup(spath, port): global dockerTunnel + global dRunning cleanfile(spath, port) + for k, v in dRunning.items(): + logging.info(f'Closing tunnel {k}') + v.terminate() + + logging.warning(f'Closing remote docker socket connection') dockerTunnel.terminate() @@ -138,7 +150,7 @@ def cleanup(spath, port): else: logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO) - s = os.path.abspath(os.path.expanduser('~/.zshrc')) + s = os.path.abspath(os.path.expanduser('~/.zshenv')) configfile(s, a.p) @@ -146,4 +158,6 @@ def cleanup(spath, port): except KeyboardInterrupt: logging.warning('Got CTRL-C, cleaning up (CTRL-C again to force)') cleanup(s, a.p) + except Exception: + cleanup(s, a.p) From c2cc0c95bd70f91192fa1f07900b56627a140300 Mon Sep 17 00:00:00 2001 From: Tanishq Dubey Date: Sat, 16 Nov 2019 16:38:07 -0500 Subject: [PATCH 3/3] make it PyPI compatible --- MANIFEST.in | 1 + .../harbormaster.py | 9 +++---- requirements.txt | 1 + setup.py | 26 +++++++++++++++++++ 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 MANIFEST.in rename harbormaster.py => harbormaster/harbormaster.py (99%) create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..bb3ec5f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.md diff --git a/harbormaster.py b/harbormaster/harbormaster.py similarity index 99% rename from harbormaster.py rename to harbormaster/harbormaster.py index 96fc2ea..5cf10f9 100755 --- a/harbormaster.py +++ b/harbormaster/harbormaster.py @@ -1,14 +1,13 @@ -#! /usr/bin/python3 +#! /usr/bin/env python3 import sys import os - -import docker -import argparse import logging - import subprocess import time +import argparse + +import docker dockerTunnel = None diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5979241 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +docker==3.7.3 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ad14cb3 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup + +def readme(): + with open('README.md') as f: + return f.read() + +setup(name='harbormaster', + version='0.2', + description='automating docker remote host forwarding', + long_description=readme(), + url='https://github.com/tanishq-dubey/harbormaster', + author='Tanishq Dubey', + author_email='tanishq@dubey.dev', + license='MIT', + packages=['harbormaster'], + zip_safe=False, + scripts=['harbormaster/harbormaster.py'], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', + 'Topic :: Utilities', + 'Environment :: Console', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3', + ])