#!/usr/bin/env python3 import os import sys import argparse import requests import json from dotenv import load_dotenv from string import Template from pathlib import Path load_dotenv() required_env_vars = { 'PORTAINER': 'The portainer instance to deploy to', 'API_KEY': 'API-Key to access portainer instance', 'PORTAINER_EP': 'Portainer Environment EndPoint to deploy to', 'DEPLOY_REPO_URL': 'The repository URL to deploy', 'DEPLOY_REF': 'The git ref to deploy', 'DEPLOY_HOST': 'The host name under which the deployment will be reachable', 'DEPLOY_NAME': 'Custom name to use as the deployment name', } # 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) portainer=args.PORTAINER api_key=args.API_KEY portainer_ep=args.PORTAINER_EP # Deploy variables to substitute in portainer deploy template deploy_variables = {key: getattr(args, key) for key in ['DEPLOY_REPO_URL', 'DEPLOY_HOST', 'DEPLOY_NAME', 'DEPLOY_REF'] } ### Find CICD-runner portainer environment endpointId ### headers = { 'Content-Type': 'application/json', 'X-API-Key': api_key, } url = f'{portainer}/api/endpoints' json_endpoints = None try: response = requests.get(url, headers=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"] == portainer_ep: endpoint_id = endpoint["Id"] break if endpoint_id is None: raise Exception(f'Portainer endpoint \'{portainer_ep}\' not found.') else: print(f'Found portainer endpoint \'{portainer_ep}\' has id: \'{endpoint_id}\'.') ### Template substitution for the portainer stack deployment ### portainer_deploy_json = None template_file = f'{Path( __file__ ).parent.absolute()}/portainer_deploy.template.json' with open(template_file, 'r') as file: portainer_template = Template(file.read()) # Perform variable substitution portainer_deploy_json = portainer_template.substitute(**deploy_variables) ### Deploy to portainer ### headers = { 'Content-Type': 'application/json', 'X-API-Key': api_key, } url = f'{portainer}/api/stacks/create/standalone/repository?endpointId={endpoint_id}' try: response = requests.post(url, headers=headers, json=json.loads(portainer_deploy_json)) response.raise_for_status() # Raise HTTPError for bad requests response_data = response.json() # Parse response JSON except requests.exceptions.RequestException as err: raise Exception(f'Could not deploy portainer stack: {err}') print(f'Successfully deployed project')