Initial commit of reusable Portainer action
This commit is contained in:
45
README.md
Normal file
45
README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Portainer Stack Deploy Action
|
||||
|
||||
A reusable Gitea/GitHub action to automatically create or update a Docker Compose Stack in Portainer using the Portainer API.
|
||||
|
||||
This action is idempotent: if the stack doesn't exist, it creates it. If it exists, it updates it and pulls the latest images.
|
||||
|
||||
## Usage
|
||||
|
||||
In your repository, create your `.gitea/workflows/deploy.yaml` and reference this action:
|
||||
|
||||
```yaml
|
||||
name: Deploy to Portainer
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# ... (Your Docker Build and Push steps here) ...
|
||||
|
||||
- name: Deploy Stack to Portainer
|
||||
uses: kaykayyali/portainer-deploy-action@main
|
||||
with:
|
||||
portainer_url: 'http://portainer.home.local:9000' # Your Portainer URL
|
||||
portainer_token: ${{ secrets.PORTAINER_API_TOKEN }}
|
||||
stack_name: 'my-awesome-app'
|
||||
compose_file: 'docker-compose.yml' # Path to your compose file in the repo
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Required | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `portainer_url` | Yes | | The full URL to your Portainer instance API |
|
||||
| `portainer_token` | Yes | | Your Portainer API Token (Access Token) |
|
||||
| `stack_name` | Yes | | The name you want to give the stack in Portainer |
|
||||
| `endpoint_id` | No | `1` | The Portainer Environment ID (Usually 1 for local) |
|
||||
| `compose_file` | No | `docker-compose.yml` | The relative path to your docker-compose file |
|
||||
33
action.yml
Normal file
33
action.yml
Normal file
@@ -0,0 +1,33 @@
|
||||
name: 'Portainer Stack Deploy'
|
||||
description: 'Deploys or updates a Docker Compose stack via the Portainer API'
|
||||
inputs:
|
||||
portainer_url:
|
||||
description: 'URL to Portainer instance (e.g., http://portainer:9000)'
|
||||
required: true
|
||||
portainer_token:
|
||||
description: 'Portainer API Token'
|
||||
required: true
|
||||
endpoint_id:
|
||||
description: 'Portainer Environment/Endpoint ID (default: 1)'
|
||||
required: false
|
||||
default: '1'
|
||||
stack_name:
|
||||
description: 'Name of the stack to create or update'
|
||||
required: true
|
||||
compose_file:
|
||||
description: 'Path to docker-compose.yml file'
|
||||
required: false
|
||||
default: 'docker-compose.yml'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Deploy to Portainer
|
||||
shell: bash
|
||||
env:
|
||||
PORTAINER_URL: ${{ inputs.portainer_url }}
|
||||
PORTAINER_TOKEN: ${{ inputs.portainer_token }}
|
||||
ENDPOINT_ID: ${{ inputs.endpoint_id }}
|
||||
STACK_NAME: ${{ inputs.stack_name }}
|
||||
COMPOSE_FILE: ${{ inputs.compose_file }}
|
||||
run: ${{ github.action_path }}/deploy.sh
|
||||
84
deploy.sh
Executable file
84
deploy.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Ensure inputs are present
|
||||
if [ -z "$PORTAINER_URL" ] || [ -z "$PORTAINER_TOKEN" ] || [ -z "$STACK_NAME" ]; then
|
||||
echo "Error: PORTAINER_URL, PORTAINER_TOKEN, and STACK_NAME must be set."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$COMPOSE_FILE" ]; then
|
||||
echo "Error: Compose file $COMPOSE_FILE not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deploying Stack: $STACK_NAME"
|
||||
echo "Portainer URL: $PORTAINER_URL"
|
||||
echo "Endpoint ID: $ENDPOINT_ID"
|
||||
|
||||
# 1. Check if stack exists
|
||||
echo "Checking if stack exists..."
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" -H "X-API-Key: $PORTAINER_TOKEN" "$PORTAINER_URL/api/stacks")
|
||||
HTTP_STATUS=$(tail -n1 <<< "$RESPONSE")
|
||||
BODY=$(sed '$ d' <<< "$RESPONSE")
|
||||
|
||||
if [ "$HTTP_STATUS" -ne 200 ]; then
|
||||
echo "Failed to fetch stacks. HTTP Status: $HTTP_STATUS"
|
||||
echo "$BODY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Try to extract the ID for the matching stack name
|
||||
STACK_ID=$(echo "$BODY" | jq -r ".[] | select(.Name == \"$STACK_NAME\") | .Id")
|
||||
|
||||
COMPOSE_CONTENT=$(cat "$COMPOSE_FILE")
|
||||
# Escape content for JSON payload
|
||||
COMPOSE_JSON=$(jq -Rs . <<< "$COMPOSE_CONTENT")
|
||||
|
||||
if [ -n "$STACK_ID" ] && [ "$STACK_ID" != "null" ]; then
|
||||
echo "Stack '$STACK_NAME' exists with ID: $STACK_ID. Updating..."
|
||||
|
||||
# PUT to update stack
|
||||
# pullImage=true ensures it pulls the latest image if tag is the same
|
||||
PAYLOAD="{\"stackFileContent\": $COMPOSE_JSON, \"env\": [], \"prune\": true, \"pullImage\": true}"
|
||||
|
||||
UPDATE_RES=$(curl -s -w "\n%{http_code}" -X PUT \
|
||||
-H "X-API-Key: $PORTAINER_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD" \
|
||||
"$PORTAINER_URL/api/stacks/$STACK_ID?endpointId=$ENDPOINT_ID")
|
||||
|
||||
U_STATUS=$(tail -n1 <<< "$UPDATE_RES")
|
||||
U_BODY=$(sed '$ d' <<< "$UPDATE_RES")
|
||||
|
||||
if [ "$U_STATUS" -eq 200 ]; then
|
||||
echo "Stack successfully updated!"
|
||||
else
|
||||
echo "Failed to update stack. HTTP $U_STATUS"
|
||||
echo "$U_BODY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
else
|
||||
echo "Stack '$STACK_NAME' does not exist. Creating..."
|
||||
|
||||
# POST to create stack
|
||||
PAYLOAD="{\"name\": \"$STACK_NAME\", \"stackFileContent\": $COMPOSE_JSON, \"env\": []}"
|
||||
|
||||
CREATE_RES=$(curl -s -w "\n%{http_code}" -X POST \
|
||||
-H "X-API-Key: $PORTAINER_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$PAYLOAD" \
|
||||
"$PORTAINER_URL/api/stacks/create/standalone/string?endpointId=$ENDPOINT_ID")
|
||||
|
||||
C_STATUS=$(tail -n1 <<< "$CREATE_RES")
|
||||
C_BODY=$(sed '$ d' <<< "$CREATE_RES")
|
||||
|
||||
if [ "$C_STATUS" -eq 200 ]; then
|
||||
echo "Stack successfully created!"
|
||||
else
|
||||
echo "Failed to create stack. HTTP $C_STATUS"
|
||||
echo "$C_BODY"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
Reference in New Issue
Block a user