diff --git a/.drone.yml b/.drone.yml index eb7a46a..d318b3f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,9 +7,17 @@ steps: image: alpine commands: - apk update - - apk add envsubst curl - - envsubst < portainer_deploy.template.json > portainer_deploy.json - - curl -d @portainer_deploy.json -H "Content-Type:application/json" -H "X-API-Key:ptr_RwxH2Cd+htdD2FoFiG46erT9beyvj9VoF3BrQPtDH3Q=" -X POST https://dvportainer.privatedns.org/api/stacks/create/standalone/repository?endpointId=10 + - apk add envsubst curl python3 + - python3 -m ensurepip + - pip3 install requests python-dotenv --quiet + - python3 deploy/portainer/deploy.py \ + --PORTAINER https://dvportainer.privatedns.org \ + --API_KEY=ptr_RwxH2Cd+htdD2FoFiG46erT9beyvj9VoF3BrQPtDH3Q= \ + --PORTAINER_EP=CICD-runner \ + --DEPLOY_REPO_URL=${DRONE_REPO_LINK} \ + --DEPLOY_REF=${DRONE_COMMIT_REF} \ + --DEPLOY_HOST=dvdemo.privatedns.org \ + --DEPLOY_NAME=${DRONE_COMMIT_SHA} trigger: branch: diff --git a/deploy/portainer/.env b/deploy/portainer/.env new file mode 100644 index 0000000..3c5f482 --- /dev/null +++ b/deploy/portainer/.env @@ -0,0 +1,8 @@ +PORTAINER=https://dvportainer.privatedns.org +API_KEY=ptr_RwxH2Cd+htdD2FoFiG46erT9beyvj9VoF3BrQPtDH3Q= +PORTAINER_EP=CICD-runner +DEPLOY_REPO_URL=https://dvgit.privatedns.org/lars/DeployTests +DEPLOY_REF=refs/heads/main +DEPLOY_HOST=dvdemo.privatedns.org +DEPLOY_PROJECT=ManualDeployTests +DEPLOY_NAME=ManualDeploy \ No newline at end of file diff --git a/deploy/portainer/deploy.py b/deploy/portainer/deploy.py new file mode 100644 index 0000000..109ddae --- /dev/null +++ b/deploy/portainer/deploy.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse +import requests +import json +from dotenv import load_dotenv +from string import Template + +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_PROJECT', '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 +with open('portainer_deploy.template.json', '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') \ No newline at end of file diff --git a/portainer_deploy.docker-compose.yml b/deploy/portainer/portainer_deploy.docker-compose.yml similarity index 100% rename from portainer_deploy.docker-compose.yml rename to deploy/portainer/portainer_deploy.docker-compose.yml diff --git a/portainer_deploy.template.json b/deploy/portainer/portainer_deploy.template.json similarity index 62% rename from portainer_deploy.template.json rename to deploy/portainer/portainer_deploy.template.json index 5630c19..39d6e92 100644 --- a/portainer_deploy.template.json +++ b/deploy/portainer/portainer_deploy.template.json @@ -13,15 +13,19 @@ "env": [ { "name": "HOST", - "value": "dvdemo.privatedns.org" + "value": "${DEPLOY_HOST}" + }, + { + "name": "COMPOSE_PROJECT_NAME", + "value": "${DEPLOY_NAME}" } ], "fromAppTemplate": false, - "name": "DeployTest_${DRONE_COMMIT}", + "name": "${DEPLOY_NAME}", "repositoryAuthentication": true, "repositoryUsername": "cicd", - "repositoryPassword": "gJ6@$7ZjWGyV4%i", - "repositoryReferenceName": "${DRONE_COMMIT_REF}", - "repositoryURL": "${DRONE_REPO_LINK}", + "repositoryPassword": "gJ6@$$7ZjWGyV4%i", + "repositoryReferenceName": "${DEPLOY_REF}", + "repositoryURL": "${DEPLOY_REPO_URL}", "tlsskipVerify": false } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2112a12..58389ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,6 @@ version: '3.4' services: http-echo: - container_name: http-echo image: mendhak/http-https-echo:latest expose: - 8080 diff --git a/portainer_deploy.json b/portainer_deploy.json deleted file mode 100644 index e97245d..0000000 --- a/portainer_deploy.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "additionalFiles": [], - "autoUpdate": { - "forcePullImage": false, - "forceUpdate": false, - "interval": "1m30s", - "jobID": "15", - "webhook": "05de31a2-79fa-4644-9c12-faa67e5c49f0" - }, - "composeFile": "docker-compose.yml", - "env": [ - { - "name": "HOST", - "value": "dvdemo.privatedns.org" - }, - { - "name": "ECHO_PATH", - "value": "deployed_echo" - } - ], - "fromAppTemplate": false, - "name": "DeployTest_test1", - "repositoryAuthentication": true, - "repositoryUsername": "cicd", - "repositoryPassword": "gJ6@$7ZjWGyV4%i", - "repositoryReferenceName": "refs/heads/main", - "repositoryURL": "https://dvgit.privatedns.org/lars/DeployTests", - "tlsskipVerify": false -}