trips) portainer API deploy + preflight checks (#5)
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m25s
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 2m25s
- Switch CI deploy step from Portainer webhook to Portainer API (CE-compatible) - Add manual workflow trigger (`workflow_dispatch`) - Add preflight checks for Portainer auth, endpoint ID, and stack ID before redeploy - Update README with required secrets and deploy flow Reviewed-on: #5 Co-authored-by: Shaun Campbell <shaun@campbellwireless.net> Co-committed-by: Shaun Campbell <shaun@campbellwireless.net>
This commit was merged in pull request #5.
This commit is contained in:
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY_HOST: registry.campbellwireless.net
|
||||
@@ -50,3 +51,107 @@ jobs:
|
||||
tags: |
|
||||
${{ env.REGISTRY_HOST }}/${{ env.IMAGE_NAME }}:latest
|
||||
${{ env.REGISTRY_HOST }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.sha_short }}
|
||||
|
||||
- name: Preflight Portainer deploy config
|
||||
env:
|
||||
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
|
||||
PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }}
|
||||
PORTAINER_STACK_ID: ${{ secrets.PORTAINER_STACK_ID }}
|
||||
PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }}
|
||||
PORTAINER_INSECURE_TLS: ${{ secrets.PORTAINER_INSECURE_TLS }}
|
||||
run: |
|
||||
set -eu
|
||||
for required in PORTAINER_URL PORTAINER_API_KEY PORTAINER_STACK_ID PORTAINER_ENDPOINT_ID; do
|
||||
if [ -z "$(eval "printf '%s' \"\${$required:-}\"")" ]; then
|
||||
echo "Missing required secret: $required" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
CURL_ARGS=(--fail --show-error --silent --retry 3 --retry-all-errors)
|
||||
if [ "${PORTAINER_INSECURE_TLS:-false}" = "true" ]; then
|
||||
CURL_ARGS+=(--insecure)
|
||||
fi
|
||||
|
||||
API_BASE="${PORTAINER_URL%/}/api"
|
||||
|
||||
ENDPOINT_JSON="$(
|
||||
curl "${CURL_ARGS[@]}" \
|
||||
--header "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||
"${API_BASE}/endpoints/${PORTAINER_ENDPOINT_ID}"
|
||||
)"
|
||||
echo "${ENDPOINT_JSON}" | jq -e --arg id "${PORTAINER_ENDPOINT_ID}" \
|
||||
'((.Id // .id) | tostring) == $id' >/dev/null
|
||||
|
||||
STACK_JSON="$(
|
||||
curl "${CURL_ARGS[@]}" \
|
||||
--header "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||
"${API_BASE}/stacks/${PORTAINER_STACK_ID}?endpointId=${PORTAINER_ENDPOINT_ID}"
|
||||
)"
|
||||
echo "${STACK_JSON}" | jq -e --arg id "${PORTAINER_STACK_ID}" \
|
||||
'((.Id // .id) | tostring) == $id' >/dev/null
|
||||
|
||||
echo "Portainer preflight checks passed."
|
||||
|
||||
- name: Trigger Portainer stack redeploy
|
||||
env:
|
||||
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
|
||||
PORTAINER_API_KEY: ${{ secrets.PORTAINER_API_KEY }}
|
||||
PORTAINER_STACK_ID: ${{ secrets.PORTAINER_STACK_ID }}
|
||||
PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }}
|
||||
PORTAINER_INSECURE_TLS: ${{ secrets.PORTAINER_INSECURE_TLS }}
|
||||
run: |
|
||||
set -eu
|
||||
for required in PORTAINER_URL PORTAINER_API_KEY PORTAINER_STACK_ID PORTAINER_ENDPOINT_ID; do
|
||||
if [ -z "$(eval "printf '%s' \"\${$required:-}\"")" ]; then
|
||||
echo "Missing required secret: $required" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
CURL_ARGS=(--fail --show-error --silent --retry 3 --retry-all-errors)
|
||||
if [ "${PORTAINER_INSECURE_TLS:-false}" = "true" ]; then
|
||||
CURL_ARGS+=(--insecure)
|
||||
fi
|
||||
|
||||
STACK_BASE="${PORTAINER_URL%/}/api/stacks/${PORTAINER_STACK_ID}"
|
||||
QUERY="endpointId=${PORTAINER_ENDPOINT_ID}"
|
||||
|
||||
# Git-based stacks can be redeployed directly.
|
||||
if curl "${CURL_ARGS[@]}" \
|
||||
--header "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||
--request POST \
|
||||
"${STACK_BASE}/git/redeploy?${QUERY}" >/dev/null; then
|
||||
echo "Portainer deploy: git stack redeploy triggered."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Non-git stacks: fetch current stack file and redeploy with pullImage=true.
|
||||
STACK_FILE_CONTENT="$(
|
||||
curl "${CURL_ARGS[@]}" \
|
||||
--header "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||
"${STACK_BASE}/file" \
|
||||
| jq -r '.StackFileContent'
|
||||
)"
|
||||
|
||||
PAYLOAD_LOWER="$(jq -cn --arg stackFileContent "${STACK_FILE_CONTENT}" \
|
||||
'{stackFileContent: $stackFileContent, prune: false, pullImage: true}')"
|
||||
if curl "${CURL_ARGS[@]}" \
|
||||
--header "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||
--header "Content-Type: application/json" \
|
||||
--request PUT \
|
||||
--data "${PAYLOAD_LOWER}" \
|
||||
"${STACK_BASE}?${QUERY}" >/dev/null; then
|
||||
echo "Portainer deploy: stack updated with lower-camel payload."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PAYLOAD_UPPER="$(jq -cn --arg StackFileContent "${STACK_FILE_CONTENT}" \
|
||||
'{StackFileContent: $StackFileContent, Prune: false, PullImage: true}')"
|
||||
curl "${CURL_ARGS[@]}" \
|
||||
--header "X-API-Key: ${PORTAINER_API_KEY}" \
|
||||
--header "Content-Type: application/json" \
|
||||
--request PUT \
|
||||
--data "${PAYLOAD_UPPER}" \
|
||||
"${STACK_BASE}?${QUERY}" >/dev/null
|
||||
echo "Portainer deploy: stack updated with upper-camel payload."
|
||||
|
||||
Reference in New Issue
Block a user