Compare commits
40 Commits
15f9fa410b
...
TestBranch
Author | SHA1 | Date | |
---|---|---|---|
af00f3729d | |||
2f141ba9c1 | |||
3a32158272 | |||
612b1e078c | |||
5b0e7641b8 | |||
fd15ed45ee | |||
eb0a6d9483 | |||
4ead2aa92d | |||
28f45e37ee | |||
280a2d6c92 | |||
e8fd475820 | |||
58381805e9 | |||
ab47ee2e18 | |||
1d42c7f95c | |||
398db54e15 | |||
c6f62df2c2 | |||
3a5b246220 | |||
00e89bdc64 | |||
cc61dba1a8 | |||
ffed04c846 | |||
fd6520128e | |||
90dbbc1239 | |||
|
3c163b391c | ||
b380440f5a | |||
|
39ee09787c | ||
87e1749d49 | |||
64363a929c | |||
521b046940 | |||
e8b353b28d | |||
84bac2f1ef | |||
12255dad5f | |||
d165ef0e20 | |||
8d86f4beac | |||
6d58f79915 | |||
97e02bea1b | |||
5b28a51b97 | |||
eb5d075aed | |||
b9fe963cab | |||
0ad30129c4 | |||
7f1796bd31 |
27
.drone.yml
27
.drone.yml
@@ -1,27 +0,0 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: deploy
|
||||
|
||||
steps:
|
||||
- name: Push to Portainer
|
||||
image: alpine
|
||||
commands:
|
||||
- apk update
|
||||
- apk add envsubst curl python3
|
||||
- python3 -m ensurepip
|
||||
- pip3 install requests python-dotenv --quiet
|
||||
- python3 deploy/portainer/deploy.py
|
||||
--PORTAINER https://dvportainer.privatedns.org
|
||||
--PORTAINER_API_KEY=ptr_RwxH2Cd+htdD2FoFiG46erT9beyvj9VoF3BrQPtDH3Q=
|
||||
--PORTAINER_EP=CICD-runner
|
||||
--GITEA_API_KEY=f449c74ec7f04e54fe1e481eae43492b34cea406
|
||||
--DEPLOY_REPO_URL=${DRONE_REPO_LINK}
|
||||
--DEPLOY_REF=${DRONE_COMMIT_REF}
|
||||
--DEPLOY_HOST=dvdemo.privatedns.org
|
||||
--DEPLOY_NAME=${DRONE_COMMIT_SHA}
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- pull_request
|
||||
action:
|
||||
- opened
|
27
.gitea/workflows/DeployPR.yaml
Normal file
27
.gitea/workflows/DeployPR.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Deploy PR
|
||||
run-name: ${{ gitea.actor }} is deploying a PR
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, reopened ]
|
||||
|
||||
jobs:
|
||||
Deploy PR:
|
||||
runs-on: ubuntu-latest
|
||||
container: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.12.0'
|
||||
- name: Install pip packages
|
||||
run: pip install requests python-dotenv --quiet
|
||||
- name: Run Deploy script
|
||||
run: >
|
||||
python deploy/portainer/deploy.py
|
||||
--PORTAINER https://dvportainer.privatedns.org
|
||||
--PORTAINER_API_KEY=ptr_RwxH2Cd+htdD2FoFiG46erT9beyvj9VoF3BrQPtDH3Q=
|
||||
--PORTAINER_EP=CICD-runner
|
||||
--GITEA_API_KEY=f449c74ec7f04e54fe1e481eae43492b34cea406
|
||||
--DEPLOY_REPO_URL=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}
|
||||
--DEPLOY_BRANCH=${GITHUB_HEAD_REF}
|
||||
--DEPLOY_HOST=dvdemo.privatedns.org
|
27
.gitea/workflows/UndeployPR.yaml
Normal file
27
.gitea/workflows/UndeployPR.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Undeploy PR
|
||||
run-name: ${{ gitea.actor }} is undeploying a PR
|
||||
on:
|
||||
pull_request:
|
||||
types: [ closed ]
|
||||
|
||||
jobs:
|
||||
Deploy PR:
|
||||
runs-on: ubuntu-latest
|
||||
container: catthehacker/ubuntu:act-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.12.0'
|
||||
- name: Install pip packages
|
||||
run: pip install requests python-dotenv --quiet
|
||||
- name: Run Undeploy script
|
||||
run: >
|
||||
python deploy/portainer/undeploy.py
|
||||
--PORTAINER https://dvportainer.privatedns.org
|
||||
--PORTAINER_API_KEY=ptr_RwxH2Cd+htdD2FoFiG46erT9beyvj9VoF3BrQPtDH3Q=
|
||||
--PORTAINER_EP=CICD-runner
|
||||
--GITEA_API_KEY=f449c74ec7f04e54fe1e481eae43492b34cea406
|
||||
--DEPLOY_REPO_URL=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}
|
||||
--DEPLOY_BRANCH=${GITHUB_HEAD_REF}
|
||||
|
@@ -3,7 +3,5 @@ PORTAINER_API_KEY=ptr_RwxH2Cd+htdD2FoFiG46erT9beyvj9VoF3BrQPtDH3Q=
|
||||
PORTAINER_EP=CICD-runner
|
||||
GITEA_API_KEY=f449c74ec7f04e54fe1e481eae43492b34cea406
|
||||
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
|
||||
DEPLOY_BRANCH=main
|
||||
DEPLOY_HOST=dvdemo.privatedns.org
|
@@ -19,9 +19,8 @@ required_env_vars = {
|
||||
'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_REF': 'The git ref to deploy',
|
||||
'DEPLOY_BRANCH': 'The branch 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
|
||||
@@ -44,26 +43,14 @@ if not_parsed:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
# 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']
|
||||
}
|
||||
deploy_variables['DEPLOY_WEBHOOK'] = str(uuid.uuid4())
|
||||
|
||||
portainer=args.PORTAINER
|
||||
portainer_api_key=args.PORTAINER_API_KEY
|
||||
portainer_ep=args.PORTAINER_EP
|
||||
gitea_api_key=args.GITEA_API_KEY
|
||||
deploy_repo=deploy_variables['DEPLOY_REPO_URL']
|
||||
deploy_webhook=deploy_variables['DEPLOY_WEBHOOK']
|
||||
deploy_ref=deploy_variables['DEPLOY_REF']
|
||||
deploy_webhook = str(uuid.uuid4())
|
||||
|
||||
### Find CICD-runner portainer environment endpointId ###
|
||||
portainer_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': portainer_api_key,
|
||||
'X-API-Key': args.PORTAINER_API_KEY
|
||||
}
|
||||
endpoint_url = f'{portainer}/api/endpoints'
|
||||
endpoint_url = f'{args.PORTAINER}/api/endpoints'
|
||||
json_endpoints = None
|
||||
try:
|
||||
response = requests.get(endpoint_url, headers=portainer_headers)
|
||||
@@ -75,14 +62,20 @@ except requests.exceptions.RequestException as err:
|
||||
|
||||
endpoint_id = None
|
||||
for endpoint in json_endpoints:
|
||||
if endpoint["Name"] == portainer_ep:
|
||||
if endpoint["Name"] == args.PORTAINER_EP:
|
||||
endpoint_id = endpoint["Id"]
|
||||
break
|
||||
if endpoint_id is None:
|
||||
raise Exception(f'Portainer endpoint \'{portainer_ep}\' not found.')
|
||||
raise Exception(f'Portainer endpoint \'{args.PORTAINER_EP}\' not found.')
|
||||
else:
|
||||
print(f'Found portainer endpoint \'{portainer_ep}\' has id: \'{endpoint_id}\'.')
|
||||
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 = {
|
||||
@@ -90,31 +83,31 @@ portainer_deploy_payload = {
|
||||
"deploy/portainer/portainer_deploy.docker-compose.yml"
|
||||
],
|
||||
"autoUpdate": {
|
||||
"webhook": f"{deploy_variables['DEPLOY_WEBHOOK']}"
|
||||
"webhook": deploy_webhook
|
||||
},
|
||||
"composeFile": "docker-compose.yml",
|
||||
"env": [
|
||||
{
|
||||
"name": "HOST",
|
||||
"value": f"{deploy_variables['DEPLOY_HOST']}"
|
||||
"value": args.DEPLOY_HOST
|
||||
},
|
||||
{
|
||||
"name": "COMPOSE_PROJECT_NAME",
|
||||
"value": f"{deploy_variables['DEPLOY_NAME']}"
|
||||
"value": args.DEPLOY_BRANCH
|
||||
}
|
||||
],
|
||||
"fromAppTemplate": False,
|
||||
"name": f"{deploy_variables['DEPLOY_NAME']}",
|
||||
"name": f"{owner}_{repo}_{args.DEPLOY_BRANCH.replace('/', '_')}".lower(),
|
||||
"repositoryAuthentication": True,
|
||||
"repositoryUsername": "cicd",
|
||||
"repositoryPassword": "gJ6@$7ZjWGyV4%i",
|
||||
"repositoryReferenceName": f"{deploy_variables['DEPLOY_REF']}",
|
||||
"repositoryURL": f"{deploy_variables['DEPLOY_REPO_URL']}",
|
||||
"repositoryReferenceName": f"refs/heads/{args.DEPLOY_BRANCH}",
|
||||
"repositoryURL": args.DEPLOY_REPO_URL,
|
||||
"tlsskipVerify": False
|
||||
}
|
||||
|
||||
### Deploy to portainer ###
|
||||
deploy_url = f'{portainer}/api/stacks/create/standalone/repository?endpointId={endpoint_id}'
|
||||
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
|
||||
@@ -124,30 +117,20 @@ except requests.exceptions.RequestException as err:
|
||||
raise Exception(f'Could not deploy portainer stack: {err}')
|
||||
|
||||
### Add Webhook to Gitea ###
|
||||
repo_url = urlparse(deploy_repo)
|
||||
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]
|
||||
|
||||
ref_parts = deploy_ref.strip('/').split('/')
|
||||
branch = ref_parts[-1]
|
||||
|
||||
webhook_payload = {
|
||||
"type": "gitea",
|
||||
"branch_filter": f"{branch}",
|
||||
"branch_filter": f"{args.DEPLOY_BRANCH}",
|
||||
"config": {
|
||||
"url": f"{portainer}/api/stacks/webhooks/{deploy_webhook}",
|
||||
"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/{owner}/{repo}/hooks'
|
||||
webhook_url = f'{gitea}/api/v1/repos/{repo_path}/hooks'
|
||||
webhook_headers = {
|
||||
"Authorization": f"token {gitea_api_key}"
|
||||
"Authorization": f"token {args.GITEA_API_KEY}"
|
||||
}
|
||||
try:
|
||||
response = requests.post(webhook_url, headers=webhook_headers, json=webhook_payload)
|
||||
|
148
deploy/portainer/undeploy.py
Normal file
148
deploy/portainer/undeploy.py
Normal file
@@ -0,0 +1,148 @@
|
||||
#!/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'
|
||||
}
|
||||
|
||||
# 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_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': args.PORTAINER_API_KEY
|
||||
}
|
||||
endpoint_url = f'{args.PORTAINER}/api/endpoints'
|
||||
try:
|
||||
endpoint_response = requests.get(endpoint_url, headers=portainer_headers)
|
||||
endpoint_response.raise_for_status() # Raise HTTPError for bad requests
|
||||
json_endpoints = endpoint_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}\'.')
|
||||
|
||||
# ?filters=\{'EndpointId':'{endpoint_id}'\}
|
||||
stacks_url = f"{args.PORTAINER}/api/stacks"
|
||||
try:
|
||||
stacks_response = requests.get(stacks_url, headers=portainer_headers)
|
||||
stacks_response.raise_for_status() # Raise HTTPError for bad requests
|
||||
json_stacks = stacks_response.json()
|
||||
|
||||
except requests.exceptions.RequestException as err:
|
||||
raise Exception(f'Could not retrieve portainer stacks: {err}')
|
||||
|
||||
stack_id = None
|
||||
stack_webhook = None
|
||||
for stack in json_stacks:
|
||||
if (
|
||||
stack['GitConfig'] is not None
|
||||
and stack['GitConfig']['URL'] == args.DEPLOY_REPO_URL
|
||||
and stack['GitConfig']['ReferenceName'] == f"refs/heads/{args.DEPLOY_BRANCH}"
|
||||
):
|
||||
stack_id = stack["Id"]
|
||||
stack_webhook = stack["AutoUpdate"]["Webhook"]
|
||||
break;
|
||||
|
||||
if stack_id is None or stack_webhook is None:
|
||||
raise Exception(f"Portainer stack with url:'{args.DEPLOY_REPO_URL}' and branch:'{args.DEPLOY_BRANCH}' not found.")
|
||||
else:
|
||||
print(f"Found portainer stack to remove with url:'{args.DEPLOY_REPO_URL}' and branch:'{args.DEPLOY_BRANCH}', stack has id: '{stack_id}' and webhook: '{stack_webhook}'")
|
||||
|
||||
### Find correct webhook in gitea
|
||||
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]
|
||||
|
||||
gitea_headers = {
|
||||
"Authorization": f"token {args.GITEA_API_KEY}"
|
||||
}
|
||||
|
||||
webhook_url = f'{gitea}/api/v1/repos/{repo_path}/hooks'
|
||||
try:
|
||||
#TODO: Webhooks are returned paginated, this only checks first page
|
||||
get_webhooks_response = requests.get(webhook_url, headers=gitea_headers)
|
||||
get_webhooks_response.raise_for_status() # Raise HTTPError for bad requests
|
||||
json_webhooks = get_webhooks_response.json()
|
||||
|
||||
except requests.exceptions.RequestException as err:
|
||||
raise Exception(f'Could not get webhooks from Gitea: {err}')
|
||||
|
||||
webhook_id = None
|
||||
for webhook in json_webhooks:
|
||||
if webhook["config"]["url"] == f"{args.PORTAINER}/api/stacks/webhooks/{stack_webhook}":
|
||||
webhook_id = webhook["id"]
|
||||
break
|
||||
|
||||
if webhook_id is None:
|
||||
raise Exception(f"Gitea webhook pointing to Portainer webhook '{stack_webhook}' not found.")
|
||||
else:
|
||||
print(f"Found Gitea webhook pointing to Portainer webhook '{stack_webhook}\' has id: '{webhook_id}'.")
|
||||
|
||||
### Remove Webhook from Gitea ###
|
||||
remove_webhook_url = f"{gitea}/api/v1/repos/{repo_path}/hooks/{webhook_id}"
|
||||
try:
|
||||
#TODO: Webhooks are returned paginated, this only checks first page
|
||||
del_webhooks_response = requests.delete(remove_webhook_url, headers=gitea_headers)
|
||||
del_webhooks_response.raise_for_status() # Raise HTTPError for bad requests
|
||||
|
||||
except requests.exceptions.RequestException as err:
|
||||
raise Exception(f"Could not delete webhook '{webhook_id}' from Gitea: {err}")
|
||||
|
||||
## Remove Stack from Portainer ###
|
||||
remove_stack_url = f"{args.PORTAINER}/api/stacks/{stack_id}?endpointId={endpoint_id}"
|
||||
try:
|
||||
#TODO: Webhooks are returned paginated, this only checks first page
|
||||
del_stack_response = requests.delete(remove_stack_url, headers=portainer_headers)
|
||||
del_stack_response.raise_for_status() # Raise HTTPError for bad requests
|
||||
|
||||
except requests.exceptions.RequestException as err:
|
||||
raise Exception(f"Could not delete stack '{stack_id}' from Portainer: {err}")
|
||||
|
||||
print(f'Successfully undeployed project')
|
@@ -1,10 +1,12 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
http-echo:
|
||||
image: mendhak/http-https-echo:latest
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
- VIRTUAL_PORT=8080
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
http-echo:
|
||||
build:
|
||||
content: ./
|
||||
dockerfile: httpecho.Dockerfile
|
||||
expose:
|
||||
- 8080
|
||||
environment:
|
||||
- VIRTUAL_PORT=8080
|
||||
- VIRTUAL_HOST=${HOST}
|
2
httpecho.Dockerfile
Normal file
2
httpecho.Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM mendhak/http-https-echo:latest
|
||||
RUN echo "Hello"
|
Reference in New Issue
Block a user