From 3b79e59076872489fa9cb758ae32810800100657 Mon Sep 17 00:00:00 2001 From: ewandor Date: Fri, 24 Nov 2023 21:04:05 +0100 Subject: [PATCH 1/5] Adding quotes in bash --- docker_updater.sh | 6 +++--- update_repos.sh | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docker_updater.sh b/docker_updater.sh index b9190a3..5de7295 100755 --- a/docker_updater.sh +++ b/docker_updater.sh @@ -5,6 +5,6 @@ set -e PROJECT_NAME=$1 COMPOSE_FILE=$2 -docker compose -p $PROJECT_NAME -f $COMPOSE_FILE stop -docker compose -p $PROJECT_NAME -f $COMPOSE_FILE pull -docker compose -p $PROJECT_NAME -f $COMPOSE_FILE up -d +docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" stop +docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" pull +docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" up -d diff --git a/update_repos.sh b/update_repos.sh index aaf52ce..356a2cf 100755 --- a/update_repos.sh +++ b/update_repos.sh @@ -1,6 +1,6 @@ REPO_PATH=$1 -git -C $REPO_PATH fetch --tags -LAST_HASH=`git -C $REPO_PATH rev-list --tags --max-count=1` -LAST_TAG=`git -C $REPO_PATH describe --tags $LAST_HASH` -git -C $REPO_PATH checkout $LAST_TAG +git -C "$REPO_PATH" fetch --tags +LAST_HASH=$(git -C "$REPO_PATH" rev-list --tags --max-count=1) +LAST_TAG=$(git -C "$REPO_PATH" describe --tags "$LAST_HASH") +git -C "$REPO_PATH" checkout "$LAST_TAG" From 9b5a45290a45f1174254698aad47fab7d4c1264e Mon Sep 17 00:00:00 2001 From: ewandor Date: Fri, 1 Dec 2023 20:40:10 +0100 Subject: [PATCH 2/5] Improving logging further --- main.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 3b46d3e..06111a4 100644 --- a/main.py +++ b/main.py @@ -102,23 +102,29 @@ if __name__ == '__main__': client = PortainerClient(portainer_url=config.portainer_url, access_token=config.access_token) for stack in client.list_stacks(): - endpoint = stack['EndpointId'] - stack_name = stack['Name'] if stack['Status'] == 1: + endpoint = stack['EndpointId'] + stack_name = stack['Name'] + + print(f"Checking {stack_name} for updates.") + containers = client.list_stack_containers(endpoint, stack_name) an_image_was_updated = False for c in containers: this_image_was_updated = client.update_image(endpoint, c['Image']) if this_image_was_updated: - print(f"{c['Image']} was updated") + print(f"{c['Image']} was updated.") an_image_was_updated = True if an_image_was_updated: - print(f"Restarting {stack_name}") + print(f"{stack_name} was updated. Restarting...") if endpoint == 2 and stack_name in ('portainer', 'traefik'): update_local_stack(stack) else: client.upgrade_stack(stack) + print(f"{stack_name} restarted.") + else: + print(f"{stack_name} is up to date. Skipping.") os.system('docker image prune -fa') From 3b6f13abc4b9829fd5c646a0063339815a66becf Mon Sep 17 00:00:00 2001 From: ewandor Date: Fri, 1 Dec 2023 20:45:06 +0100 Subject: [PATCH 3/5] Changing local updater sequence --- docker_updater.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker_updater.sh b/docker_updater.sh index 5de7295..c429f4e 100755 --- a/docker_updater.sh +++ b/docker_updater.sh @@ -5,6 +5,6 @@ set -e PROJECT_NAME=$1 COMPOSE_FILE=$2 -docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" stop docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" pull +docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" down docker compose -p "$PROJECT_NAME" -f "$COMPOSE_FILE" up -d From aa60dd25298b59f6859b85eb9daf605c2660778e Mon Sep 17 00:00:00 2001 From: ewandor Date: Sat, 2 Dec 2023 01:50:01 +0100 Subject: [PATCH 4/5] Adding endpoint separation to allow image purging per endpoint --- main.py | 73 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index 06111a4..b401121 100644 --- a/main.py +++ b/main.py @@ -25,8 +25,13 @@ class PortainerClient: "X-API-Key": self.access_token } - def list_stacks(self): - response = requests.get(f"{self.api_url}/stacks", headers=self._get_headers()) + def list_endpoints(self): + response = requests.get(f"{self.api_url}/endpoints", headers=self._get_headers()) + return response.json() + + def list_stacks(self, endpoint_id=None): + filters = f'?filters={{"EndpointID":{endpoint_id}}}' if endpoint_id else "" + response = requests.get(f"{self.api_url}/stacks{filters}", headers=self._get_headers()) return response.json() def get_stack(self, stack_id): @@ -75,6 +80,12 @@ class PortainerClient: return f"Downloaded newer image for {image}" in status + def delete_image(self, endpoint, image): + response = requests.delete( + f"{self.api_url}/endpoints/{endpoint}/docker/images/{image}?force=false", + headers=self._get_headers() + ) + def update_repositories(): for repo_path in config.git_repositories: @@ -83,7 +94,19 @@ def update_repositories(): exit(ret) -def update_local_stack(stack): +def update_stack_images(endpoint, stack_name): + containers = client.list_stack_containers(endpoint, stack_name) + + updated_images = [] + for c in containers: + if client.update_image(endpoint, c['Image']): + print(f"{c['Image']} was updated.") + updated_images.append(c['ImageID']) + + return updated_images + + +def upgrade_local_stack(stack): project_name = stack['Name'] print(f"Updating stack: {project_name}") @@ -96,35 +119,39 @@ def update_local_stack(stack): print(f"Stack {project_name} updated!\n") -if __name__ == '__main__': - update_repositories() - - client = PortainerClient(portainer_url=config.portainer_url, access_token=config.access_token) - - for stack in client.list_stacks(): +def upgrade_endpoint_stack(client, endpoint): + endpoint_id = endpoint['Id'] + endpoint_updated_images = [] + for stack in client.list_stacks(endpoint_id): if stack['Status'] == 1: - endpoint = stack['EndpointId'] stack_name = stack['Name'] print(f"Checking {stack_name} for updates.") - containers = client.list_stack_containers(endpoint, stack_name) - - an_image_was_updated = False - for c in containers: - this_image_was_updated = client.update_image(endpoint, c['Image']) - if this_image_was_updated: - print(f"{c['Image']} was updated.") - an_image_was_updated = True - - if an_image_was_updated: + updated_images = update_stack_images(endpoint_id, stack_name) + if updated_images: + endpoint_updated_images += updated_images print(f"{stack_name} was updated. Restarting...") - if endpoint == 2 and stack_name in ('portainer', 'traefik'): - update_local_stack(stack) + if endpoint_id == 2 and stack_name in ('portainer', 'traefik'): + upgrade_local_stack(stack) else: client.upgrade_stack(stack) print(f"{stack_name} restarted.") else: print(f"{stack_name} is up to date. Skipping.") - os.system('docker image prune -fa') + if endpoint_updated_images: + print("Deleting obsolete images") + for image_id in endpoint_updated_images: + client.delete_image(endpoint_id, image_id) + else: + print("No obsolete image to delete") + + +if __name__ == '__main__': + update_repositories() + + client = PortainerClient(portainer_url=config.portainer_url, access_token=config.access_token) + + for e in client.list_endpoints(): + upgrade_endpoint_stack(client, e) From 8a84a70c59acb4a530b8a7761674eb93381b5d8e Mon Sep 17 00:00:00 2001 From: ewandor Date: Thu, 15 Feb 2024 10:55:53 +0100 Subject: [PATCH 5/5] Adding dynamic interpreter selector and real script path finding --- main.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) mode change 100644 => 100755 main.py diff --git a/main.py b/main.py old mode 100644 new mode 100755 index b401121..5055c9f --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import os import json @@ -5,10 +7,10 @@ import requests import config - DOCKER_COMPOSE_PATH = config.portainer_compose_dir + '/{}/docker-compose.yml' -BASH_COMPANION_SCRIPT = os.getcwd() + '/docker_updater.sh' -REPO_UPDATER_SCRIPT = os.getcwd() + '/update_repos.sh' +SCRIPT_DIR = os.path.dirname(__file__) +BASH_COMPANION_SCRIPT = f'{SCRIPT_DIR}/docker_updater.sh' +REPO_UPDATER_SCRIPT = f'{SCRIPT_DIR}/update_repos.sh' class PortainerClient: