Skip to content

Commit

Permalink
Make CLI expose fewer globals, formatting (#31)
Browse files Browse the repository at this point in the history
* Add the ouroboros file that will be installed by pip

* change cli.parser to cli.parse

* Update cli.py to not use the globals. Update cli tests.

* Update the CLI to use fewer globals, and update other code not to use CLI globals

* Remove unused import

* delete the script file, doesn't make sense to go here

* remove unused import

* Autopep8

* Test to_monitor

* Add tests for exceptions

* Test exception in container.running()

* version bump
  • Loading branch information
tlkamp authored and circa10a committed Oct 25, 2018
1 parent 10b36a8 commit b1a0dd9
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 115 deletions.
2 changes: 1 addition & 1 deletion ouroboros/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION='0.1.2'
VERSION='0.1.3'
30 changes: 13 additions & 17 deletions ouroboros/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@
import defaults

host = ''
interval = ''
monitor = []
loglevel = ''
api_client = None
run_once = None
cleanup = None


def checkURI(uri):
Expand Down Expand Up @@ -53,21 +48,27 @@ def get_interval_env():
return False


def parser(sysargs):
def parse(sysargs):
"""Declare command line options"""
global host, interval, monitor, loglevel, api_client, run_once, cleanup
global host, api_client
parser = argparse.ArgumentParser(description='ouroboros',
epilog='Example: python3 main.py -u tcp://1.2.3.4:5678 -i 20 -m container1 container2 -l warn')
parser.add_argument('-u', '--url', help='Url for tcp host (defaults to "unix://var/run/docker.sock")')
parser.add_argument('-i', '--interval', type=int, default=get_interval_env() or defaults.INTERVAL,

parser.add_argument('-i', '--interval', type=int, default=get_interval_env() or defaults.INTERVAL, dest="interval",
help='Interval in seconds between checking for updates (defaults to 300s)')
parser.add_argument('-m', '--monitor', nargs='+', default=environ.get('MONITOR') or [],

parser.add_argument('-m', '--monitor', nargs='+', default=environ.get('MONITOR') or [], dest="monitor",
help='Which container to monitor (defaults to all running).')

parser.add_argument('-l', '--loglevel', choices=['notset', 'debug', 'info', 'warn', 'error', 'critical'],
default=environ.get('LOGLEVEL') or 'info', help='Change logger mode (defaults to info)')
parser.add_argument('-r', '--runonce', default=environ.get('RUNONCE') or False,
dest="loglevel", default=environ.get('LOGLEVEL') or 'info',
help='Change logger mode (defaults to info)')

parser.add_argument('-r', '--runonce', default=environ.get('RUNONCE') or False, dest="run_once",
help='Only run ouroboros once then exit', action='store_true')
parser.add_argument('-c', '--cleanup', default=environ.get('CLEANUP') or False,

parser.add_argument('-c', '--cleanup', default=environ.get('CLEANUP') or False, dest="cleanup",
help='Remove old images after updating', action='store_true')
args = parser.parse_args(sysargs)

Expand All @@ -76,10 +77,5 @@ def parser(sysargs):
if not checkURI(host):
host = defaults.LOCAL_UNIX_SOCKET

interval = args.interval
monitor = args.monitor
loglevel = args.loglevel
run_once = args.runonce
cleanup = args.cleanup
api_client = docker.APIClient(base_url=host)
return args
34 changes: 24 additions & 10 deletions ouroboros/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

log = logging.getLogger(__name__)


def new_container_properties(old_container, new_image):
"""Store object for spawning new container in place of the one with outdated image"""
props = {
Expand All @@ -16,49 +17,62 @@ def new_container_properties(old_container, new_image):
}
return props


def running():
"""Return running container objects list"""
running_containers = []
try:
for container in cli.api_client.containers(filters={'status': 'running'}):
for container in cli.api_client.containers(
filters={'status': 'running'}):
if 'ouroboros' not in container['Image']:
running_containers.append(cli.api_client.inspect_container(container))
running_containers.append(
cli.api_client.inspect_container(container))
return running_containers
except:
log.critical(f'Can\'t connect to Docker API at {cli.api_client.base_url}')
except BaseException:
log.critical(
f'Can\'t connect to Docker API at {cli.api_client.base_url}')

def to_monitor():

def to_monitor(monitor=None):
"""Return filtered running container objects list"""
running_containers = []
try:
if cli.monitor:
for container in cli.api_client.containers(filters={'name': cli.monitor, 'status': 'running'}):
running_containers.append(cli.api_client.inspect_container(container))
if monitor:
for container in cli.api_client.containers(
filters={'name': monitor, 'status': 'running'}):
running_containers.append(
cli.api_client.inspect_container(container))
else:
running_containers.extend(running())
log.info(f'{len(running_containers)} running container(s) matched filter')
return running_containers
except:
log.critical(f'Can\'t connect to Docker API at {cli.api_client.base_url}')
except BaseException:
log.critical(
f'Can\'t connect to Docker API at {cli.api_client.base_url}')


def get_name(container_object):
"""Parse out first name of container"""
return container_object['Name'].replace('/', '')


def stop(container_object):
"""Stop out of date container"""
log.debug(f'Stopping container: {get_name(container_object)}')
return cli.api_client.stop(container_object)


def remove(container_object):
"""Remove out of date container"""
log.debug(f'Removing container: {get_name(container_object)}')
return cli.api_client.remove_container(container_object)


def create_new(config):
"""Create new container with latest image"""
return cli.api_client.create_container(**config)


def start(container_object):
"""Start newly created container with latest image"""
log.debug(f"Starting container: {container_object['Id']}")
Expand Down
12 changes: 6 additions & 6 deletions ouroboros/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from logger import set_logger


def main():
def main(args):
"""Find running containers and update them with images using latest tag"""
log = logging.getLogger(__name__)
if not container.running():
Expand All @@ -33,18 +33,18 @@ def main():
container.remove(container_object=running_container)
new_container = container.create_new(config=new_config)
container.start(container_object=new_container)
if cli.cleanup:
if args.cleanup:
image.remove(old_image=current_image)
updated_count += 1
log.info(f'{updated_count} container(s) updated')
if cli.run_once:
if args.run_once:
exit(0)


if __name__ == "__main__":
cli.parser(argv[1:])
logging.basicConfig(**set_logger(cli.loglevel))
schedule.every(cli.interval).seconds.do(main)
args = cli.parse(argv[1:])
logging.basicConfig(**set_logger(args.loglevel))
schedule.every(args.interval).seconds.do(main, args=args)

while True:
schedule.run_pending()
Expand Down
115 changes: 67 additions & 48 deletions tests/integration/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,67 +13,86 @@
test_host_port = 1234
test_container_mount_dest = '/tmp'
test_container_props = {
'name': test_container_name,
'image': test_image,
'command': 'tail -f /dev/null',
'detach': True,
'ports': [
'5678'
],
'environment': [
'testEnvVar=testVar'
],
'volumes': [
'/tmp'
]
'name': test_container_name,
'image': test_image,
'command': 'tail -f /dev/null',
'detach': True,
'ports': [
'5678'
],
'environment': [
'testEnvVar=testVar'
],
'volumes': [
'/tmp'
]
}


def test_create_network():
api_client.create_network(test_network)
assert api_client.inspect_network(test_network)['Name'] == test_network
api_client.create_network(test_network)
assert api_client.inspect_network(test_network)['Name'] == test_network


def test_create_container():
api_client.pull(repository=test_repo, tag=test_tag)
api_client.create_container(**test_container_props, networking_config=api_client.create_networking_config({test_network: api_client.create_endpoint_config()}),
host_config=api_client.create_host_config(binds=[f"{test_container_props['volumes'][0]}:{test_container_mount_dest}"],
port_bindings={test_container_props['ports'][0]: test_host_port}))
api_client.start(test_container_name)
running_container = api_client.containers(filters={'name': test_container_name})[0]
assert running_container['State'] == 'running'
assert running_container['Id'] in api_client.inspect_network(test_network)['Containers']
api_client.pull(repository=test_repo, tag=test_tag)
api_client.create_container(**test_container_props, networking_config=api_client.create_networking_config({test_network: api_client.create_endpoint_config()}),
host_config=api_client.create_host_config(binds=[f"{test_container_props['volumes'][0]}:{test_container_mount_dest}"],
port_bindings={test_container_props['ports'][0]: test_host_port}))
api_client.start(test_container_name)
running_container = api_client.containers(
filters={'name': test_container_name})[0]
assert running_container['State'] == 'running'
assert running_container['Id'] in api_client.inspect_network(test_network)[
'Containers']


def test_main(mocker):
mocker.patch('sys.argv', [''])
mocker.patch.dict('os.environ', {'INTERVAL': '6', 'LOGLEVEL': 'debug', 'RUNONCE': 'true', 'CLEANUP': 'true', 'MONITOR': test_container_name})
with pytest.raises(SystemExit):
assert imp.load_source('__main__', 'ouroboros/main.py') == SystemExit
mocker.patch('sys.argv', [''])
mocker.patch.dict('os.environ',
{'INTERVAL': '6',
'LOGLEVEL': 'debug',
'RUNONCE': 'true',
'CLEANUP': 'true',
'MONITOR': test_container_name})
mocker.patch('ouroboros.cli.api_client', api_client)

with pytest.raises(SystemExit):
assert imp.load_source('__main__', 'ouroboros/main.py') == SystemExit


def test_container_updated(mocker):
running_container = api_client.containers(filters={'name': test_container_name})[0]
new_container = api_client.inspect_container(running_container)
host_port = new_container['HostConfig']['PortBindings'][f"{test_container_props['ports'][0]}/tcp"][0]['HostPort']
assert new_container['State']['Status'] == 'running'
assert new_container['Config']['Image'] == f'{test_repo}:latest'
assert new_container['Config']['Cmd'] == test_container_props['command'].split()
assert test_container_props['environment'][0] in new_container['Config']['Env']
assert new_container['Mounts'][0]['Source'] == test_container_props['volumes'][0]
assert new_container['Mounts'][0]['Destination'] == test_container_mount_dest
assert host_port == str(test_host_port)
running_container = api_client.containers(
filters={'name': test_container_name})[0]
new_container = api_client.inspect_container(running_container)
host_port = new_container['HostConfig']['PortBindings'][
f"{test_container_props['ports'][0]}/tcp"][0]['HostPort']
assert new_container['State']['Status'] == 'running'
assert new_container['Config']['Image'] == f'{test_repo}:latest'
assert new_container['Config']['Cmd'] == test_container_props['command'].split(
)
assert test_container_props['environment'][0] in new_container['Config']['Env']
assert new_container['Mounts'][0]['Source'] == test_container_props['volumes'][0]
assert new_container['Mounts'][0]['Destination'] == test_container_mount_dest
assert host_port == str(test_host_port)


def test_rm_updated_container():
running_container = api_client.containers(filters={'name': test_container_name})[0]
api_client.stop(running_container)
api_client.remove_container(running_container)
assert api_client.containers(filters={'name': test_container_name}) == []
running_container = api_client.containers(
filters={'name': test_container_name})[0]
api_client.stop(running_container)
api_client.remove_container(running_container)
assert api_client.containers(filters={'name': test_container_name}) == []


def test_rm_test_images():
images = [test_image, f'{test_repo}:latest']
for image in images:
if api_client.images(image):
api_client.remove_image(image)
assert api_client.images(name=image) == []
images = [test_image, f'{test_repo}:latest']
for image in images:
if api_client.images(image):
api_client.remove_image(image)
assert api_client.images(name=image) == []


def test_rm_network():
api_client.remove_network(test_network)
assert api_client.networks(names=test_network) == []
api_client.remove_network(test_network)
assert api_client.networks(names=test_network) == []
Loading

0 comments on commit b1a0dd9

Please sign in to comment.