#!/usr/bin/env python3 import os import sys import argparse import requests import json import uuid from dotenv import load_dotenv from string import Template from pathlib import Path from urllib.parse import urlparse load_dotenv() required_env_vars = { 'PORTAINER': 'The portainer instance to deploy to', 'PORTAINER_API_KEY': 'API-Key to access portainer instance', 'PORTAINER_EP': 'Portainer Environment EndPoint to deploy to', 'GITEA_API_KEY': 'API-Key to access Gitea instance', 'DEPLOY_REPO_URL': 'The repository URL to deploy', 'DEPLOY_BRANCH': 'The branch to deploy', 'DEPLOY_HOST': 'The host name under which the deployment will be reachable', } # Try getting all arguments from (in order): 1 command line, 2 .env file, 3 Environment parser = argparse.ArgumentParser(description='Deploys a docker compose application to portainer.') for var, usage in required_env_vars.items(): parser.add_argument(f'--{var}', default=os.getenv(var, None), help=usage) args = parser.parse_args() # Check if all were parsed not_parsed = [] for var, usage in required_env_vars.items(): if not getattr(args, var): not_parsed.append(var) else: print(f'--{var}: {getattr(args, var)}') if not_parsed: print(f"Error: The following required environment variables were not provided: {', '.join(not_parsed)}") parser.print_help() sys.exit(1) deploy_webhook = str(uuid.uuid4()) ### Find CICD-runner portainer environment endpointId ### portainer_headers = { 'Content-Type': 'application/json', 'X-API-Key': args.PORTAINER_API_KEY } endpoint_url = f'{args.PORTAINER}/api/endpoints' json_endpoints = None try: response = requests.get(endpoint_url, headers=portainer_headers) response.raise_for_status() # Raise HTTPError for bad requests json_endpoints = response.json() except requests.exceptions.RequestException as err: raise Exception(f'Could not retrieve portainer endpoints: {err}') endpoint_id = None for endpoint in json_endpoints: if endpoint["Name"] == args.PORTAINER_EP: endpoint_id = endpoint["Id"] break if endpoint_id is None: raise Exception(f'Portainer endpoint \'{args.PORTAINER_EP}\' not found.') else: print(f'Found portainer endpoint \'{args.PORTAINER_EP}\' has id: \'{endpoint_id}\'.') repo_url = urlparse(args.DEPLOY_REPO_URL) gitea = f"{repo_url.scheme}://{repo_url.netloc}" repo_path = repo_url.path repo_parts = repo_path.strip('/').split('/') owner = repo_parts[0] repo = repo_parts[1] ### Template substitution for the portainer stack deployment ### portainer_deploy_payload = { "additionalFiles": [ "deploy/portainer/portainer_deploy.docker-compose.yml" ], "autoUpdate": { "webhook": deploy_webhook }, "composeFile": "docker-compose.yml", "env": [ { "name": "HOST", "value": args.DEPLOY_HOST }, { "name": "COMPOSE_PROJECT_NAME", "value": args.DEPLOY_BRANCH } ], "fromAppTemplate": False, "name": f"{owner}_{repo}_{args.DEPLOY_BRANCH.replace('/', '_')}".lower(), "repositoryAuthentication": True, "repositoryUsername": "cicd", "repositoryPassword": "gJ6@$7ZjWGyV4%i", "repositoryReferenceName": f"refs/heads/{args.DEPLOY_BRANCH}", "repositoryURL": args.DEPLOY_REPO_URL, "tlsskipVerify": False } ### Deploy to portainer ### deploy_url = f'{args.PORTAINER}/api/stacks/create/standalone/repository?endpointId={endpoint_id}' try: response = requests.post(deploy_url, headers=portainer_headers, json=portainer_deploy_payload) response.raise_for_status() # Raise HTTPError for bad requests deploy_response = response.json() except requests.exceptions.RequestException as err: raise Exception(f'Could not deploy portainer stack: {err}') ### Add Webhook to Gitea ### webhook_payload = { "type": "gitea", "branch_filter": f"{args.DEPLOY_BRANCH}", "config": { "url": f"{args.PORTAINER}/api/stacks/webhooks/{deploy_webhook}", "content_type": "json" }, "events": ["push"], # You can specify other events as needed "active": True } webhook_url = f'{gitea}/api/v1/repos/{repo_path}/hooks' webhook_headers = { "Authorization": f"token {args.GITEA_API_KEY}" } try: response = requests.post(webhook_url, headers=webhook_headers, json=webhook_payload) response.raise_for_status() # Raise HTTPError for bad requests webhook_response = response.json() except requests.exceptions.RequestException as err: raise Exception(f'Could not add webhook to Gitea: {err}') print(f'Successfully deployed project')