From 771f4bb216db0f23b73fed724ae76bcc08f12412 Mon Sep 17 00:00:00 2001 From: gmt2001 Date: Mon, 20 Jul 2020 22:41:44 -0400 Subject: [PATCH] --- .github/workflows/build.yml | 2 +- CHANGELOG.md | 7 +++++ Dockerfile | 4 +++ docker-compose.yml | 1 + pyouroboros/__init__.py | 2 +- pyouroboros/dockerclient.py | 58 +++++++++++++++++++++++++++++++++++-- pyouroboros/helpers.py | 27 +++++++++++++++++ 7 files changed, 96 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7cad6c13..1b5acf63 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: types: [published] env: - OUROBOROS_VERSION: 1.5.1 + OUROBOROS_VERSION: 1.6.0 jobs: build: diff --git a/CHANGELOG.md b/CHANGELOG.md index d783504d..7635251c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [v1.6.0](https://github.com/gmt2001/ouroboros/tree/v1.6.0) (2020-06-11) +[Full Changelog](https://github.com/gmt2001/ouroboros/compare/1.6.0...v1.5.1) + +**Implemented enhancements:** + +- Added hooks system + ## [v1.5.1](https://github.com/gmt2001/ouroboros/tree/v1.5.1) (2020-06-11) [Full Changelog](https://github.com/gmt2001/ouroboros/compare/1.5.1...v1.4.3) diff --git a/Dockerfile b/Dockerfile index 1812a5d3..e0c3319f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,4 +13,8 @@ COPY /pyouroboros /app/pyouroboros RUN pip install --no-cache-dir . +RUN mkdir /app/pyouroboros/hooks + +VOLUME /app/pyouroboros/hooks + ENTRYPOINT ["ouroboros"] diff --git a/docker-compose.yml b/docker-compose.yml index ff436703..da1e1263 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,3 +14,4 @@ services: restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock + - /app/pyouroboros/hooks:/app/pyouroboros/hooks diff --git a/pyouroboros/__init__.py b/pyouroboros/__init__.py index dd252968..59739525 100644 --- a/pyouroboros/__init__.py +++ b/pyouroboros/__init__.py @@ -1,2 +1,2 @@ -VERSION = "1.5.1" +VERSION = "1.6.0" BRANCH = "master" diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 2bfe3a1f..7e56fc56 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -4,7 +4,7 @@ from os.path import isdir, isfile, join from docker.errors import DockerException, APIError, NotFound -from pyouroboros.helpers import set_properties, remove_sha_prefix, get_digest +from pyouroboros.helpers import set_properties, remove_sha_prefix, get_digest, run_hook class Docker(object): @@ -171,6 +171,7 @@ def recreate(self, container, latest_image): self.logger.error('Unable to attach updated container to network "%s". Error: %s', network.name, e) new_container.start() + return new_container def pull(self, current_tag): """Docker pull image tag""" @@ -299,10 +300,18 @@ def update(self): updated_count = 0 try: updateable, depends_on_containers, hard_depends_on_containers = self.socket_check() + locals = {} + locals['updateable'] = updateable + locals['depends_on_containers'] = depends_on_containers + locals['hard_depends_on_containers'] = hard_depends_on_containers + run_hook('updates_enumerated', None, locals) except TypeError: return for container in depends_on_containers + hard_depends_on_containers: + locals = {} + locals['container'] = container + run_hook('before_stop_depends_container', None, locals) self.stop(container) for container, current_image, latest_image in updateable: @@ -310,6 +319,11 @@ def update(self): # Ugly hack for repo digest repo_digest_id = current_image.attrs['RepoDigests'][0].split('@')[1] if repo_digest_id != latest_image.id: + locals = {} + locals['container'] = container + locals['current_image'] = current_image + locals['latest_image'] = latest_image + run_hook('dry_run_update', None, locals) self.logger.info('dry run : %s would be updated', container.name) continue @@ -317,6 +331,11 @@ def update(self): # Ugly hack for repo digest repo_digest_id = current_image.attrs['RepoDigests'][0].split('@')[1] if repo_digest_id != latest_image.id: + locals = {} + locals['container'] = container + locals['current_image'] = current_image + locals['latest_image'] = latest_image + run_hook('notify_update', None, locals) self.notification_manager.send( container_tuples=[(container.name, current_image, latest_image)], socket=self.socket, @@ -334,10 +353,22 @@ def update(self): self.logger.info('%s will be updated', container.name) - self.recreate(container, latest_image) + locals = {} + locals['old_container'] = container + locals['old_image'] = current_image + locals['new_image'] = latest_image + run_hook('before_update', None, locals) + + new_container = self.recreate(container, latest_image) + + locals['new_container'] = new_container + run_hook('after_update', None, locals) if self.config.cleanup: try: + locals = {} + locals['image'] = current_image + run_hook('before_image_cleanup', None, locals) self.client.images.remove(current_image.id) except APIError as e: self.logger.error("Could not delete old image for %s, Error: %s", container.name, e) @@ -351,11 +382,19 @@ def update(self): for container in depends_on_containers: # Reload container to ensure it isn't referencing the old image + locals = {} + locals['container'] = container + run_hook('before_start_depends_container', None, locals) container.reload() container.start() for container in hard_depends_on_containers: - self.recreate(container, container.image) + locals = {} + locals['old_container'] = container + run_hook('before_recreate_hard_depends_container', None, locals) + new_container = self.recreate(container, container.image) + locals['new_container'] = new_container + run_hook('after_recreate_hard_depends_container', None, locals) if updated_count > 0: self.notification_manager.send(container_tuples=updateable, socket=self.socket, kind='update') @@ -367,17 +406,28 @@ def update_self(self, count=None, old_container=None, me_list=None, new_image=No old_me = self.client.containers.get(old_me_id) old_me_image_id = old_me.image.id + locals = {} + locals['old_container'] = old_me + locals['new_container'] = self.client.containers.get(me_list[0].id if me_list[0].attrs['Created'] >= me_list[1].attrs['Created'] else me_list[1].id) + run_hook('before_self_cleanup', None, locals) + old_me.stop() old_me.remove() self.client.images.remove(old_me_image_id) self.logger.debug('Ahhh. All better.') + run_hook('after_self_cleanup', None, locals) self.monitored = self.monitor_filter() elif count == 1: self.logger.debug('I need to update! Starting the ouroboros ;)') self_name = 'ouroboros-updated' if old_container.name == 'ouroboros' else 'ouroboros' new_config = set_properties(old=old_container, new=new_image, self_name=self_name) + locals = {} + locals['self_name'] = self_name + locals['old_container'] = old_container + locals['new_image'] = new_image + run_hook('before_self_update', None, locals) try: me_created = self.client.api.create_container(**new_config) new_me = self.client.containers.get(me_created.get("Id")) @@ -385,6 +435,8 @@ def update_self(self, count=None, old_container=None, me_list=None, new_image=No self.logger.debug('If you strike me down, I shall become ' 'more powerful than you could possibly imagine.') self.logger.debug('https://bit.ly/2VVY7GH') + locals['new_container'] = new_me + run_hook('after_self_update', None, locals) sleep(30) except APIError as e: self.logger.error("Self update failed.") diff --git a/pyouroboros/helpers.py b/pyouroboros/helpers.py index 246afdde..60e282b7 100644 --- a/pyouroboros/helpers.py +++ b/pyouroboros/helpers.py @@ -1,3 +1,30 @@ +from inspect import getframeinfo, currentframe +from os.path import dirname, abspath +from pathlib import Path + +def get_exec_dir(): + filename = getframeinfo(currentframe()).filename + path = dirname(abspath(filename)) + if path.endswith('/'): + path = path[:-1] + return path + +def run_hook(hookname, globals=None, locals=None): + pathlist = Path(get_exec_dir() + '/hooks/' + hookname).rglob('*.py') + for path in pathlist: + execfile(str(path), globals, locals) + +# Copied from https://stackoverflow.com/a/41658338 +def execfile(filepath, globals=None, locals=None): + if globals is None: + globals = {} + globals.update({ + "__file__": filepath, + "__name__": "__main__", + }) + with open(filepath, 'rb') as file: + exec(compile(file.read(), filepath, 'exec'), globals, locals) + def set_properties(old, new, self_name=None): """Store object for spawning new container in place of the one with outdated image""" properties = {