diff --git a/docker_updater.sh b/docker_updater.sh index b9190a3..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 up -d +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 diff --git a/main.py b/main.py old mode 100644 new mode 100755 index 2ad3183..7c80544 --- a/main.py +++ b/main.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import os import json from time import sleep @@ -6,10 +8,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: @@ -30,8 +32,12 @@ class PortainerClient: response = requests.get(f"{self.api_url}/{uri}", headers=self._get_headers()) return response.json() - def list_stacks(self): - return self._get('stacks') + def list_endpoints(self): + return self._get("endpoints") + + def list_stacks(self, endpoint_id=None): + filters = f'?filters={{"EndpointID":{endpoint_id}}}' if endpoint_id else "" + return self._get(f'stacks{filters}') def get_stack(self, stack_id): return self._get(f'stacks/{stack_id}') @@ -76,6 +82,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 ping_server(self): try: r = requests.get(f"{self.api_url}/", headers=self._get_headers()) @@ -92,7 +104,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}") @@ -105,32 +129,42 @@ 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(): - endpoint = stack['EndpointId'] - stack_name = stack['Name'] +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: - containers = client.list_stack_containers(endpoint, stack_name) + stack_name = 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 + print(f"Checking {stack_name} for updates.") - if an_image_was_updated: - print(f"Restarting {stack_name}") - if endpoint == 2 and stack_name in ('portainer', 'traefik'): - update_local_stack(stack) + 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_id == 2 and stack_name in ('portainer', 'traefik'): + upgrade_local_stack(stack) while not client.ping_server(): print("Waiting for server to be back online...") sleep(1) 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) 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"