feat(action): post /update to every endpoint in a fleet, continuing past failures
This commit is contained in:
+15
-3
@@ -14,7 +14,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: gitea.example.com/shcizo/package-updater/gitea-action@v1
|
- uses: gitea.example.com/shcizo/package-updater/gitea-action@v1
|
||||||
with:
|
with:
|
||||||
endpoint: https://updater.example.com/update
|
endpoint: |
|
||||||
|
https://updater-swarm.example.com/update
|
||||||
|
https://updater-web01.example.com/update
|
||||||
|
https://updater-web02.example.com/update
|
||||||
image: registry.example.com/${{ gitea.repository }}
|
image: registry.example.com/${{ gitea.repository }}
|
||||||
tag: ${{ gitea.sha }}
|
tag: ${{ gitea.sha }}
|
||||||
token: ${{ secrets.UPDATER_TOKEN }}
|
token: ${{ secrets.UPDATER_TOKEN }}
|
||||||
@@ -22,15 +25,24 @@ jobs:
|
|||||||
|
|
||||||
`UPDATER_TOKEN` should be set as an organisation-level secret in Gitea so all repos share it.
|
`UPDATER_TOKEN` should be set as an organisation-level secret in Gitea so all repos share it.
|
||||||
|
|
||||||
|
All instances in the fleet must share the same bearer token, since one `token`
|
||||||
|
input is sent to every endpoint.
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
| Name | Required | Default | Description |
|
| Name | Required | Default | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `endpoint` | yes | — | Full URL to `/update` |
|
| `endpoint` | yes | — | Full URL to `/update`. Several may be given, one per line, to update a fleet. |
|
||||||
| `image` | yes | — | Image reference without tag |
|
| `image` | yes | — | Image reference without tag |
|
||||||
| `tag` | no | `""` | Tag that was just pushed (logged for audit) |
|
| `tag` | no | `""` | Tag that was just pushed (logged for audit) |
|
||||||
| `token` | yes | — | Bearer token configured in package-updater |
|
| `token` | yes | — | Bearer token configured in package-updater |
|
||||||
|
|
||||||
## Failure modes
|
## Failure modes
|
||||||
|
|
||||||
The step exits non-zero if `package-updater` returns HTTP 4xx or 5xx. This is intentional — the workflow surfaces the deploy failure to whoever pushed.
|
Every endpoint is attempted, even when an earlier one fails — otherwise one dead
|
||||||
|
server would leave the rest of the fleet un-updated, and the CI log would not show
|
||||||
|
which hosts actually succeeded.
|
||||||
|
|
||||||
|
The step exits non-zero if any endpoint returned 4xx/5xx or was unreachable. The
|
||||||
|
log lists each endpoint with its HTTP status and response body, so a partial
|
||||||
|
deploy is visible at a glance.
|
||||||
|
|||||||
+51
-9
@@ -1,8 +1,8 @@
|
|||||||
name: "Deploy via package-updater"
|
name: "Deploy via package-updater"
|
||||||
description: "Notifies package-updater to pull & restart a Docker Compose service"
|
description: "Notifies one or more package-updater instances to pull & restart a service"
|
||||||
inputs:
|
inputs:
|
||||||
endpoint:
|
endpoint:
|
||||||
description: "Full URL to /update (e.g. https://updater.example.com/update)"
|
description: "Full URL to /update. Give several, one per line, to update a fleet."
|
||||||
required: true
|
required: true
|
||||||
image:
|
image:
|
||||||
description: "Image reference without tag (e.g. registry.example.com/myapp)"
|
description: "Image reference without tag (e.g. registry.example.com/myapp)"
|
||||||
@@ -20,18 +20,60 @@ runs:
|
|||||||
- name: Trigger update
|
- name: Trigger update
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
|
ENDPOINTS: ${{ inputs.endpoint }}
|
||||||
|
IMAGE: ${{ inputs.image }}
|
||||||
|
TAG: ${{ inputs.tag }}
|
||||||
TOKEN: ${{ inputs.token }}
|
TOKEN: ${{ inputs.token }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
# No `set -e`: a failing endpoint must not abort the loop, or one dead
|
||||||
response=$(curl -sS -w "\n%{http_code}" \
|
# server leaves the rest of the fleet un-updated and hides which hosts
|
||||||
-X POST "${{ inputs.endpoint }}" \
|
# actually succeeded.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
payload=$(jq -nc --arg image "$IMAGE" --arg tag "$TAG" \
|
||||||
|
'{image: $image, tag: $tag}')
|
||||||
|
|
||||||
|
attempted=0
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
while IFS= read -r endpoint; do
|
||||||
|
# URLs never contain whitespace, so stripping all of it safely
|
||||||
|
# handles indentation, blank lines and CRLF line endings.
|
||||||
|
endpoint=$(printf '%s' "$endpoint" | tr -d '[:space:]')
|
||||||
|
[ -z "$endpoint" ] && continue
|
||||||
|
|
||||||
|
attempted=$((attempted + 1))
|
||||||
|
echo "--- $endpoint"
|
||||||
|
|
||||||
|
if ! response=$(curl -sS -w "\n%{http_code}" \
|
||||||
|
-X POST "$endpoint" \
|
||||||
-H "Authorization: Bearer $TOKEN" \
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"image\":\"${{ inputs.image }}\",\"tag\":\"${{ inputs.tag }}\"}")
|
-d "$payload"); then
|
||||||
body=$(echo "$response" | head -n -1)
|
echo "unreachable"
|
||||||
code=$(echo "$response" | tail -n 1)
|
failed=$((failed + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split on the last newline: curl's -w appended the status code
|
||||||
|
# there. Pure bash — `head -n -1` is GNU-only and fails on
|
||||||
|
# BSD/macOS, so the fan-out could not be tested locally.
|
||||||
|
code="${response##*$'\n'}"
|
||||||
|
body="${response%$'\n'*}"
|
||||||
echo "HTTP $code"
|
echo "HTTP $code"
|
||||||
echo "$body" | jq .
|
printf '%s' "$body" | jq . || printf '%s\n' "$body"
|
||||||
|
|
||||||
if [ "$code" -ge 400 ]; then
|
if [ "$code" -ge 400 ]; then
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
done <<< "$ENDPOINTS"
|
||||||
|
|
||||||
|
if [ "$attempted" -eq 0 ]; then
|
||||||
|
echo "no endpoints given"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if [ "$failed" -gt 0 ]; then
|
||||||
|
echo "$failed of $attempted endpoint(s) failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "all $attempted endpoint(s) updated"
|
||||||
|
|||||||
Reference in New Issue
Block a user