Add GHA build + Woodpecker deploy pipelines

Context
-------
Matches the pattern used by claude-memory-mcp (infra CLAUDE.md §CI/CD).
GHA is cheap and parallel — build+push happens there. Woodpecker runs
in-cluster and has kubectl access, so it owns the `kubectl set image`
step.

This change
-----------
- `.github/workflows/ci.yml` — push to main runs ruff + mypy strict +
  pytest, then builds `viktorbarzin/broker-sync:<8-char-sha>` +
  `:latest` and pushes to DockerHub, then triggers Woodpecker.
- `.woodpecker/deploy.yml` — kubectl set image on all 5 CronJobs in
  the broker-sync namespace (version probe + 4 sync jobs), then
  spawns a one-shot Job from the version CronJob as a smoke test
  and waits for completion.
- Woodpecker repo ID is `TBD` — needs filling in once the repo is
  registered with Woodpecker (see infra CLAUDE.md Repo IDs list).
  The workflow skips deploy cleanly if still TBD, so this doesn't
  block green builds.

Test plan
---------
## Automated
Nothing to run locally — CI is verified by pushing and watching the
run on GitHub.

## Manual Verification
1. Push this branch to GitHub, confirm `test` job runs and passes.
2. Push to `main`, confirm `build` job produces
   `viktorbarzin/broker-sync:<sha>` on DockerHub.
3. Register repo with Woodpecker, note the numeric repo ID, replace
   `TBD` in ci.yml, push again.
4. Confirm Woodpecker deploy pipeline runs `kubectl set image` on
   all 5 CronJobs and the smoke-test job returns `broker-sync 0.1.0`.
This commit is contained in:
Viktor Barzin 2026-04-17 19:32:00 +00:00
parent 43d2251159
commit 18d8241c85
2 changed files with 115 additions and 0 deletions

85
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,85 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
IMAGE_NAME: broker-sync
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Poetry
run: pipx install poetry==1.8.4
- name: Install deps
run: poetry install --no-interaction
- run: poetry run ruff check .
- run: poetry run mypy broker_sync tests
- run: poetry run pytest -q
build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
outputs:
image_tag: ${{ steps.meta.outputs.sha }}
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- id: meta
run: echo "sha=$(echo ${{ github.sha }} | cut -c1-8)" >> $GITHUB_OUTPUT
- uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: true
platforms: linux/amd64
tags: |
viktorbarzin/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.sha }}
viktorbarzin/${{ env.IMAGE_NAME }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Trigger Woodpecker deploy
env:
# TODO: replace WOODPECKER_REPO_ID with the numeric id assigned when the
# broker-sync repo is registered with Woodpecker. See infra CLAUDE.md
# "Woodpecker API uses numeric repo IDs".
WOODPECKER_REPO_ID: "TBD"
run: |
if [ "$WOODPECKER_REPO_ID" = "TBD" ]; then
echo "Woodpecker repo not yet registered — skipping deploy trigger."
exit 0
fi
for attempt in 1 2 3; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
"https://ci.viktorbarzin.me/api/repos/${WOODPECKER_REPO_ID}/pipelines" \
-H "Authorization: Bearer ${{ secrets.WOODPECKER_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"branch":"main","variables":{"IMAGE_TAG":"${{ needs.build.outputs.image_tag }}"}}')
if [ "$STATUS" -ge 200 ] && [ "$STATUS" -lt 300 ]; then
echo "Woodpecker deploy triggered (HTTP $STATUS)"
exit 0
fi
echo "Attempt $attempt failed (HTTP $STATUS), retrying in 30s..."
sleep 30
done
echo "Failed to trigger Woodpecker deploy after 3 attempts"
exit 1