From 8304aec73fdd81ed1b17b9719f62c57694d1f130 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Sat, 2 Feb 2019 16:02:35 -0600 Subject: [PATCH 01/38] fix number for pull --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efcf7c7d..330313ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - cron documentation example update [\#182](https://github.com/pyouroboros/ouroboros/issues/182) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] ***Other Pull Requests** -- v1.1.2 Merge [\#185](https://github.com/pyouroboros/ouroboros/pull/185) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- v1.1.2 Merge [\#186](https://github.com/pyouroboros/ouroboros/pull/186) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) ## [1.1.1](https://github.com/pyouroboros/ouroboros/tree/1.1.1) (2019-02-01) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.1.0...1.1.1) From d9aeee20ecf61f4fb4a1b982784e93e5c8acab8d Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Sun, 3 Feb 2019 15:06:52 -0600 Subject: [PATCH 02/38] initial push for swarm --- pyouroboros/config.py | 6 +- pyouroboros/dockerclient.py | 214 ++++++++++++++++++++++++++++-------- pyouroboros/ouroboros.py | 17 ++- 3 files changed, 185 insertions(+), 52 deletions(-) diff --git a/pyouroboros/config.py b/pyouroboros/config.py index c5df05ea..f46d7921 100644 --- a/pyouroboros/config.py +++ b/pyouroboros/config.py @@ -8,7 +8,7 @@ class Config(object): 'PROMETHEUS_PORT', 'NOTIFIERS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'LATEST', 'CRON', 'INFLUX_URL', 'INFLUX_PORT', 'INFLUX_USERNAME', 'INFLUX_PASSWORD', 'INFLUX_DATABASE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', - 'DRY_RUN', 'HOSTNAME', 'DOCKER_TLS_VERIFY'] + 'DRY_RUN', 'HOSTNAME', 'DOCKER_TLS_VERIFY', 'SWARM'] hostname = environ.get('HOSTNAME') interval = 300 @@ -16,6 +16,7 @@ class Config(object): docker_sockets = 'unix://var/run/docker.sock' docker_tls = False docker_tls_verify = True + swarm = False monitor = [] ignore = [] data_export = None @@ -91,7 +92,8 @@ def parse(self): except ValueError as e: print(e) elif option in ['LATEST', 'CLEANUP', 'RUN_ONCE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DRY_RUN', - 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', 'DOCKER_TLS_VERIFY']: + 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', 'DOCKER_TLS_VERIFY', + 'SWARM']: if env_opt.lower() in ['true', 'yes']: setattr(self, option.lower(), True) elif env_opt.lower() in ['false', 'no']: diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 56ff4900..66b949ec 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -13,9 +13,7 @@ def __init__(self, socket, config, data_manager, notification_manager): self.socket = socket self.client = self.connect() self.data_manager = data_manager - self.data_manager.total_updated[self.socket] = 0 self.logger = getLogger() - self.monitored = self.monitor_filter() self.notification_manager = notification_manager @@ -56,50 +54,19 @@ def connect(self): return client - def get_running(self): - """Return running container objects list, except ouroboros itself""" - running_containers = [] - try: - for container in self.client.containers.list(filters={'status': 'running'}): - if self.config.self_update: - running_containers.append(container) - else: - try: - if 'ouroboros' not in container.image.tags[0]: - running_containers.append(container) - except IndexError: - self.logger.error("%s has no tags.. you should clean it up! Ignoring.", container.id) - continue - - except DockerException: - self.logger.critical("Can't connect to Docker API at %s", self.config.docker_socket) - exit(1) - - return running_containers - - def monitor_filter(self): - """Return filtered running container objects list""" - running_containers = self.get_running() - monitored_containers = [] - - for container in running_containers: - ouro_label = container.labels.get('com.ouroboros.enable', False) - # if labels enabled, use the label. 'true/yes' trigger monitoring. - if self.config.label_enable and ouro_label: - if ouro_label.lower() in ["true", "yes"]: - monitored_containers.append(container) - else: - continue - elif not self.config.labels_only and self.config.monitor and container.name in self.config.monitor \ - and container.name not in self.config.ignore: - monitored_containers.append(container) - elif not self.config.labels_only and container.name not in self.config.ignore: - monitored_containers.append(container) - self.data_manager.monitored_containers[self.socket] = len(monitored_containers) - self.data_manager.set(self.socket) +class Container(object): + def __init__(self, docker_client): + self.docker = docker_client + self.logger = self.docker.logger + self.config = self.docker.config + self.client = self.docker.client + self.socket = self.docker.socket + self.data_manager = self.docker.data_manager + self.data_manager.total_updated[self.socket] = 0 + self.notification_manager = self.docker.notification_manager - return monitored_containers + self.monitored = self.monitor_filter() def pull(self, image_object): """Docker pull image tag/latest""" @@ -149,7 +116,52 @@ def pull(self, image_object): self.logger.critical("Couldn't pull. Skipping. Error: %s", e) raise ConnectionError - def update_containers(self): + def get_running(self): + """Return running container objects list, except ouroboros itself""" + running_containers = [] + try: + for container in self.client.containers.list(filters={'status': 'running'}): + if self.config.self_update: + running_containers.append(container) + else: + try: + if 'ouroboros' not in container.image.tags[0]: + running_containers.append(container) + except IndexError: + self.logger.error("%s has no tags.. you should clean it up! Ignoring.", container.id) + continue + + except DockerException: + self.logger.critical("Can't connect to Docker API at %s", self.config.docker_socket) + exit(1) + + return running_containers + + def monitor_filter(self): + """Return filtered running container objects list""" + running_containers = self.get_running() + monitored_containers = [] + + for container in running_containers: + ouro_label = container.labels.get('com.ouroboros.enable', False) + # if labels enabled, use the label. 'true/yes' trigger monitoring. + if self.config.label_enable and ouro_label: + if ouro_label.lower() in ["true", "yes"]: + monitored_containers.append(container) + else: + continue + elif not self.config.labels_only and self.config.monitor and container.name in self.config.monitor \ + and container.name not in self.config.ignore: + monitored_containers.append(container) + elif not self.config.labels_only and container.name not in self.config.ignore: + monitored_containers.append(container) + + self.data_manager.monitored_containers[self.socket] = len(monitored_containers) + self.data_manager.set(self.socket) + + return monitored_containers + + def update(self): updated_count = 0 updated_container_tuples = [] depends_on_list = [] @@ -279,3 +291,115 @@ 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') sleep(30) + + +class Service(object): + def __init__(self, docker_client): + self.docker = docker_client + self.logger = self.docker.logger + self.config = self.docker.config + self.client = self.docker.client + self.socket = self.docker.socket + self.data_manager = self.docker.data_manager + self.data_manager.total_updated[self.socket] = 0 + self.notification_manager = self.docker.notification_manager + + self.monitored = self.monitor_filter() + + def monitor_filter(self): + """Return filtered service objects list""" + services = self.client.services.list(filters={'label': 'com.ouroboros.enable'}) + + monitored_services = [] + + for service in services: + ouro_label = service.attrs['Spec']['Labels'].get('com.ouroboros.enable') + if ouro_label.lower() in ["true", "yes"]: + monitored_services.append(service) + + self.data_manager.monitored_containers[self.socket] = len(monitored_services) + self.data_manager.set(self.socket) + + return monitored_services + + def pull(self, tag): + """Docker pull image tag/latest""" + self.logger.debug('Checking tag: %s', tag) + try: + if self.config.dry_run: + registry_data = self.client.images.get_registry_data(tag) + return registry_data + else: + if self.config.auth_json: + return_image = self.client.images.pull(tag, auth_config=self.config.auth_json) + else: + return_image = self.client.images.pull(tag) + return return_image + except APIError as e: + if '' in str(e): + self.logger.debug("Docker api issue. Ignoring") + raise ConnectionError + elif 'unauthorized' in str(e): + if self.config.dry_run: + self.logger.error('dry run : Upstream authentication issue while checking %s. See: ' + 'https://github.com/docker/docker-py/issues/2225', tag) + raise ConnectionError + else: + self.logger.critical("Invalid Credentials. Exiting") + exit(1) + elif 'Client.Timeout' in str(e): + self.logger.critical("Couldn't find an image on docker.com for %s. Local Build?", image.tags[0]) + raise ConnectionError + elif ('pull access' or 'TLS handshake') in str(e): + self.logger.critical("Couldn't pull. Skipping. Error: %s", e) + raise ConnectionError + + def update(self): + updated_count = 0 + updated_service_tuples = [] + self.monitored = self.monitor_filter() + + if not self.monitored: + self.logger.info('No services monitored') + + for service in self.monitored: + image_string = service.attrs['Spec']['TaskTemplate']['ContainerSpec']['Image'] + tag = image_string.split('@')[0] + sha256 = image_string.split('@')[1][7:] + + try: + latest_image = self.pull(tag) + except ConnectionError: + continue + + if self.config.dry_run: + # Ugly hack for repo digest + if sha256 != latest_image.id: + self.logger.info('dry run : %s would be updated', service.name) + continue + + if sha256 != latest_image.id: + updated_service_tuples.append( + (service, sha256[-10:], latest_image) + ) + + if 'ouroboros' in service.name: + self.data_manager.total_updated[self.socket] += 1 + self.data_manager.add(label=service.name, socket=self.socket) + self.data_manager.add(label='all', socket=self.socket) + self.notification_manager.send(container_tuples=updated_service_tuples, + socket=self.socket, kind='update') + + self.logger.info('%s will be updated', service.name) + service.update(image=tag) + + updated_count += 1 + + self.logger.debug("Incrementing total service updated count") + + self.data_manager.total_updated[self.socket] += 1 + self.data_manager.add(label=service.name, socket=self.socket) + self.data_manager.add(label='all', socket=self.socket) + + if updated_count > 0: + self.notification_manager.send(container_tuples=updated_service_tuples, socket=self.socket, kind='update') diff --git a/pyouroboros/ouroboros.py b/pyouroboros/ouroboros.py index 58ca7f3a..53439a94 100644 --- a/pyouroboros/ouroboros.py +++ b/pyouroboros/ouroboros.py @@ -8,10 +8,10 @@ from pyouroboros.config import Config from pyouroboros import VERSION, BRANCH -from pyouroboros.dockerclient import Docker from pyouroboros.logger import OuroborosLogger from pyouroboros.dataexporters import DataManager from pyouroboros.notifiers import NotificationManager +from pyouroboros.dockerclient import Docker, Container, Service def main(): @@ -50,6 +50,9 @@ def main(): core_group.add_argument('-u', '--self-update', default=Config.self_update, dest='SELF_UPDATE', action='store_true', help='Let ouroboros update itself') + core_group.add_argument('-S', '--swarm', default=Config.swarm, dest='SWARM', action='store_true', + help='Put ouroboros in swarm mode') + core_group.add_argument('-o', '--run-once', default=Config.run_once, action='store_true', dest='RUN_ONCE', help='Single run') @@ -151,9 +154,13 @@ def main(): for socket in config.docker_sockets: try: docker = Docker(socket, config, data_manager, notification_manager) + if config.swarm: + mode = Service(docker) + else: + mode = Container(docker) if config.cron: scheduler.add_job( - docker.update_containers, + mode.update, name=f'Cron container update for {socket}', trigger='cron', minute=config.cron[0], @@ -164,14 +171,14 @@ def main(): ) else: if config.run_once: - scheduler.add_job(docker.update_containers, name=f'Run Once container update for {socket}') + scheduler.add_job(mode.update, name=f'Run Once container update for {socket}') else: scheduler.add_job( - docker.update_containers, + mode.update, name=f'Initial run interval container update for {socket}' ) scheduler.add_job( - docker.update_containers, + mode.update, name=f'Interval container update for {socket}', trigger='interval', seconds=config.interval ) From 2ceba60013007a98bddbfc7bf41810d9bf74ce50 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 6 Feb 2019 00:02:26 -0600 Subject: [PATCH 03/38] version + branch bump --- pyouroboros/__init__.py | 4 ++-- pyouroboros/dockerclient.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyouroboros/__init__.py b/pyouroboros/__init__.py index aaee78f6..5972b921 100644 --- a/pyouroboros/__init__.py +++ b/pyouroboros/__init__.py @@ -1,2 +1,2 @@ -VERSION = "1.1.2" -BRANCH = "master" +VERSION = "1.2.0" +BRANCH = "develop" diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 66b949ec..9109e9e9 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -348,7 +348,7 @@ def pull(self, tag): self.logger.critical("Invalid Credentials. Exiting") exit(1) elif 'Client.Timeout' in str(e): - self.logger.critical("Couldn't find an image on docker.com for %s. Local Build?", image.tags[0]) + self.logger.critical("Couldn't find an image on docker.com for %s. Local Build?", tag) raise ConnectionError elif ('pull access' or 'TLS handshake') in str(e): self.logger.critical("Couldn't pull. Skipping. Error: %s", e) From 3538391c8bc06b1661d53f93c40108a494cc390b Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 6 Feb 2019 00:17:04 -0600 Subject: [PATCH 04/38] Jenkins Deployment --- .travis.yml | 31 ------------ Jenkinsfile | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++ deploy.sh | 79 ----------------------------- prebuild.sh | 3 -- 4 files changed, 140 insertions(+), 113 deletions(-) delete mode 100644 .travis.yml create mode 100644 Jenkinsfile delete mode 100755 deploy.sh delete mode 100644 prebuild.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6539e5cf..00000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -sudo: true -dist: xenial -language: python -python: - - 3.7.2 -services: - - docker -addons: - apt: - packages: - - docker-ce - -script: - - pip install flake8 && flake8 --max-line-length 120 *.py pyouroboros/ - - python3 ./ouroboros -l debug --run-once - -deploy: - - provider: pypi - user: pyouroboros - password: $PYPI_PASSWORD - skip_existing: true - on: - branch: master - - provider: script - script: bash deploy.sh - on: - branch: master - - provider: script - script: bash deploy.sh - on: - branch: develop \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..382d74fe --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,140 @@ +pipeline { + agent none + environment { + DOCKER_REPO = "pyouroboros/ouroboros" + GIT_REPO = 'pyouroboros/ouroboros' + VERSION_FILE = "pyouroboros/__init__.py" + FLAKE_FILES = "ouroboros *.py pyouroboros/*.py" + TAG = "" + GIT_TOKEN = credentials('github-jenkins-token') + PYPI_CREDS = credentials('pypi-creds-dirtycajunrice') + } + stages { + stage('Flake8 + Run Once') { + agent { label 'amd64'} + steps { + sh """ + python3 -m venv venv && venv/bin/pip install flake8 && venv/bin/python -m flake8 --max-line-length 120 ${FLAKE_FILES} + venv/bin/python -m pip install -r requirements.txt && venv/bin/python ouroboros --log-level debug --run-once + rm -rf venv/ + """ + script { + TAG = sh(returnStdout: true, script: 'grep -i version ${VERSION_FILE} | cut -d" " -f3 | tr -d \\"').trim() + } + } + } + stage('Docker Builds') { + parallel { + stage('amd64') { + when { + anyOf { + branch 'master' + branch 'develop' + } + } + agent { label 'amd64'} + steps { + script { + if (BRANCH_NAME == 'master') { + def image = docker.build("${DOCKER_REPO}:${TAG}-amd64") + image.push() + + } else if (BRANCH_NAME == 'develop') { + def image = docker.build("${DOCKER_REPO}:develop-amd64") + image.push() + } + } + } + } + stage('ARMv6') { + when { + anyOf { + branch 'master' + branch 'develop' + } + } + agent { label 'arm64'} + steps { + script { + if (BRANCH_NAME == 'master') { + def image = docker.build("${DOCKER_REPO}:${TAG}-arm", "-f Dockerfile.arm .") + image.push() + } else if (BRANCH_NAME == 'develop') { + def image = docker.build("${DOCKER_REPO}:develop-arm", "-f Dockerfile.arm .") + image.push() + } + } + } + } + stage('ARM64v8') { + when { + anyOf { + branch 'master' + branch 'develop' + } + } + agent { label 'arm64'} + steps { + script { + if (BRANCH_NAME == 'master') { + def image = docker.build("${DOCKER_REPO}:${TAG}-arm64", "-f Dockerfile.arm64 .") + image.push() + } else if (BRANCH_NAME == 'develop') { + def image = docker.build("${DOCKER_REPO}:develop-arm64", "-f Dockerfile.arm64 .") + image.push() + } + } + } + } + } + } + stage('Docker Manifest Build') { + when { + anyOf { + branch 'master' + branch 'develop' + } + } + agent { label 'amd64'} + steps { + script { + if (BRANCH_NAME == 'master') { + sh(script: "docker manifest create ${DOCKER_REPO}:${TAG} ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm") + sh(script: "docker manifest inspect ${DOCKER_REPO}:${TAG}") + sh(script: "docker manifest push -p ${DOCKER_REPO}:${TAG}") + sh(script: "docker manifest create ${DOCKER_REPO}:latest ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm") + sh(script: "docker manifest inspect ${DOCKER_REPO}:latest") + sh(script: "docker manifest push -p ${DOCKER_REPO}:latest") + } else if (BRANCH_NAME == 'develop') { + sh(script: "docker manifest create ${DOCKER_REPO}:develop ${DOCKER_REPO}:develop-amd64 ${DOCKER_REPO}:develop-arm64 ${DOCKER_REPO}:develop-arm") + sh(script: "docker manifest inspect ${DOCKER_REPO}:develop") + sh(script: "docker manifest push -p ${DOCKER_REPO}:develop") + } + } + } + } + stage('GitHub Release') { + when { branch 'master' } + agent { label 'amd64'} + steps { + sh """ + git remote set-url origin "https://${GIT_TOKEN_USR}:${GIT_TOKEN_PSW}@github.com/${GIT_REPO}.git" + git tag ${TAG} + git push --tags + """ + } + } + stage('PyPi Release') { + when { branch 'master' } + agent { label 'amd64'} + steps { + sh """ + python3 -m venv venv && venv/bin/pip install twine + venv/bin/python setup.py sdist && venv/bin/python -m twine --skip-existing -u ${PYPI_CREDS_USR} -p ${PYPI_CREDS_PSW} upload dist/* + git tag ${TAG} + git push --tags + """ + } + } + } +} diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index 0f4c7340..00000000 --- a/deploy.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env bash -# Travis-ci convenience environment vars used: -# TRAVIS_BRANCH | branch name -# $TRAVIS_REPO_SLUG | organization/project (GitHub Capitalization) -# Travis-ci manual environment vars used: -# GITHUB_USER | github username -# GITHUB_TOKEN | $GITHUB_USER's token -# DOCKER_USER | docker username -# DOCKER_PASSWORD | $DOCKER_USER's password - -VERSION="$(grep -i version pyouroboros/__init__.py | cut -d' ' -f3 | tr -d \")" - -# Set branch to latest if master, else keep the same -if [[ "$TRAVIS_BRANCH" == "master" ]]; then - BRANCH="latest" -else - BRANCH="$TRAVIS_BRANCH" -fi - -# get the docker lowercase variant of the repo_name -REPOSITORY="$(echo $TRAVIS_REPO_SLUG | tr '[:upper:]' '[:lower:]')" - -# Docker experimental config -echo '{"experimental":true}' | sudo tee /etc/docker/daemon.json -[[ -d ~/.docker ]] || mkdir ~/.docker -[[ -f ~/.docker/config.json ]] || touch ~/.docker/config.json -echo '{"experimental":"enabled"}' | sudo tee ~/.docker/config.json -sudo service docker restart - -# Auth -echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USER" --password-stdin - -# Prepare QEMU for ARM builds -docker run --rm --privileged multiarch/qemu-user-static:register --reset -bash prebuild.sh -chmod +x qemu-aarch64-static qemu-arm-static - -# Set tag based off of branch -if [[ "$BRANCH" == "latest" ]]; then - TAG="$VERSION" -else - TAG="$BRANCH" -fi - -# AMDx64 -docker build -t "${REPOSITORY}:${TAG}-amd64" . && \ -docker push "${REPOSITORY}:${TAG}-amd64" - -# Create Initial Manifests -docker manifest create "${REPOSITORY}:${TAG}" "${REPOSITORY}:${TAG}-amd64" -if [[ "$BRANCH" == "latest" ]]; then - docker manifest create "${REPOSITORY}:${BRANCH}" "${REPOSITORY}:${TAG}-amd64" -fi - -# ARM variants -for i in $(ls *arm*); do - ARCH="$(echo ${i} | cut -d. -f2)" - docker build -f "Dockerfile.${ARCH}" -t "${REPOSITORY}:${TAG}-${ARCH}" . && \ - docker push "${REPOSITORY}:${TAG}-${ARCH}" - # Add variant to manifest - docker manifest create -a "${REPOSITORY}:${TAG}" "${REPOSITORY}:${TAG}-${ARCH}" - if [[ "$BRANCH" == "latest" ]]; then - docker manifest create -a "${REPOSITORY}:${BRANCH}" "${REPOSITORY}:${TAG}-${ARCH}" - fi -done - -docker manifest inspect "${REPOSITORY}:${TAG}" && \ -docker manifest push "${REPOSITORY}:${TAG}" -if [[ "$BRANCH" == "latest" ]]; then - docker manifest inspect "${REPOSITORY}:${BRANCH}" && \ - docker manifest push "${REPOSITORY}:${BRANCH}" -fi - -# Git tags -if [[ "$BRANCH" == "latest" ]]; then - git remote set-url origin "https://${GITHUB_USER}:${GITHUB_TOKEN}@github.com/${REPOSITORY}.git" && \ - git tag "${VERSION}" && \ - git push --tags -fi \ No newline at end of file diff --git a/prebuild.sh b/prebuild.sh deleted file mode 100644 index 8d12fd72..00000000 --- a/prebuild.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -wget -q "https://github.com/multiarch/qemu-user-static/releases/download/v3.1.0-2/qemu-aarch64-static" -wget -q "https://github.com/multiarch/qemu-user-static/releases/download/v3.1.0-2/qemu-arm-static" \ No newline at end of file From 71ab27ae920994e793406a1c4b0312ce084338c9 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 6 Feb 2019 00:24:50 -0600 Subject: [PATCH 05/38] parallel releases --- Jenkinsfile | 90 ++++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 382d74fe..317218d5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -88,52 +88,56 @@ pipeline { } } } - stage('Docker Manifest Build') { - when { - anyOf { - branch 'master' - branch 'develop' + stage('Releases') { + parallel { + stage('Docker Manifest') { + when { + anyOf { + branch 'master' + branch 'develop' + } + } + agent { label 'amd64'} + steps { + script { + if (BRANCH_NAME == 'master') { + sh(script: "docker manifest create ${DOCKER_REPO}:${TAG} ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm") + sh(script: "docker manifest inspect ${DOCKER_REPO}:${TAG}") + sh(script: "docker manifest push -p ${DOCKER_REPO}:${TAG}") + sh(script: "docker manifest create ${DOCKER_REPO}:latest ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm") + sh(script: "docker manifest inspect ${DOCKER_REPO}:latest") + sh(script: "docker manifest push -p ${DOCKER_REPO}:latest") + } else if (BRANCH_NAME == 'develop') { + sh(script: "docker manifest create ${DOCKER_REPO}:develop ${DOCKER_REPO}:develop-amd64 ${DOCKER_REPO}:develop-arm64 ${DOCKER_REPO}:develop-arm") + sh(script: "docker manifest inspect ${DOCKER_REPO}:develop") + sh(script: "docker manifest push -p ${DOCKER_REPO}:develop") + } + } + } } - } - agent { label 'amd64'} - steps { - script { - if (BRANCH_NAME == 'master') { - sh(script: "docker manifest create ${DOCKER_REPO}:${TAG} ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm") - sh(script: "docker manifest inspect ${DOCKER_REPO}:${TAG}") - sh(script: "docker manifest push -p ${DOCKER_REPO}:${TAG}") - sh(script: "docker manifest create ${DOCKER_REPO}:latest ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm") - sh(script: "docker manifest inspect ${DOCKER_REPO}:latest") - sh(script: "docker manifest push -p ${DOCKER_REPO}:latest") - } else if (BRANCH_NAME == 'develop') { - sh(script: "docker manifest create ${DOCKER_REPO}:develop ${DOCKER_REPO}:develop-amd64 ${DOCKER_REPO}:develop-arm64 ${DOCKER_REPO}:develop-arm") - sh(script: "docker manifest inspect ${DOCKER_REPO}:develop") - sh(script: "docker manifest push -p ${DOCKER_REPO}:develop") + stage('GitHub') { + when { branch 'master' } + agent { label 'amd64'} + steps { + sh """ + git remote set-url origin "https://${GIT_TOKEN_USR}:${GIT_TOKEN_PSW}@github.com/${GIT_REPO}.git" + git tag ${TAG} + git push --tags + """ + } + } + stage('PyPi') { + when { branch 'master' } + agent { label 'amd64'} + steps { + sh """ + python3 -m venv venv && venv/bin/pip install twine + venv/bin/python setup.py sdist && venv/bin/python -m twine --skip-existing -u ${PYPI_CREDS_USR} -p ${PYPI_CREDS_PSW} upload dist/* + git tag ${TAG} + git push --tags + """ } } - } - } - stage('GitHub Release') { - when { branch 'master' } - agent { label 'amd64'} - steps { - sh """ - git remote set-url origin "https://${GIT_TOKEN_USR}:${GIT_TOKEN_PSW}@github.com/${GIT_REPO}.git" - git tag ${TAG} - git push --tags - """ - } - } - stage('PyPi Release') { - when { branch 'master' } - agent { label 'amd64'} - steps { - sh """ - python3 -m venv venv && venv/bin/pip install twine - venv/bin/python setup.py sdist && venv/bin/python -m twine --skip-existing -u ${PYPI_CREDS_USR} -p ${PYPI_CREDS_PSW} upload dist/* - git tag ${TAG} - git push --tags - """ } } } From 9f20bdc21996537325b8b0af42b4ab4a967578fd Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 6 Feb 2019 20:34:44 -0600 Subject: [PATCH 06/38] re-addresses #171 --- pyouroboros/dockerclient.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 9109e9e9..5bf54492 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -150,11 +150,12 @@ def monitor_filter(self): monitored_containers.append(container) else: continue - elif not self.config.labels_only and self.config.monitor and container.name in self.config.monitor \ - and container.name not in self.config.ignore: - monitored_containers.append(container) - elif not self.config.labels_only and container.name not in self.config.ignore: - monitored_containers.append(container) + elif not self.config.labels_only: + if self.config.monitor: + if container.name in self.config.monitor and container.name not in self.config.ignore: + monitored_containers.append(container) + elif container.name not in self.config.ignore: + monitored_containers.append(container) self.data_manager.monitored_containers[self.socket] = len(monitored_containers) self.data_manager.set(self.socket) From 40045bbf29c781a1da736165784512702f31b7f1 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 6 Feb 2019 20:35:57 -0600 Subject: [PATCH 07/38] clean up finals for jenkins --- Dockerfile.arm | 2 -- Dockerfile.arm64 | 2 -- 2 files changed, 4 deletions(-) diff --git a/Dockerfile.arm b/Dockerfile.arm index d242cdb2..2d3e876c 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -6,8 +6,6 @@ ENV TZ UTC WORKDIR /app -COPY /qemu-arm-static /usr/bin/qemu-arm-static - COPY /requirements.txt /setup.py /ouroboros /README.md /app/ COPY /pyouroboros /app/pyouroboros diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 4ff6acbd..6b99bf36 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -6,8 +6,6 @@ ENV TZ UTC WORKDIR /app -COPY /qemu-aarch64-static /usr/bin/qemu-aarch64-static - COPY /requirements.txt /setup.py /ouroboros /README.md /app/ COPY /pyouroboros /app/pyouroboros From fdd8a35a145564c6abc6280adce092e62739037f Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 6 Feb 2019 20:44:24 -0600 Subject: [PATCH 08/38] add jenkins tag over travis --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b04c69ce..f30f4ef2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Discord](https://img.shields.io/discord/532695326117593112.svg?colorB=7289DA&label=Discord&logo=Discord&logoColor=7289DA&style=flat-square)](https://discord.gg/qHNByUW) [![BuyUsCoffee](https://img.shields.io/badge/BuyMeACoffee-Donate-ff813f.svg?logo=CoffeeScript&style=flat-square)](https://buymeacoff.ee/ouroboros) -[![Travis](https://img.shields.io/travis/pyouroboros/ouroboros/master.svg?style=flat-square)](https://travis-ci.org/pyouroboros/ouroboros) +[![Build Status](https://jenkins.cajun.pro/buildStatus/icon?job=Ouroboros/master)](https://jenkins.cajun.pro/job/Ouroboros/job/master/) [![Release](https://img.shields.io/github/release/pyouroboros/ouroboros.svg?style=flat-square)](https://hub.docker.com/r/pyouroboros/ouroboros/) [![Pypi Downloads](https://img.shields.io/pypi/dm/ouroboros-cli.svg?style=flat-square)](https://pypi.org/project/ouroboros-cli/) [![Python Version](https://img.shields.io/pypi/pyversions/ouroboros-cli.svg?style=flat-square)](https://pypi.org/project/ouroboros-cli/) From 433bc10f3c7e48026d1d829855242c8daf99f547 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 6 Feb 2019 21:09:53 -0600 Subject: [PATCH 09/38] jenkins cleanup --- Jenkinsfile | 59 +++++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 317218d5..024fc18b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,14 +24,14 @@ pipeline { } } stage('Docker Builds') { + when { + anyOf { + branch 'master' + branch 'develop' + } + } parallel { stage('amd64') { - when { - anyOf { - branch 'master' - branch 'develop' - } - } agent { label 'amd64'} steps { script { @@ -47,12 +47,6 @@ pipeline { } } stage('ARMv6') { - when { - anyOf { - branch 'master' - branch 'develop' - } - } agent { label 'arm64'} steps { script { @@ -67,12 +61,6 @@ pipeline { } } stage('ARM64v8') { - when { - anyOf { - branch 'master' - branch 'develop' - } - } agent { label 'arm64'} steps { script { @@ -89,28 +77,33 @@ pipeline { } } stage('Releases') { + when { + anyOf { + branch 'master' + branch 'develop' + } + } parallel { stage('Docker Manifest') { - when { - anyOf { - branch 'master' - branch 'develop' - } - } agent { label 'amd64'} steps { script { if (BRANCH_NAME == 'master') { - sh(script: "docker manifest create ${DOCKER_REPO}:${TAG} ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm") - sh(script: "docker manifest inspect ${DOCKER_REPO}:${TAG}") - sh(script: "docker manifest push -p ${DOCKER_REPO}:${TAG}") - sh(script: "docker manifest create ${DOCKER_REPO}:latest ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm") - sh(script: "docker manifest inspect ${DOCKER_REPO}:latest") - sh(script: "docker manifest push -p ${DOCKER_REPO}:latest") + sh(script: """ + docker manifest create ${DOCKER_REPO}:${TAG} ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm + docker manifest inspect ${DOCKER_REPO}:${TAG} + docker manifest push -p ${DOCKER_REPO}:${TAG} + docker manifest create ${DOCKER_REPO}:latest ${DOCKER_REPO}:${TAG}-amd64 ${DOCKER_REPO}:${TAG}-arm64 ${DOCKER_REPO}:${TAG}-arm + docker manifest inspect ${DOCKER_REPO}:latest + docker manifest push -p ${DOCKER_REPO}:latest + """ + ) } else if (BRANCH_NAME == 'develop') { - sh(script: "docker manifest create ${DOCKER_REPO}:develop ${DOCKER_REPO}:develop-amd64 ${DOCKER_REPO}:develop-arm64 ${DOCKER_REPO}:develop-arm") - sh(script: "docker manifest inspect ${DOCKER_REPO}:develop") - sh(script: "docker manifest push -p ${DOCKER_REPO}:develop") + sh(script: """ + docker manifest create ${DOCKER_REPO}:develop ${DOCKER_REPO}:develop-amd64 ${DOCKER_REPO}:develop-arm64 ${DOCKER_REPO}:develop-arm + docker manifest inspect ${DOCKER_REPO}:develop + docker manifest push -p ${DOCKER_REPO}:develop + """ } } } From d0b31c464d19fac825332cfab6ee12042f5b263c Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 6 Feb 2019 21:11:09 -0600 Subject: [PATCH 10/38] missing bracket --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index 024fc18b..bd385e0f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -104,6 +104,7 @@ pipeline { docker manifest inspect ${DOCKER_REPO}:develop docker manifest push -p ${DOCKER_REPO}:develop """ + ) } } } From 01d3209cbaf320ee6d709e453bacc8e45b854bd2 Mon Sep 17 00:00:00 2001 From: julian Date: Sat, 9 Feb 2019 16:29:10 +0100 Subject: [PATCH 11/38] carry over network config Signed-off-by: julian --- pyouroboros/dockerclient.py | 33 +++++++++++++++++++++++++++++++++ pyouroboros/helpers.py | 4 +++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 5bf54492..a57bda9c 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -237,6 +237,39 @@ def update(self): created = self.client.api.create_container(**new_config) new_container = self.client.containers.get(created.get("Id")) + + # disconnect new container from old networks (with possible + # broken config) + for network in new_container.attrs['NetworkSettings']['Networks']: + self.client.api.disconnect_container_from_network( + container=new_container.attrs['Id'], + net_id=network, + force=True + ) + + # connect the new container to all networks of the old container + for network in container.attrs['NetworkSettings']['Networks']: + host_net = self.client.networks.get(network_id=network) + if host_net.attrs.get('Options', {}).get('com.docker.network.bridge.default_bridge') == 'true': + # default network + # User specified IP address is supported on user defined networks only + self.client.api.connect_container_to_network( + container=new_container.attrs['Id'], + net_id=network, + aliases=container.attrs['NetworkSettings']['Networks'][network]['Aliases'], + links=container.attrs['NetworkSettings']['Networks'][network]['Links'] + ) + else: + # user defined network + self.client.api.connect_container_to_network( + container=new_container.attrs['Id'], + net_id=network, + aliases=container.attrs['NetworkSettings']['Networks'][network]['Aliases'], + links=container.attrs['NetworkSettings']['Networks'][network]['Links'], + ipv4_address=container.attrs['NetworkSettings']['Networks'][network]['IPAddress'], + ipv6_address=container.attrs['NetworkSettings']['Networks'][network]['GlobalIPv6Address'] + ) + new_container.start() if self.config.cleanup: diff --git a/pyouroboros/helpers.py b/pyouroboros/helpers.py index 252e6083..6f2634a6 100644 --- a/pyouroboros/helpers.py +++ b/pyouroboros/helpers.py @@ -18,7 +18,9 @@ def set_properties(old, new, self_name=None): 'host_config': old.attrs['HostConfig'], 'labels': old.attrs['Config']['Labels'], 'entrypoint': old.attrs['Config']['Entrypoint'], - 'environment': old.attrs['Config']['Env'] + 'environment': old.attrs['Config']['Env'], + # networks are conigured later + 'networking_config': None } return properties From 5930f4aa2e23a766e5b9a68cef951fdf07c5a3f2 Mon Sep 17 00:00:00 2001 From: Caleb Lemoine <21261388+circa10a@users.noreply.github.com> Date: Sun, 10 Feb 2019 13:37:34 -0600 Subject: [PATCH 12/38] Adjust apscheduler logger (#199) * modify default apscheduler log level * add back new line * fix flake8 * safer level check * binary check rather than loop * search is 4x than comparing .upper(), who knew * less error prone is .upper * remove excess import * use getEffectiveLevel() --- pyouroboros/logger.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyouroboros/logger.py b/pyouroboros/logger.py index 9d0c4ff3..abcdfaab 100644 --- a/pyouroboros/logger.py +++ b/pyouroboros/logger.py @@ -47,3 +47,7 @@ def __init__(self, level='INFO'): # Add the Handler to the Logger self.logger.addHandler(console_logger) + + # Less verbose apscheduler logging if info + if self.logger.getEffectiveLevel() == 20: + getLogger('apscheduler').setLevel('WARNING') From f5346c9ef6b3da0fc33c7a9795b69445b18de1dd Mon Sep 17 00:00:00 2001 From: julian Date: Sun, 10 Feb 2019 13:16:22 +0100 Subject: [PATCH 13/38] fix bug in user defined network detection Unfortunately there is no sane option to check whether a docker network is user defined, but we need that information in order to decide whether we can carry over the ip addresses. This commit adds a try/except block that tries to set the ip addresses on the new container, and falls back to not setting them when an exception occurs. Signed-off-by: julian --- pyouroboros/dockerclient.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index a57bda9c..d9f00874 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -249,18 +249,8 @@ def update(self): # connect the new container to all networks of the old container for network in container.attrs['NetworkSettings']['Networks']: - host_net = self.client.networks.get(network_id=network) - if host_net.attrs.get('Options', {}).get('com.docker.network.bridge.default_bridge') == 'true': - # default network - # User specified IP address is supported on user defined networks only - self.client.api.connect_container_to_network( - container=new_container.attrs['Id'], - net_id=network, - aliases=container.attrs['NetworkSettings']['Networks'][network]['Aliases'], - links=container.attrs['NetworkSettings']['Networks'][network]['Links'] - ) - else: - # user defined network + try: + # assuming the network has user configured subnet self.client.api.connect_container_to_network( container=new_container.attrs['Id'], net_id=network, @@ -269,6 +259,24 @@ def update(self): ipv4_address=container.attrs['NetworkSettings']['Networks'][network]['IPAddress'], ipv6_address=container.attrs['NetworkSettings']['Networks'][network]['GlobalIPv6Address'] ) + except APIError as e: + if ('user specified IP address is supported only when ' + 'connecting to networks with user configured subnets' in str(e)): + # configure the network without ip addresses + try: + self.client.api.connect_container_to_network( + container=new_container.attrs['Id'], + net_id=network, + aliases=container.attrs['NetworkSettings']['Networks'][network]['Aliases'], + links=container.attrs['NetworkSettings']['Networks'][network]['Links'] + ) + except APIError as e: + self.logger.error( + 'Unable to attach updated container to network "%s". Error: %s', network, e + ) + else: + # another exception occured + raise new_container.start() From d1cbc368212cc264a0fccc4ad4faaeb857bfe03a Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Mon, 11 Feb 2019 18:23:14 -0600 Subject: [PATCH 14/38] account for sha instead of image. Fixes #178 --- pyouroboros/dockerclient.py | 2 +- pyouroboros/notifiers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index a57bda9c..7463b57b 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -422,7 +422,7 @@ def update(self): self.data_manager.add(label=service.name, socket=self.socket) self.data_manager.add(label='all', socket=self.socket) self.notification_manager.send(container_tuples=updated_service_tuples, - socket=self.socket, kind='update') + socket=self.socket, kind='update', mode='service') self.logger.info('%s will be updated', service.name) service.update(image=tag) diff --git a/pyouroboros/notifiers.py b/pyouroboros/notifiers.py index bc2a3e35..c93a4ac7 100644 --- a/pyouroboros/notifiers.py +++ b/pyouroboros/notifiers.py @@ -32,7 +32,7 @@ def build_apprise(self): return apprise_obj - def send(self, container_tuples=None, socket=None, kind='update', next_run=None): + def send(self, container_tuples=None, socket=None, kind='update', next_run=None, mode='container'): if kind == 'startup': now = datetime.now(timezone.utc).astimezone() title = f'Ouroboros has started' @@ -52,7 +52,7 @@ def send(self, container_tuples=None, socket=None, kind='update', next_run=None) [ "{} updated from {} to {}".format( container.name, - old_image.short_id.split(':')[1], + old_image if mode == 'service' else old_image.short_id.split(':')[1], new_image.short_id.split(':')[1] ) for container, old_image, new_image in container_tuples ] From 0f5c40c3f2f97d6a3c1addf25efe747311275c37 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Mon, 11 Feb 2019 19:08:44 -0600 Subject: [PATCH 15/38] rework #193 #196 #200 #195 --- pyouroboros/dockerclient.py | 52 ++++++++++++------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 2b785904..cfa48ef8 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -238,45 +238,27 @@ def update(self): created = self.client.api.create_container(**new_config) new_container = self.client.containers.get(created.get("Id")) - # disconnect new container from old networks (with possible - # broken config) - for network in new_container.attrs['NetworkSettings']['Networks']: - self.client.api.disconnect_container_from_network( - container=new_container.attrs['Id'], - net_id=network, - force=True - ) + # disconnect new container from old networks (with possible broken config) + for network_config in new_container.attrs['NetworkSettings']['Networks']: + network = self.client.networks.get(network_config['NetworkID']) + network.disconnect(new_container.id, force=True) # connect the new container to all networks of the old container - for network in container.attrs['NetworkSettings']['Networks']: + for network_config in container.attrs['NetworkSettings']['Networks']: + network = self.client.networks.get(network_config['NetworkID']) + new_network_config = { + 'container': container, + 'aliases': network_config['Aliases'], + 'links': network_config['Links'] + } + if network_config['Gateway']: + network_config.update({'ipv4_address': network_config['IPAddress']}) + if network_config['IPv6Gateway']: + network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) try: - # assuming the network has user configured subnet - self.client.api.connect_container_to_network( - container=new_container.attrs['Id'], - net_id=network, - aliases=container.attrs['NetworkSettings']['Networks'][network]['Aliases'], - links=container.attrs['NetworkSettings']['Networks'][network]['Links'], - ipv4_address=container.attrs['NetworkSettings']['Networks'][network]['IPAddress'], - ipv6_address=container.attrs['NetworkSettings']['Networks'][network]['GlobalIPv6Address'] - ) + network.connect(**new_network_config) except APIError as e: - if ('user specified IP address is supported only when ' - 'connecting to networks with user configured subnets' in str(e)): - # configure the network without ip addresses - try: - self.client.api.connect_container_to_network( - container=new_container.attrs['Id'], - net_id=network, - aliases=container.attrs['NetworkSettings']['Networks'][network]['Aliases'], - links=container.attrs['NetworkSettings']['Networks'][network]['Links'] - ) - except APIError as e: - self.logger.error( - 'Unable to attach updated container to network "%s". Error: %s', network, e - ) - else: - # another exception occured - raise + self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) new_container.start() From fdce01ffbc9a5439546978dccb7e56ed67363a3d Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Mon, 11 Feb 2019 19:56:28 -0600 Subject: [PATCH 16/38] extend depends_on label. Addresses #198 --- pyouroboros/dockerclient.py | 192 +++++++++++++++++++----------------- 1 file changed, 101 insertions(+), 91 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index cfa48ef8..04c154f9 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -162,14 +162,14 @@ def monitor_filter(self): return monitored_containers - def update(self): - updated_count = 0 - updated_container_tuples = [] - depends_on_list = [] + def check(self): + depends_on_names = [] + updateable = [] self.monitored = self.monitor_filter() if not self.monitored: self.logger.info('No containers are running or monitored on %s', self.socket) + return me_list = [c for c in self.client.api.containers() if 'ouroboros' in c['Names'][0].strip('/')] if len(me_list) > 1: @@ -177,8 +177,7 @@ def update(self): for container in self.monitored: current_image = container.image - - shared_image = [uct for uct in updated_container_tuples if uct[1].id == current_image.id] + shared_image = [uct for uct in updateable if uct[1].id == current_image.id] if shared_image: latest_image = shared_image[0][2] else: @@ -187,6 +186,46 @@ def update(self): except ConnectionError: continue + if current_image.id != latest_image.id: + updateable.append((container, current_image, latest_image)) + else: + continue + + # Get container list to restart after update complete + depends_on = container.labels.get('com.ouroboros.depends_on', False) + if depends_on: + depends_on_names.extend([name.strip() for name in depends_on.split(',')]) + + depends_on_containers = [] + for name in list(set(depends_on_names)): + try: + depends_on_containers.append(self.client.containers.get(name)) + except NotFound: + self.logger.error("Could not find dependant container %s on socket %s. Ignoring", name, self.socket) + + return updateable, depends_on_containers + + def stop_container(self, container): + self.logger.debug('Stopping container: %s', container.name) + stop_signal = container.labels.get('com.ouroboros.stop_signal', False) + if stop_signal: + try: + container.kill(signal=stop_signal) + except APIError as e: + self.logger.error('Cannot kill container using signal %s. stopping normally. Error: %s', + stop_signal, e) + container.stop() + else: + container.stop() + + def update(self): + updated_count = 0 + updateable, depends_on_containers = self.check() + + for container in depends_on_containers: + self.stop_container(container) + + for container, current_image, latest_image in updateable: if self.config.dry_run: # Ugly hack for repo digest repo_digest_id = current_image.attrs['RepoDigests'][0].split('@')[1] @@ -194,102 +233,73 @@ def update(self): self.logger.info('dry run : %s would be updated', container.name) continue - # If current running container is running latest image - if current_image.id != latest_image.id: - updated_container_tuples.append( - (container, current_image, latest_image) - ) + if container.name in ['ouroboros', 'ouroboros-updated']: + self.data_manager.total_updated[self.socket] += 1 + self.data_manager.add(label=container.name, socket=self.socket) + self.data_manager.add(label='all', socket=self.socket) + self.notification_manager.send(container_tuples=updateable, + socket=self.socket, kind='update') + self.update_self(old_container=container, new_image=latest_image, count=1) - if container.name in ['ouroboros', 'ouroboros-updated']: - self.data_manager.total_updated[self.socket] += 1 - self.data_manager.add(label=container.name, socket=self.socket) - self.data_manager.add(label='all', socket=self.socket) - self.notification_manager.send(container_tuples=updated_container_tuples, - socket=self.socket, kind='update') - self.update_self(old_container=container, new_image=latest_image, count=1) - - self.logger.info('%s will be updated', container.name) - - # Get container list to restart after update complete - depends_on = container.labels.get('com.ouroboros.depends-on', False) - if depends_on: - depends_on_list.extend([name.strip() for name in depends_on.split(',')]) - # new container dict to create new container from - new_config = set_properties(old=container, new=latest_image) - - self.logger.debug('Stopping container: %s', container.name) - stop_signal = container.labels.get('com.ouroboros.stop-signal', False) - if stop_signal: - try: - container.kill(signal=stop_signal) - except APIError as e: - self.logger.error('Cannot kill container using signal %s. stopping normally. Error: %s', - stop_signal, e) - container.stop() - else: - container.stop() + self.logger.info('%s will be updated', container.name) - self.logger.debug('Removing container: %s', container.name) - try: - container.remove() - except NotFound as e: - self.logger.error("Could not remove container. Error: %s", e) - - created = self.client.api.create_container(**new_config) - new_container = self.client.containers.get(created.get("Id")) - - # disconnect new container from old networks (with possible broken config) - for network_config in new_container.attrs['NetworkSettings']['Networks']: - network = self.client.networks.get(network_config['NetworkID']) - network.disconnect(new_container.id, force=True) - - # connect the new container to all networks of the old container - for network_config in container.attrs['NetworkSettings']['Networks']: - network = self.client.networks.get(network_config['NetworkID']) - new_network_config = { - 'container': container, - 'aliases': network_config['Aliases'], - 'links': network_config['Links'] - } - if network_config['Gateway']: - network_config.update({'ipv4_address': network_config['IPAddress']}) - if network_config['IPv6Gateway']: - network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) - try: - network.connect(**new_network_config) - except APIError as e: - self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) + # new container dict to create new container from + new_config = set_properties(old=container, new=latest_image) - new_container.start() + self.stop_container(container) - if self.config.cleanup: - try: - 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) - updated_count += 1 + self.logger.debug('Removing container: %s', container.name) + try: + container.remove() + except NotFound as e: + self.logger.error("Could not remove container. Error: %s", e) + continue - self.logger.debug("Incrementing total container updated count") + created = self.client.api.create_container(**new_config) + new_container = self.client.containers.get(created.get("Id")) + + # disconnect new container from old networks (with possible broken config) + for network_config in new_container.attrs['NetworkSettings']['Networks']: + network = self.client.networks.get(network_config['NetworkID']) + network.disconnect(new_container.id, force=True) + + # connect the new container to all networks of the old container + for network_config in container.attrs['NetworkSettings']['Networks']: + network = self.client.networks.get(network_config['NetworkID']) + new_network_config = { + 'container': container, + 'aliases': network_config['Aliases'], + 'links': network_config['Links'] + } + if network_config['Gateway']: + network_config.update({'ipv4_address': network_config['IPAddress']}) + if network_config['IPv6Gateway']: + network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) + try: + network.connect(**new_network_config) + except APIError as e: + self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) - self.data_manager.total_updated[self.socket] += 1 - self.data_manager.add(label=container.name, socket=self.socket) - self.data_manager.add(label='all', socket=self.socket) + new_container.start() - if depends_on_list: - depends_on_containers = [] - for name in list(set(depends_on_list)): + if self.config.cleanup: try: - depends_on_containers.append(self.client.containers.get(name)) - except NotFound: - self.logger.error("Could not find dependant container %s on socket %s. Ignoring", name, self.socket) + 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) + updated_count += 1 + + self.logger.debug("Incrementing total container updated count") + + self.data_manager.total_updated[self.socket] += 1 + self.data_manager.add(label=container.name, socket=self.socket) + self.data_manager.add(label='all', socket=self.socket) - if depends_on_containers: - for container in depends_on_containers: - self.logger.debug('Restarting dependant container %s', container.name) - container.restart() + for container in depends_on_containers: + container.start() if updated_count > 0: - self.notification_manager.send(container_tuples=updated_container_tuples, socket=self.socket, kind='update') + self.notification_manager.send(container_tuples=updateable, socket=self.socket, kind='update') def update_self(self, count=None, old_container=None, me_list=None, new_image=None): if count == 2: From 7c2551298aab0a4ebfe770f8d0fb2ff02fdd05f7 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Mon, 11 Feb 2019 20:08:48 -0600 Subject: [PATCH 17/38] address #197 with hard_depends_on --- pyouroboros/dockerclient.py | 101 +++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 04c154f9..6da59417 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -164,6 +164,7 @@ def monitor_filter(self): def check(self): depends_on_names = [] + hard_depends_on_names = [] updateable = [] self.monitored = self.monitor_filter() @@ -193,17 +194,30 @@ def check(self): # Get container list to restart after update complete depends_on = container.labels.get('com.ouroboros.depends_on', False) + hard_depends_on = container.labels.get('com.ouroboros.hard_depends_on', False) if depends_on: depends_on_names.extend([name.strip() for name in depends_on.split(',')]) + if hard_depends_on: + hard_depends_on_names.extend([name.strip() for name in hard_depends_on.split(',')]) + + hard_depends_on_containers = [] + hard_depends_on_names = list(set(hard_depends_on_names)) + for name in hard_depends_on_names: + try: + hard_depends_on_containers.append(self.client.containers.get(name)) + except NotFound: + self.logger.error("Could not find dependant container %s on socket %s. Ignoring", name, self.socket) depends_on_containers = [] - for name in list(set(depends_on_names)): + depends_on_names = list(set(depends_on_names)) + depends_on_names = [name for name in depends_on_names if name not in hard_depends_on_names] + for name in depends_on_names: try: depends_on_containers.append(self.client.containers.get(name)) except NotFound: self.logger.error("Could not find dependant container %s on socket %s. Ignoring", name, self.socket) - return updateable, depends_on_containers + return updateable, depends_on_containers, hard_depends_on_containers def stop_container(self, container): self.logger.debug('Stopping container: %s', container.name) @@ -218,9 +232,48 @@ def stop_container(self, container): else: container.stop() + def recreate(self, container, latest_image): + new_config = set_properties(old=container, new=latest_image) + + self.stop_container(container) + + self.logger.debug('Removing container: %s', container.name) + try: + container.remove() + except NotFound as e: + self.logger.error("Could not remove container. Error: %s", e) + return + + created = self.client.api.create_container(**new_config) + new_container = self.client.containers.get(created.get("Id")) + + # disconnect new container from old networks (with possible broken config) + for network_config in new_container.attrs['NetworkSettings']['Networks']: + network = self.client.networks.get(network_config['NetworkID']) + network.disconnect(new_container.id, force=True) + + # connect the new container to all networks of the old container + for network_config in container.attrs['NetworkSettings']['Networks']: + network = self.client.networks.get(network_config['NetworkID']) + new_network_config = { + 'container': container, + 'aliases': network_config['Aliases'], + 'links': network_config['Links'] + } + if network_config['Gateway']: + network_config.update({'ipv4_address': network_config['IPAddress']}) + if network_config['IPv6Gateway']: + network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) + try: + network.connect(**new_network_config) + except APIError as e: + self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) + + new_container.start() + def update(self): updated_count = 0 - updateable, depends_on_containers = self.check() + updateable, depends_on_containers, hard_depends_on_containers = self.check() for container in depends_on_containers: self.stop_container(container) @@ -243,44 +296,7 @@ def update(self): self.logger.info('%s will be updated', container.name) - # new container dict to create new container from - new_config = set_properties(old=container, new=latest_image) - - self.stop_container(container) - - self.logger.debug('Removing container: %s', container.name) - try: - container.remove() - except NotFound as e: - self.logger.error("Could not remove container. Error: %s", e) - continue - - created = self.client.api.create_container(**new_config) - new_container = self.client.containers.get(created.get("Id")) - - # disconnect new container from old networks (with possible broken config) - for network_config in new_container.attrs['NetworkSettings']['Networks']: - network = self.client.networks.get(network_config['NetworkID']) - network.disconnect(new_container.id, force=True) - - # connect the new container to all networks of the old container - for network_config in container.attrs['NetworkSettings']['Networks']: - network = self.client.networks.get(network_config['NetworkID']) - new_network_config = { - 'container': container, - 'aliases': network_config['Aliases'], - 'links': network_config['Links'] - } - if network_config['Gateway']: - network_config.update({'ipv4_address': network_config['IPAddress']}) - if network_config['IPv6Gateway']: - network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) - try: - network.connect(**new_network_config) - except APIError as e: - self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) - - new_container.start() + self.recreate(container, latest_image) if self.config.cleanup: try: @@ -298,6 +314,9 @@ def update(self): for container in depends_on_containers: container.start() + for container in hard_depends_on_containers: + self.recreate(container, container.image) + if updated_count > 0: self.notification_manager.send(container_tuples=updateable, socket=self.socket, kind='update') From 126b552ea30f79a24e909046fa26093e4b3b0531 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Mon, 11 Feb 2019 22:44:49 -0600 Subject: [PATCH 18/38] consolidation --- pyouroboros/dockerclient.py | 18 ++++++++++-------- pyouroboros/helpers.py | 2 +- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 6da59417..83f602a6 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -247,16 +247,15 @@ def recreate(self, container, latest_image): created = self.client.api.create_container(**new_config) new_container = self.client.containers.get(created.get("Id")) - # disconnect new container from old networks (with possible broken config) - for network_config in new_container.attrs['NetworkSettings']['Networks']: - network = self.client.networks.get(network_config['NetworkID']) - network.disconnect(new_container.id, force=True) - # connect the new container to all networks of the old container - for network_config in container.attrs['NetworkSettings']['Networks']: + for network_name, network_config in container.attrs['NetworkSettings']['Networks'].items(): network = self.client.networks.get(network_config['NetworkID']) + try: + network.disconnect(new_container.id, force=True) + except APIError: + pass new_network_config = { - 'container': container, + 'container': new_container, 'aliases': network_config['Aliases'], 'links': network_config['Links'] } @@ -273,7 +272,10 @@ def recreate(self, container, latest_image): def update(self): updated_count = 0 - updateable, depends_on_containers, hard_depends_on_containers = self.check() + try: + updateable, depends_on_containers, hard_depends_on_containers = self.check() + except TypeError: + return for container in depends_on_containers: self.stop_container(container) diff --git a/pyouroboros/helpers.py b/pyouroboros/helpers.py index 6f2634a6..776a3b8b 100644 --- a/pyouroboros/helpers.py +++ b/pyouroboros/helpers.py @@ -20,7 +20,7 @@ def set_properties(old, new, self_name=None): 'entrypoint': old.attrs['Config']['Entrypoint'], 'environment': old.attrs['Config']['Env'], # networks are conigured later - 'networking_config': None + #'networking_config': None } return properties From e3c4d51ba32d2bf8862dc5fb3165ca0184b8b640 Mon Sep 17 00:00:00 2001 From: Lars de Ridder Date: Tue, 12 Feb 2019 14:13:23 +0100 Subject: [PATCH 19/38] Set labels enable if labels only is set --- pyouroboros/config.py | 4 ++++ pyouroboros/ouroboros.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pyouroboros/config.py b/pyouroboros/config.py index f46d7921..e260aa2e 100644 --- a/pyouroboros/config.py +++ b/pyouroboros/config.py @@ -112,6 +112,10 @@ def parse(self): if self.interval < 30: self.interval = 30 + + # Setting lables only implies labels should be enabled. + if self.labels_only: + self.label_enable = True for option in ['docker_sockets', 'notifiers', 'monitor', 'ignore']: if isinstance(getattr(self, option), str): diff --git a/pyouroboros/ouroboros.py b/pyouroboros/ouroboros.py index 53439a94..d71846bd 100644 --- a/pyouroboros/ouroboros.py +++ b/pyouroboros/ouroboros.py @@ -80,7 +80,8 @@ def main(): docker_group.add_argument('-M', '--labels-only', default=Config.labels_only, dest='LABELS_ONLY', action='store_true', help='Only watch containers that utilize labels\n' - 'This allows a more strict compliance for environments' + 'This allows a more strict compliance for environments\n' + 'Note: This implies --label-enable true' 'DEFAULT: False') docker_group.add_argument('-c', '--cleanup', default=Config.cleanup, dest='CLEANUP', action='store_true', From 92c19c7956cb63160aa097b5a8848bf7e1f92da1 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Tue, 12 Feb 2019 09:20:21 -0600 Subject: [PATCH 20/38] remove networking_config none --- pyouroboros/helpers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyouroboros/helpers.py b/pyouroboros/helpers.py index 776a3b8b..252e6083 100644 --- a/pyouroboros/helpers.py +++ b/pyouroboros/helpers.py @@ -18,9 +18,7 @@ def set_properties(old, new, self_name=None): 'host_config': old.attrs['HostConfig'], 'labels': old.attrs['Config']['Labels'], 'entrypoint': old.attrs['Config']['Entrypoint'], - 'environment': old.attrs['Config']['Env'], - # networks are conigured later - #'networking_config': None + 'environment': old.attrs['Config']['Env'] } return properties From 4629defedf10cf6ce3351d6b7e7248d251100c4d Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Tue, 12 Feb 2019 09:59:21 -0600 Subject: [PATCH 21/38] reload before start --- pyouroboros/dockerclient.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 83f602a6..261e95eb 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -314,6 +314,8 @@ def update(self): self.data_manager.add(label='all', socket=self.socket) for container in depends_on_containers: + # Reload container to ensure it isn't referencing the old image + container.reload() container.start() for container in hard_depends_on_containers: From cada9ba62490b24ce063c65aa3f1e1b8cb285faa Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Tue, 12 Feb 2019 10:16:48 -0600 Subject: [PATCH 22/38] move remove to its own function --- pyouroboros/dockerclient.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 261e95eb..72a901c2 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -232,11 +232,7 @@ def stop_container(self, container): else: container.stop() - def recreate(self, container, latest_image): - new_config = set_properties(old=container, new=latest_image) - - self.stop_container(container) - + def remove_container(self, container): self.logger.debug('Removing container: %s', container.name) try: container.remove() @@ -244,6 +240,11 @@ def recreate(self, container, latest_image): self.logger.error("Could not remove container. Error: %s", e) return + def recreate(self, container, latest_image): + new_config = set_properties(old=container, new=latest_image) + + self.stop_container(container) + created = self.client.api.create_container(**new_config) new_container = self.client.containers.get(created.get("Id")) @@ -277,9 +278,12 @@ def update(self): except TypeError: return - for container in depends_on_containers: + for container in depends_on_containers + hard_depends_on_containers: self.stop_container(container) + for container in hard_depends_on_containers: + self.remove_container(container) + for container, current_image, latest_image in updateable: if self.config.dry_run: # Ugly hack for repo digest From 41f7e2e9e0315f40c33d1bfe0c83ed729465d24b Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Tue, 12 Feb 2019 10:22:01 -0600 Subject: [PATCH 23/38] rename functions for more human readability --- pyouroboros/dockerclient.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 72a901c2..4e76dec6 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -116,7 +116,7 @@ def pull(self, image_object): self.logger.critical("Couldn't pull. Skipping. Error: %s", e) raise ConnectionError - def get_running(self): + def running_filter(self): """Return running container objects list, except ouroboros itself""" running_containers = [] try: @@ -139,7 +139,7 @@ def get_running(self): def monitor_filter(self): """Return filtered running container objects list""" - running_containers = self.get_running() + running_containers = self.running_filter() monitored_containers = [] for container in running_containers: @@ -162,7 +162,7 @@ def monitor_filter(self): return monitored_containers - def check(self): + def socket_check(self): depends_on_names = [] hard_depends_on_names = [] updateable = [] @@ -219,7 +219,7 @@ def check(self): return updateable, depends_on_containers, hard_depends_on_containers - def stop_container(self, container): + def stop(self, container): self.logger.debug('Stopping container: %s', container.name) stop_signal = container.labels.get('com.ouroboros.stop_signal', False) if stop_signal: @@ -232,7 +232,7 @@ def stop_container(self, container): else: container.stop() - def remove_container(self, container): + def remove(self, container): self.logger.debug('Removing container: %s', container.name) try: container.remove() @@ -243,7 +243,8 @@ def remove_container(self, container): def recreate(self, container, latest_image): new_config = set_properties(old=container, new=latest_image) - self.stop_container(container) + self.stop(container) + self.remove(container) created = self.client.api.create_container(**new_config) new_container = self.client.containers.get(created.get("Id")) @@ -274,15 +275,12 @@ def recreate(self, container, latest_image): def update(self): updated_count = 0 try: - updateable, depends_on_containers, hard_depends_on_containers = self.check() + updateable, depends_on_containers, hard_depends_on_containers = self.socket_check() except TypeError: return for container in depends_on_containers + hard_depends_on_containers: - self.stop_container(container) - - for container in hard_depends_on_containers: - self.remove_container(container) + self.stop(container) for container, current_image, latest_image in updateable: if self.config.dry_run: From 856c32a5697b485753647321e2eb8c3f47292037 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Tue, 12 Feb 2019 10:25:57 -0600 Subject: [PATCH 24/38] move functions into groups for readability --- pyouroboros/dockerclient.py | 109 ++++++++++++++++++------------------ 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 4e76dec6..e6cb79fe 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -68,6 +68,60 @@ def __init__(self, docker_client): self.monitored = self.monitor_filter() + # Container sub functions + def stop(self, container): + self.logger.debug('Stopping container: %s', container.name) + stop_signal = container.labels.get('com.ouroboros.stop_signal', False) + if stop_signal: + try: + container.kill(signal=stop_signal) + except APIError as e: + self.logger.error('Cannot kill container using signal %s. stopping normally. Error: %s', + stop_signal, e) + container.stop() + else: + container.stop() + + def remove(self, container): + self.logger.debug('Removing container: %s', container.name) + try: + container.remove() + except NotFound as e: + self.logger.error("Could not remove container. Error: %s", e) + return + + def recreate(self, container, latest_image): + new_config = set_properties(old=container, new=latest_image) + + self.stop(container) + self.remove(container) + + created = self.client.api.create_container(**new_config) + new_container = self.client.containers.get(created.get("Id")) + + # connect the new container to all networks of the old container + for network_name, network_config in container.attrs['NetworkSettings']['Networks'].items(): + network = self.client.networks.get(network_config['NetworkID']) + try: + network.disconnect(new_container.id, force=True) + except APIError: + pass + new_network_config = { + 'container': new_container, + 'aliases': network_config['Aliases'], + 'links': network_config['Links'] + } + if network_config['Gateway']: + network_config.update({'ipv4_address': network_config['IPAddress']}) + if network_config['IPv6Gateway']: + network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) + try: + network.connect(**new_network_config) + except APIError as e: + self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) + + new_container.start() + def pull(self, image_object): """Docker pull image tag/latest""" image = image_object @@ -116,6 +170,7 @@ def pull(self, image_object): self.logger.critical("Couldn't pull. Skipping. Error: %s", e) raise ConnectionError + # Filters def running_filter(self): """Return running container objects list, except ouroboros itself""" running_containers = [] @@ -162,6 +217,7 @@ def monitor_filter(self): return monitored_containers + # Socket Functions def socket_check(self): depends_on_names = [] hard_depends_on_names = [] @@ -219,59 +275,6 @@ def socket_check(self): return updateable, depends_on_containers, hard_depends_on_containers - def stop(self, container): - self.logger.debug('Stopping container: %s', container.name) - stop_signal = container.labels.get('com.ouroboros.stop_signal', False) - if stop_signal: - try: - container.kill(signal=stop_signal) - except APIError as e: - self.logger.error('Cannot kill container using signal %s. stopping normally. Error: %s', - stop_signal, e) - container.stop() - else: - container.stop() - - def remove(self, container): - self.logger.debug('Removing container: %s', container.name) - try: - container.remove() - except NotFound as e: - self.logger.error("Could not remove container. Error: %s", e) - return - - def recreate(self, container, latest_image): - new_config = set_properties(old=container, new=latest_image) - - self.stop(container) - self.remove(container) - - created = self.client.api.create_container(**new_config) - new_container = self.client.containers.get(created.get("Id")) - - # connect the new container to all networks of the old container - for network_name, network_config in container.attrs['NetworkSettings']['Networks'].items(): - network = self.client.networks.get(network_config['NetworkID']) - try: - network.disconnect(new_container.id, force=True) - except APIError: - pass - new_network_config = { - 'container': new_container, - 'aliases': network_config['Aliases'], - 'links': network_config['Links'] - } - if network_config['Gateway']: - network_config.update({'ipv4_address': network_config['IPAddress']}) - if network_config['IPv6Gateway']: - network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) - try: - network.connect(**new_network_config) - except APIError as e: - self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) - - new_container.start() - def update(self): updated_count = 0 try: From 5398638eca77314520882259a7b1d47b2f2045e2 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Tue, 12 Feb 2019 13:26:53 -0600 Subject: [PATCH 25/38] account for possible failure. Addresses #201 --- pyouroboros/dockerclient.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index e6cb79fe..d014372b 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -112,13 +112,20 @@ def recreate(self, container, latest_image): 'links': network_config['Links'] } if network_config['Gateway']: - network_config.update({'ipv4_address': network_config['IPAddress']}) + new_network_config.update({'ipv4_address': network_config['IPAddress']}) if network_config['IPv6Gateway']: - network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) + new_network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) try: network.connect(**new_network_config) except APIError as e: - self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) + if 'user configured subnets' in str(e): + if new_network_config.get('ipv4_address'): + del new_network_config['ipv4_address'] + if new_network_config.get('ipv6_address'): + del new_network_config['ipv6_address'] + network.connect(**new_network_config) + else: + self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) new_container.start() From 977e3b6f4e97e5d14190cc08aa7864a69a1976d8 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Tue, 12 Feb 2019 13:28:57 -0600 Subject: [PATCH 26/38] final cleanup --- pyouroboros/dockerclient.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index d014372b..77cf1eaf 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -109,12 +109,10 @@ def recreate(self, container, latest_image): new_network_config = { 'container': new_container, 'aliases': network_config['Aliases'], - 'links': network_config['Links'] + 'links': network_config['Links'], + 'ipv4_address': network_config['IPAddress'], + 'ipv6_address': network_config['GlobalIPv6Address'] } - if network_config['Gateway']: - new_network_config.update({'ipv4_address': network_config['IPAddress']}) - if network_config['IPv6Gateway']: - new_network_config.update({'ipv6_address': network_config['GlobalIPv6Address']}) try: network.connect(**new_network_config) except APIError as e: From 750c12df34e14e7d0eadf715e228cc4860233e14 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Tue, 12 Feb 2019 14:00:23 -0600 Subject: [PATCH 27/38] check both messages + network.name --- pyouroboros/dockerclient.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 77cf1eaf..341d0caa 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -116,14 +116,14 @@ def recreate(self, container, latest_image): try: network.connect(**new_network_config) except APIError as e: - if 'user configured subnets' in str(e): + if any(err in str(e) for err in ['user configured subnets', 'user defined networks']): if new_network_config.get('ipv4_address'): del new_network_config['ipv4_address'] if new_network_config.get('ipv6_address'): del new_network_config['ipv6_address'] network.connect(**new_network_config) else: - self.logger.error('Unable to attach updated container to network "%s". Error: %s', network, e) + self.logger.error('Unable to attach updated container to network "%s". Error: %s', network.name, e) new_container.start() From 55fb7b1253b80f3f54350e2bfe19954faf66f5c0 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 13 Feb 2019 10:52:32 -0600 Subject: [PATCH 28/38] change tag grab logic. Fixes #204 --- pyouroboros/dockerclient.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 341d0caa..848655b7 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -127,15 +127,13 @@ def recreate(self, container, latest_image): new_container.start() - def pull(self, image_object): + def pull(self, current_tag): """Docker pull image tag/latest""" - image = image_object - try: - tag = image.tags[0] - except IndexError: - self.logger.error('Malformed or missing tag. Skipping...') + tag = current_tag + if not tag: + self.logger.error('Missing tag. Skipping...') raise ConnectionError - if self.config.latest and image.tags[0][-6:] != 'latest': + if self.config.latest and tag[-6:] != 'latest': if ':' in tag: split_tag = tag.split(':') if len(split_tag) == 2: @@ -169,7 +167,7 @@ def pull(self, image_object): self.logger.critical("Invalid Credentials. Exiting") exit(1) elif 'Client.Timeout' in str(e): - self.logger.critical("Couldn't find an image on docker.com for %s. Local Build?", image.tags[0]) + self.logger.critical("Couldn't find an image on docker.com for %s. Local Build?", tag) raise ConnectionError elif ('pull access' or 'TLS handshake') in str(e): self.logger.critical("Couldn't pull. Skipping. Error: %s", e) @@ -239,12 +237,13 @@ def socket_check(self): for container in self.monitored: current_image = container.image + current_tag = container.attrs['Config']['Image'] shared_image = [uct for uct in updateable if uct[1].id == current_image.id] if shared_image: latest_image = shared_image[0][2] else: try: - latest_image = self.pull(current_image) + latest_image = self.pull(current_tag) except ConnectionError: continue From 397647ee08601b82ea72c96b01581bf3d73672f8 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 13 Feb 2019 12:16:20 -0600 Subject: [PATCH 29/38] Catch issue with #178 --- pyouroboros/dockerclient.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 848655b7..9a8fe2ca 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -430,8 +430,12 @@ def update(self): for service in self.monitored: image_string = service.attrs['Spec']['TaskTemplate']['ContainerSpec']['Image'] - tag = image_string.split('@')[0] - sha256 = image_string.split('@')[1][7:] + if '@' in image_string: + tag = image_string.split('@')[0] + sha256 = image_string.split('@')[1][7:] + else: + self.logger.error('No image SHA for %s. Skipping', image_string) + continue try: latest_image = self.pull(tag) From 588b2587a96b3ff6e8dc6e9e7be91a9f78fdd42c Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 13 Feb 2019 13:00:17 -0600 Subject: [PATCH 30/38] add mode to notification manager --- pyouroboros/dockerclient.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 9a8fe2ca..09be013e 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -472,4 +472,9 @@ def update(self): self.data_manager.add(label='all', socket=self.socket) if updated_count > 0: - self.notification_manager.send(container_tuples=updated_service_tuples, socket=self.socket, kind='update') + self.notification_manager.send( + container_tuples=updated_service_tuples, + socket=self.socket, + kind='update', + mode='service' + ) From 5b52b0826ca70e6ec48f1ac9428eb865ba862f82 Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 13 Feb 2019 13:02:24 -0600 Subject: [PATCH 31/38] watch for self update in swarm --- pyouroboros/dockerclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index 09be013e..cf1b06d4 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -453,7 +453,7 @@ def update(self): (service, sha256[-10:], latest_image) ) - if 'ouroboros' in service.name: + if 'ouroboros' in service.name and self.config.self_update: self.data_manager.total_updated[self.socket] += 1 self.data_manager.add(label=service.name, socket=self.socket) self.data_manager.add(label='all', socket=self.socket) From 849b2da567a03f6b19c7a3442a7004826b3dd591 Mon Sep 17 00:00:00 2001 From: Lars de Ridder Date: Wed, 13 Feb 2019 20:34:23 +0100 Subject: [PATCH 32/38] Now label_only true and label_enable false generates just a warning --- pyouroboros/config.py | 5 ++--- pyouroboros/ouroboros.py | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pyouroboros/config.py b/pyouroboros/config.py index e260aa2e..870e02a1 100644 --- a/pyouroboros/config.py +++ b/pyouroboros/config.py @@ -113,9 +113,8 @@ def parse(self): if self.interval < 30: self.interval = 30 - # Setting lables only implies labels should be enabled. - if self.labels_only: - self.label_enable = True + if self.labels_only and not self.label_enable: + self.logger.warning('labels_only enabled but not in use without label_enable') for option in ['docker_sockets', 'notifiers', 'monitor', 'ignore']: if isinstance(getattr(self, option), str): diff --git a/pyouroboros/ouroboros.py b/pyouroboros/ouroboros.py index d71846bd..53439a94 100644 --- a/pyouroboros/ouroboros.py +++ b/pyouroboros/ouroboros.py @@ -80,8 +80,7 @@ def main(): docker_group.add_argument('-M', '--labels-only', default=Config.labels_only, dest='LABELS_ONLY', action='store_true', help='Only watch containers that utilize labels\n' - 'This allows a more strict compliance for environments\n' - 'Note: This implies --label-enable true' + 'This allows a more strict compliance for environments' 'DEFAULT: False') docker_group.add_argument('-c', '--cleanup', default=Config.cleanup, dest='CLEANUP', action='store_true', From 4af95f6e18e99d81cb7c9f72070d044ca3ee92ae Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 13 Feb 2019 13:58:13 -0600 Subject: [PATCH 33/38] get version change bump preparation --- pyouroboros/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyouroboros/__init__.py b/pyouroboros/__init__.py index 5972b921..1dc08991 100644 --- a/pyouroboros/__init__.py +++ b/pyouroboros/__init__.py @@ -1,2 +1,2 @@ -VERSION = "1.2.0" +VERSION = "1.3.0" BRANCH = "develop" From 556951ab75435acf373c7b3eaa0a92832cfdfa8a Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 13 Feb 2019 14:01:34 -0600 Subject: [PATCH 34/38] Revert "get version change bump preparation" This reverts commit 4af95f6e --- pyouroboros/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyouroboros/__init__.py b/pyouroboros/__init__.py index 1dc08991..5972b921 100644 --- a/pyouroboros/__init__.py +++ b/pyouroboros/__init__.py @@ -1,2 +1,2 @@ -VERSION = "1.3.0" +VERSION = "1.2.0" BRANCH = "develop" From dc1e37f28bb6aa4a871e6bbe71654f7ef749d7db Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 13 Feb 2019 14:07:16 -0600 Subject: [PATCH 35/38] fix whitespace from pr #202 --- pyouroboros/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyouroboros/config.py b/pyouroboros/config.py index 870e02a1..38ef542b 100644 --- a/pyouroboros/config.py +++ b/pyouroboros/config.py @@ -112,7 +112,7 @@ def parse(self): if self.interval < 30: self.interval = 30 - + if self.labels_only and not self.label_enable: self.logger.warning('labels_only enabled but not in use without label_enable') From 7c775e879d02df981da01c6c43932b641fa42cae Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Wed, 13 Feb 2019 14:31:28 -0600 Subject: [PATCH 36/38] remove --latest. Fixes #206 --- pyouroboros/config.py | 8 +++----- pyouroboros/dockerclient.py | 14 ++------------ pyouroboros/ouroboros.py | 3 --- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/pyouroboros/config.py b/pyouroboros/config.py index 38ef542b..1f430807 100644 --- a/pyouroboros/config.py +++ b/pyouroboros/config.py @@ -5,7 +5,7 @@ class Config(object): options = ['INTERVAL', 'PROMETHEUS', 'DOCKER_SOCKETS', 'MONITOR', 'IGNORE', 'LOG_LEVEL', 'PROMETHEUS_ADDR', - 'PROMETHEUS_PORT', 'NOTIFIERS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'LATEST', 'CRON', + 'PROMETHEUS_PORT', 'NOTIFIERS', 'REPO_USER', 'REPO_PASS', 'CLEANUP', 'RUN_ONCE', 'CRON', 'INFLUX_URL', 'INFLUX_PORT', 'INFLUX_USERNAME', 'INFLUX_PASSWORD', 'INFLUX_DATABASE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DATA_EXPORT', 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', 'DRY_RUN', 'HOSTNAME', 'DOCKER_TLS_VERIFY', 'SWARM'] @@ -21,7 +21,6 @@ class Config(object): ignore = [] data_export = None log_level = 'info' - latest = False cleanup = False run_once = False dry_run = False @@ -91,9 +90,8 @@ def parse(self): setattr(self, option.lower(), opt) except ValueError as e: print(e) - elif option in ['LATEST', 'CLEANUP', 'RUN_ONCE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DRY_RUN', - 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', 'DOCKER_TLS_VERIFY', - 'SWARM']: + elif option in ['CLEANUP', 'RUN_ONCE', 'INFLUX_SSL', 'INFLUX_VERIFY_SSL', 'DRY_RUN', 'SWARM', + 'SELF_UPDATE', 'LABEL_ENABLE', 'DOCKER_TLS', 'LABELS_ONLY', 'DOCKER_TLS_VERIFY']: if env_opt.lower() in ['true', 'yes']: setattr(self, option.lower(), True) elif env_opt.lower() in ['false', 'no']: diff --git a/pyouroboros/dockerclient.py b/pyouroboros/dockerclient.py index cf1b06d4..9299d824 100644 --- a/pyouroboros/dockerclient.py +++ b/pyouroboros/dockerclient.py @@ -128,21 +128,11 @@ def recreate(self, container, latest_image): new_container.start() def pull(self, current_tag): - """Docker pull image tag/latest""" + """Docker pull image tag""" tag = current_tag if not tag: self.logger.error('Missing tag. Skipping...') raise ConnectionError - if self.config.latest and tag[-6:] != 'latest': - if ':' in tag: - split_tag = tag.split(':') - if len(split_tag) == 2: - if '/' not in split_tag[1]: - tag = split_tag[0] - else: - tag = ':'.join(split_tag[:-1]) - tag = f'{tag}:latest' - self.logger.debug('Checking tag: %s', tag) try: if self.config.dry_run: @@ -389,7 +379,7 @@ def monitor_filter(self): return monitored_services def pull(self, tag): - """Docker pull image tag/latest""" + """Docker pull image tag""" self.logger.debug('Checking tag: %s', tag) try: if self.config.dry_run: diff --git a/pyouroboros/ouroboros.py b/pyouroboros/ouroboros.py index 53439a94..5223edc5 100644 --- a/pyouroboros/ouroboros.py +++ b/pyouroboros/ouroboros.py @@ -86,9 +86,6 @@ def main(): docker_group.add_argument('-c', '--cleanup', default=Config.cleanup, dest='CLEANUP', action='store_true', help='Remove old images after updating') - docker_group.add_argument('-L', '--latest', default=Config.latest, dest='LATEST', action='store_true', - help='Check for latest image instead of pulling current tag') - docker_group.add_argument('-r', '--repo-user', default=Config.repo_user, dest='REPO_USER', help='Private docker registry username\n' 'EXAMPLE: foo@bar.baz') From 688845cf16e8f261e3c0fb543d2f77849ac496b4 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 13 Feb 2019 20:45:30 -0600 Subject: [PATCH 37/38] CHANGELOG + branch to master --- CHANGELOG.md | 40 +++++++++++++++++++++++++++++++++++++++- pyouroboros/__init__.py | 2 +- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 330313ec..41791b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Change Log +## [1.2.0](https://github.com/pyouroboros/ouroboros/tree/1.2.0) (2019-02-13) +[Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.1.2...1.2.0) + +**Implemented enhancements:** + +- Move "Interval container update" messages to debug log level [\#194](https://github.com/pyouroboros/ouroboros/issues/194) +- \[Feature Request\] Support for Swarm Services [\#178](https://github.com/pyouroboros/ouroboros/issues/178) +- Add Warning for label\_enable not set while using labels\_only [\#202](https://github.com/pyouroboros/ouroboros/pull/202) ([larsderidder](https://github.com/larsderidder)) + +**Fixed bugs:** + +- Change depends\_on logic [\#198](https://github.com/pyouroboros/ouroboros/issues/198) +- Containers relying upon network namespace of a container that gets updated breaks when the parent container is recreated [\#197](https://github.com/pyouroboros/ouroboros/issues/197) +- Exception when trying to update container with complex compose networks [\#196](https://github.com/pyouroboros/ouroboros/issues/196) +- Problem with network IPv4 address carry-over [\#193](https://github.com/pyouroboros/ouroboros/issues/193) +- Monitor Ignored Re-Address + jenkins cleanup [\#191](https://github.com/pyouroboros/ouroboros/pull/191) [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] ([DirtyCajunRice](https://github.com/DirtyCajunRice)) + +**Closed issues:** + +- Remove legacy --latest [\#206](https://github.com/pyouroboros/ouroboros/issues/206) [[breaking change](https://github.com/pyouroboros/ouroboros/labels/breaking%20change)] [[cleanup](https://github.com/pyouroboros/ouroboros/labels/cleanup)] +- Add environment variables in Wiki [\#203](https://github.com/pyouroboros/ouroboros/issues/203) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] +- Slack notifications via webhook not working [\#187](https://github.com/pyouroboros/ouroboros/issues/187) + +**Other Pull Requests** + +- v1.2.0 Merge [\#207](https://github.com/pyouroboros/ouroboros/pull/207) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- v1.2.0 to develop [\#206](https://github.com/pyouroboros/ouroboros/pull/206) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- Patch/tag bug [\#205](https://github.com/pyouroboros/ouroboros/pull/205) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- Patch/group 5 [\#201](https://github.com/pyouroboros/ouroboros/pull/201) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- Fix bug in user defined network detection [\#200](https://github.com/pyouroboros/ouroboros/pull/200) ([nightvisi0n](https://github.com/nightvisi0n)) +- Adjust apscheduler logger [\#199](https://github.com/pyouroboros/ouroboros/pull/199) ([circa10a](https://github.com/circa10a)) +- Carry over network config [\#195](https://github.com/pyouroboros/ouroboros/pull/195) ([nightvisi0n](https://github.com/nightvisi0n)) +- Jenkins tweaks [\#192](https://github.com/pyouroboros/ouroboros/pull/192) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- Swarm + Jenkins [\#188](https://github.com/pyouroboros/ouroboros/pull/188) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) + ## [1.1.2](https://github.com/pyouroboros/ouroboros/tree/1.1.2) (2019-02-02) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.1.1...1.1.2) @@ -11,8 +46,11 @@ - cron documentation example update [\#182](https://github.com/pyouroboros/ouroboros/issues/182) [[documentation](https://github.com/pyouroboros/ouroboros/labels/documentation)] -***Other Pull Requests** +**Other Pull Requests** + - v1.1.2 Merge [\#186](https://github.com/pyouroboros/ouroboros/pull/186) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- v1.1.2 to develop [\#183](https://github.com/pyouroboros/ouroboros/pull/183) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- Fix default timezone [\#177](https://github.com/pyouroboros/ouroboros/pull/177) ([circa10a](https://github.com/circa10a)) ## [1.1.1](https://github.com/pyouroboros/ouroboros/tree/1.1.1) (2019-02-01) [Full Changelog](https://github.com/pyouroboros/ouroboros/compare/1.1.0...1.1.1) diff --git a/pyouroboros/__init__.py b/pyouroboros/__init__.py index 5972b921..5ae08196 100644 --- a/pyouroboros/__init__.py +++ b/pyouroboros/__init__.py @@ -1,2 +1,2 @@ VERSION = "1.2.0" -BRANCH = "develop" +BRANCH = "master" From 5c43af4db5497f8efb9ff2ad588a5b877f164904 Mon Sep 17 00:00:00 2001 From: dirtycajunrice Date: Wed, 13 Feb 2019 20:46:45 -0600 Subject: [PATCH 38/38] bump numbers --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41791b06..4b866691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,8 +25,8 @@ **Other Pull Requests** -- v1.2.0 Merge [\#207](https://github.com/pyouroboros/ouroboros/pull/207) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) -- v1.2.0 to develop [\#206](https://github.com/pyouroboros/ouroboros/pull/206) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- v1.2.0 Merge [\#208](https://github.com/pyouroboros/ouroboros/pull/208) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) +- v1.2.0 to develop [\#207](https://github.com/pyouroboros/ouroboros/pull/207) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Patch/tag bug [\#205](https://github.com/pyouroboros/ouroboros/pull/205) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Patch/group 5 [\#201](https://github.com/pyouroboros/ouroboros/pull/201) ([DirtyCajunRice](https://github.com/DirtyCajunRice)) - Fix bug in user defined network detection [\#200](https://github.com/pyouroboros/ouroboros/pull/200) ([nightvisi0n](https://github.com/nightvisi0n))