diff --git a/main.py b/main.py index 7203edd..aad88b4 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import os +import json import requests @@ -10,6 +11,71 @@ BASH_COMPANION_SCRIPT = os.getcwd() + '/docker_updater.sh' REPO_UPDATER_SCRIPT = os.getcwd() + '/update_repos.sh' +class PortainerClient: + + def __init__(self, portainer_url, access_token): + self.portainer_url = portainer_url + self.access_token = access_token + + self.api_url = f"{self.portainer_url}/api" + + def _get_headers(self): + return { + "accept": "application/json", + "X-API-Key": self.access_token + } + + def list_stacks(self): + response = requests.get(f"{self.api_url}/stacks", headers=self._get_headers()) + return response.json() + + def get_stack(self, stack_id): + response = requests.get(f"{self.api_url}/stacks/{stack_id}", headers=self._get_headers()) + return response.json() + + def get_stack_file(self, stack_id): + response = requests.get(f"{self.api_url}/stacks/{stack_id}/file", headers=self._get_headers()) + return response.json() + + def upgrade_stack(self, stack): + file_response = self.get_stack_file(stack['Id']) + payload = { + "pullImage": True, + "prune": True, + "StackFileContent": file_response["StackFileContent"], + "Env": stack["Env"] + } + response = requests.put( + f"{self.api_url}/stacks/{stack['Id']}?endpointId={stack['EndpointId']}", + headers=self._get_headers(), + json=payload + ) + + return response.json() + + def list_stack_containers(self, endpoint, stack_name): + filters = '{"label":["com.docker.compose.project=' + stack_name + '"]}' + response = requests.get( + f"{self.api_url}/endpoints/{endpoint}/docker/containers/json?all=1&filters={filters}", + headers=self._get_headers() + ) + + return response.json() + + def update_image(self, endpoint, image): + response = requests.post( + f"{self.api_url}/endpoints/{endpoint}/docker/images/create?fromImage={image}", + headers=self._get_headers() + ) + + try: + status = json.loads(response.text.strip().split('\r\n')[-1])['status'] + except Exception: + return False + + return f"Downloaded newer image for {image}" in status + + def update_repositories(): for repo_path in config.git_repositories: ret = os.system(f"{REPO_UPDATER_SCRIPT} {repo_path}") @@ -17,7 +83,7 @@ def update_repositories(): exit(ret) -def update_stack(stack): +def update_local_stack(stack): project_name = stack['Name'] print(f"Updating stack: {project_name}") @@ -33,17 +99,23 @@ def update_stack(stack): if __name__ == '__main__': update_repositories() - headers = { - "accept": "application/json", - "X-API-Key": config.access_token - } + client = PortainerClient(portainer_url=config.portainer_url, access_token=config.access_token) - api_url = f"{config.portainer_url}/api/stacks" - response = requests.get(api_url, headers=headers) - stack_list = response.json() + for stack in client.list_stacks(): + endpoint = stack['EndpointId'] + stack_name = stack['Name'] + if stack['Status'] == 1: + containers = client.list_stack_containers(endpoint, stack_name) - for s in stack_list: - if s['Status'] == 1: - update_stack(s) + an_image_was_updated = False + for c in containers: + this_image_was_updated = client.update_image(endpoint, c['Image']) + an_image_was_updated = an_image_was_updated or this_image_was_updated + + if an_image_was_updated: + if endpoint == 2 and stack_name in ('portainer', 'traefik'): + update_local_stack(stack) + else: + client.upgrade_stack(stack) os.system('docker image prune -fa')