name: Deploy
on:
workflow_run:
workflows: ["Build"]
types: [completed]
branches:
- master
- main
- 'release/**'
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
REGISTRY: registry.local.zakuro-ai.com
IMAGE: registry.local.zakuro-ai.com/zakuroai/zakuro-broker
FLEET_MANIFEST: k8s/zc-broker-fleet-staging/statefulset.yaml
permissions:
contents: read
jobs:
deploy-staging:
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
runs-on: cpu
timeout-minutes: 45
concurrency:
group: zc-deploy-staging
cancel-in-progress: false
env:
DEPLOY_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
DEPLOY_REF: ${{ github.event.workflow_run.head_branch || github.ref_name }}
steps:
- name: Check secrets
env:
DEPLOY_PAT: ${{ secrets.DEPLOY_PAT }}
DRIVE_DEPLOY_KEY: ${{ secrets.DRIVE_DEPLOY_KEY }}
run: |
set -euo pipefail
# Fail loudly on a missing secret rather than deploying nothing. An
# unset DEPLOY_PAT once made this job report successful deploys
# that never happened.
missing=0
for name in DEPLOY_PAT DRIVE_DEPLOY_KEY; do
if [ -z "${!name:-}" ]; then
echo "::error::${name} is not set on zakuro-ai/zc — cannot deploy staging."
missing=1
fi
done
[ "$missing" = 0 ]
echo "TAG=${DEPLOY_SHA::7}" >> "$GITHUB_ENV"
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 with:
ref: ${{ env.DEPLOY_SHA }}
persist-credentials: false
- name: Start ssh-agent with the drive deploy key
env:
DRIVE_DEPLOY_KEY: ${{ secrets.DRIVE_DEPLOY_KEY }}
run: |
eval "$(ssh-agent -s)"
echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> "$GITHUB_ENV"
echo "SSH_AGENT_PID=$SSH_AGENT_PID" >> "$GITHUB_ENV"
printf '%s\n' "$DRIVE_DEPLOY_KEY" | ssh-add -
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
- name: Build and push the broker image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with:
context: .
file: docker/Dockerfile
platforms: linux/amd64
ssh: default=${{ env.SSH_AUTH_SOCK }}
tags: ${{ env.IMAGE }}:${{ env.TAG }}
push: true
cache-from: type=gha,scope=linux/amd64
- name: Verify the image is in the registry
run: |
set -euo pipefail
code=$(curl -sS -o /dev/null -w '%{http_code}' \
-H 'Accept: application/vnd.oci.image.index.v1+json' \
-H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
-H 'Accept: application/vnd.oci.image.manifest.v1+json' \
-H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
"https://${REGISTRY}/v2/zakuroai/zakuro-broker/manifests/${TAG}")
if [ "$code" != "200" ]; then
echo "::error::${IMAGE}:${TAG} is not in the registry (HTTP $code) — staging was NOT deployed."
exit 1
fi
echo "${IMAGE}:${TAG} is in the registry."
- name: Promote the image in zakuro-infra
env:
DEPLOY_PAT: ${{ secrets.DEPLOY_PAT }}
run: |
set -euo pipefail
INFRA_DIR="$(mktemp -d)/zakuro-infra"
git clone --depth=1 \
"https://x-access-token:${DEPLOY_PAT}@github.com/zakuro-ai/zakuro-infra.git" \
"$INFRA_DIR"
cd "$INFRA_DIR"
# Require exactly one broker image line. Anything else means the
# manifest changed shape, and a blind sed would either do nothing
# or rewrite the wrong container.
OLD=$(sed -n -E "s|^[[:space:]]*image: ${IMAGE}:([^[:space:]]+).*|\1|p" "$FLEET_MANIFEST")
if [ "$(printf '%s\n' "$OLD" | grep -c .)" != "1" ]; then
echo "::error::expected exactly one '${IMAGE}:<tag>' line in ${FLEET_MANIFEST}, found: '${OLD}'"
exit 1
fi
if [ "$OLD" = "$TAG" ]; then
echo "The fleet is already on ${TAG}; nothing to promote."
exit 0
fi
sed -i -E "s|^([[:space:]]*image: ${IMAGE}:)[^[:space:]]+|\1${TAG}|" "$FLEET_MANIFEST"
grep -n "image: ${IMAGE}:" "$FLEET_MANIFEST"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "$FLEET_MANIFEST"
git commit \
-m "deploy(staging): broker image ${OLD} -> ${TAG}" \
-m "Source: zakuro-ai/zc@${TAG} on ${DEPLOY_REF}. ArgoCD app zc-broker-fleet-staging auto-syncs."
pushed=false
for attempt in 1 2 3 4 5; do
if git push origin HEAD:main; then pushed=true; break; fi
echo "push rejected, rebasing (attempt $attempt)"
git pull --rebase origin main
sleep 3
done
if [ "$pushed" != true ]; then
echo "::error::could not push the tag bump to zakuro-infra — staging was NOT deployed."
exit 1
fi
# Check the effect, not the exit code: the bump must be on main.
git fetch -q origin main
if ! git merge-base --is-ancestor HEAD origin/main; then
echo "::error::the bump commit $(git rev-parse --short HEAD) is not on zakuro-infra main — staging was NOT deployed."
exit 1
fi
echo "Promoted ${IMAGE}:${TAG} in zakuro-infra ($(git rev-parse --short HEAD)); ArgoCD app zc-broker-fleet-staging syncs it."