Skip to content

Commit

Permalink
Move api client out of cli.py (#38)
Browse files Browse the repository at this point in the history
* Move the api_client out of cli

* Remove the line that references global api_client

* Remove commented line from main test
  • Loading branch information
tlkamp authored and circa10a committed Oct 27, 2018
1 parent f02dfd0 commit 6b1b1ba
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 54 deletions.
5 changes: 0 additions & 5 deletions ouroboros/cli.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
from os import environ
import argparse
import docker
import re
import defaults

api_client = None


def checkURI(uri):
"""Validate tcp:// regex"""
Expand Down Expand Up @@ -49,7 +46,6 @@ def get_interval_env():

def parse(sysargs):
"""Declare command line options"""
global 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')

Expand Down Expand Up @@ -79,5 +75,4 @@ def parse(sysargs):
if args.url is not defaults.LOCAL_UNIX_SOCKET:
args.url = args.url if checkURI(args.url) else defaults.LOCAL_UNIX_SOCKET

api_client = docker.APIClient(base_url=args.url)
return args
35 changes: 17 additions & 18 deletions ouroboros/container.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import logging
import cli

log = logging.getLogger(__name__)

Expand All @@ -18,62 +17,62 @@ def new_container_properties(old_container, new_image):
return props


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


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


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


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


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


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


def start(container_object):
def start(container_object, api_client):
"""Start newly created container with latest image"""
log.debug(f"Starting container: {container_object['Id']}")
return cli.api_client.start(container_object)
return api_client.start(container_object)
18 changes: 11 additions & 7 deletions ouroboros/image.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
from os import environ
import logging
import cli

log = logging.getLogger(__name__)


def check_credentials():
"""Returns dict of credentials if environment variable 'REPO_USER' and 'REPO_PASS' are set"""
if environ.get('REPO_USER') and environ.get('REPO_PASS'):
return {'username': environ['REPO_USER'], 'password': environ['REPO_PASS']}
return {'username': environ['REPO_USER'],
'password': environ['REPO_PASS']}
return {}

def pull_latest(image):

def pull_latest(image, api_client):
"""Return tag of latest image pulled"""
latest_image = image['RepoTags'][0].split(':')[0] + ':latest'
log.debug(f'Pulling image: {latest_image}')
cli.api_client.pull(latest_image, auth_config=check_credentials())
return cli.api_client.inspect_image(latest_image)
api_client.pull(latest_image, auth_config=check_credentials())
return api_client.inspect_image(latest_image)


def is_up_to_date(old_sha, new_sha):
"""Returns boolean if old and new image digests match"""
return old_sha == new_sha

def remove(old_image):

def remove(old_image, api_client):
"""Deletes old image after container is updated"""
log.info(f"Removing image: {old_image['RepoTags'][0]}")
return cli.api_client.remove_image(old_image)
return api_client.remove_image(old_image)
23 changes: 12 additions & 11 deletions ouroboros/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
from logger import set_logger


def main(args):
def main(args, api_client):
"""Find running containers and update them with images using latest tag"""
log = logging.getLogger(__name__)
if not container.running():
if not container.running(api_client=api_client):
log.info('No containers are running')
else:
updated_count = 0
for running_container in container.to_monitor():
current_image = cli.api_client.inspect_image(running_container['Config']['Image'])
for running_container in container.to_monitor(api_client=api_client):
current_image = api_client.inspect_image(running_container['Config']['Image'])
try:
latest_image = image.pull_latest(image=current_image)
latest_image = image.pull_latest(image=current_image, api_client=api_client)
except docker.errors.APIError as e:
log.error(e)
continue
Expand All @@ -29,12 +29,12 @@ def main(args):
log.info(f'{container.get_name(container_object=running_container)} will be updated')
# new container dict to create new container from
new_config = container.new_container_properties(old_container=running_container, new_image=latest_image['RepoTags'][0])
container.stop(container_object=running_container)
container.remove(container_object=running_container)
new_container = container.create_new(config=new_config)
container.start(container_object=new_container)
container.stop(container_object=running_container, api_client=api_client)
container.remove(container_object=running_container, api_client=api_client)
new_container = container.create_new(config=new_config, api_client=api_client)
container.start(container_object=new_container, api_client=api_client)
if args.cleanup:
image.remove(old_image=current_image)
image.remove(old_image=current_image, api_client=api_client)
updated_count += 1
log.info(f'{updated_count} container(s) updated')
if args.run_once:
Expand All @@ -43,8 +43,9 @@ def main(args):

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

while True:
schedule.run_pending()
Expand Down
2 changes: 0 additions & 2 deletions tests/integration/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ def test_main(mocker):
'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

Expand Down
26 changes: 15 additions & 11 deletions tests/unit/container_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import pytest
import ouroboros.container as container
from container_object import container_object

import docker
import ouroboros.defaults

@pytest.fixture()
def fake_container():
Expand All @@ -25,25 +26,28 @@ def test_get_name(fake_container):


def test_to_monitor(mocker):
mocker.patch.object(container.cli, 'api_client')
container.cli.api_client.containers.return_value = []
mock_client = mocker.Mock(spec=docker.APIClient)
mock_client.base_url = ouroboros.defaults.LOCAL_UNIX_SOCKET
mock_client.containers.return_value = []

result = container.to_monitor(monitor='test')
result = container.to_monitor(monitor='test', api_client=mock_client)
assert result == []
container.cli.api_client.containers.assert_called_once()
mock_client.containers.assert_called_once()


def test_to_monitor_exception(mocker, caplog):
mocker.patch.object(container.cli, 'api_client')
container.cli.api_client.containers.side_effect = BaseException('I blew up!!')
mock_client = mocker.Mock(spec=docker.APIClient)
mock_client.base_url = ouroboros.defaults.LOCAL_UNIX_SOCKET
mock_client.containers.side_effect = BaseException('I blew up!!')

container.to_monitor(monitor='test')
container.to_monitor(monitor='test', api_client=mock_client)
assert 'connect to Docker API' in caplog.text


def test_running_exception(mocker, caplog):
mocker.patch.object(container.cli, 'api_client')
container.cli.api_client.containers.side_effect = BaseException("I'm blasting off again!")
mock_client = mocker.Mock(spec=docker.APIClient)
mock_client.base_url = ouroboros.defaults.LOCAL_UNIX_SOCKET
mock_client.containers.side_effect = BaseException("I'm blasting off again!")

container.running()
container.running(mock_client)
assert 'connect to Docker API' in caplog.text

0 comments on commit 6b1b1ba

Please sign in to comment.