ci: move image build off-infra to GHA -> ghcr (ADR-0002)
Some checks are pending
Some checks are pending
Generated by infra/scripts/offinfra-onboard: GHA builds+tests on the GitHub mirror, pushes ghcr.io/viktorbarzin/fire-planner, then triggers the Woodpecker deploy (repo 208). Old in-cluster build pipeline removed: .woodpecker.yml Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
25c948c933
commit
6af612c06f
3 changed files with 146 additions and 48 deletions
126
.github/workflows/build.yml
vendored
Normal file
126
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
name: Build and Push
|
||||
|
||||
# Off-infra build (ADR-0002). Canonical repo is Forgejo viktor/fire-planner, which
|
||||
# push-mirrors here; this workflow builds on GitHub-hosted runners, pushes the
|
||||
# image to GHCR, then signals the Woodpecker deploy pipeline (repo 208)
|
||||
# to roll the cluster — the homelab never sees build IO or registry pushes.
|
||||
#
|
||||
# Committed on the FORGEJO side (the mirror is one-way; commits made on GitHub
|
||||
# are overwritten by the next sync). Generated by infra/scripts/offinfra-onboard.
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
# Mirrors the old .woodpecker.yml lint-and-test step (python:3.12-slim,
|
||||
# poetry 1.8.4, ruff + mypy + pytest).
|
||||
- name: Lint + type-check + test
|
||||
run: |
|
||||
pip install --no-cache-dir "poetry==1.8.4"
|
||||
poetry install --no-interaction --no-root
|
||||
poetry run ruff check .
|
||||
poetry run mypy fire_planner tests
|
||||
poetry run pytest -q
|
||||
|
||||
build:
|
||||
needs: lint-and-test
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
image_tag: ${{ steps.meta.outputs.sha }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0 # full history + tags so svu sees the last vX.Y.Z
|
||||
fetch-tags: true
|
||||
# Auto-semver (svu): tag-only, pushed to CANONICAL Forgejo (GitHub tags
|
||||
# would be wiped by the next mirror sync). Best-effort: never blocks the build.
|
||||
- name: Compute + tag semver (svu)
|
||||
env:
|
||||
FORGEJO_GIT_TOKEN: ${{ secrets.FORGEJO_GIT_TOKEN }}
|
||||
run: |
|
||||
set +e
|
||||
git config user.email "ci@viktorbarzin.me"
|
||||
git config user.name "fire-planner-ci"
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
curl -sSL https://github.com/caarlos0/svu/releases/download/v3.4.1/svu_3.4.1_linux_amd64.tar.gz | tar -xz svu
|
||||
CUR=$(./svu current 2>/dev/null)
|
||||
NEXT=$(./svu next 2>/dev/null)
|
||||
echo "svu current=[$CUR] next=[$NEXT]"
|
||||
if [ -n "$NEXT" ] && [ "$NEXT" != "$CUR" ]; then
|
||||
git tag "$NEXT" 2>/dev/null
|
||||
git push "https://viktor:${FORGEJO_GIT_TOKEN}@forgejo.viktorbarzin.me/viktor/fire-planner.git" "$NEXT" && echo "pushed tag $NEXT to forgejo" || echo "tag push failed (non-blocking)"
|
||||
fi
|
||||
exit 0
|
||||
- uses: docker/setup-buildx-action@v4
|
||||
- uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- id: meta
|
||||
run: echo "sha=$(echo ${{ github.sha }} | cut -c1-8)" >> "$GITHUB_OUTPUT"
|
||||
- uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64
|
||||
# Single-manifest images (no provenance/SBOM attestation children) so
|
||||
# registry retention can never orphan index children (ADR-0002).
|
||||
provenance: false
|
||||
tags: |
|
||||
ghcr.io/viktorbarzin/fire-planner:${{ steps.meta.outputs.sha }}
|
||||
ghcr.io/viktorbarzin/fire-planner:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
# Keep the newest ~10 versions on ghcr (latest rides the newest one).
|
||||
- name: ghcr retention (keep 10)
|
||||
uses: actions/delete-package-versions@v5
|
||||
continue-on-error: true
|
||||
with:
|
||||
package-name: fire-planner
|
||||
package-type: container
|
||||
min-versions-to-keep: 10
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Signal Woodpecker (repo 208 = ViktorBarzin/fire-planner mirror) to run
|
||||
# .woodpecker/deploy.yml — kubectl set image in-cluster (agent SA is cluster-admin).
|
||||
- name: Trigger Woodpecker deploy
|
||||
run: |
|
||||
for attempt in 1 2 3; do
|
||||
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||
"https://ci.viktorbarzin.me/api/repos/208/pipelines" \
|
||||
-H "Authorization: Bearer ${{ secrets.WOODPECKER_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"branch\":\"master\",\"variables\":{\"IMAGE_TAG\":\"${{ needs.build.outputs.image_tag }}\",\"IMAGE_NAME\":\"ghcr.io/viktorbarzin/fire-planner\"}}")
|
||||
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
|
||||
|
||||
notify-failure:
|
||||
needs: [lint-and-test, build, deploy]
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack notify
|
||||
run: |
|
||||
curl -sf -X POST -H 'Content-Type: application/json' \
|
||||
-d "{\"text\":\":rotating_light: fire-planner off-infra build FAILED: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"}" \
|
||||
"${{ secrets.SLACK_WEBHOOK }}" || true
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
when:
|
||||
event: push
|
||||
|
||||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
attempts: 5
|
||||
backoff: 10s
|
||||
|
||||
steps:
|
||||
- name: build-and-push
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
# Phase 4 of forgejo-registry-consolidation 2026-05-07 — Forgejo only.
|
||||
repo:
|
||||
- forgejo.viktorbarzin.me/viktor/fire-planner
|
||||
logins:
|
||||
- registry: forgejo.viktorbarzin.me
|
||||
username:
|
||||
from_secret: forgejo_user
|
||||
password:
|
||||
from_secret: forgejo_push_token
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
platforms:
|
||||
- linux/amd64
|
||||
tags:
|
||||
- "latest"
|
||||
- "${CI_COMMIT_SHA:0:8}"
|
||||
|
||||
# We build the image, so we drive the rollout too — atomic + deterministic,
|
||||
# no wait for Keel's hourly poll and no risk of Keel resolving :latest to a
|
||||
# stale concrete tag. Keel stays enrolled in parallel as a redundant net
|
||||
# (it finds the SHA already running → no-op). set image on a NEW :SHA always
|
||||
# changes the pod template → guaranteed rollout, and bootstraps off any
|
||||
# legacy pinned tag with no manual step. Sets the alembic-migrate init
|
||||
# container too — it shares the image so its tag must move in lockstep.
|
||||
- name: deploy
|
||||
image: bitnami/kubectl:latest
|
||||
depends_on:
|
||||
- build-and-push
|
||||
when:
|
||||
branch: master
|
||||
event: [push, manual]
|
||||
commands:
|
||||
- "kubectl set image deployment/fire-planner fire-planner=forgejo.viktorbarzin.me/viktor/fire-planner:${CI_COMMIT_SHA:0:8} alembic-migrate=forgejo.viktorbarzin.me/viktor/fire-planner:${CI_COMMIT_SHA:0:8} -n fire-planner"
|
||||
- "kubectl rollout status deployment/fire-planner -n fire-planner --timeout=300s"
|
||||
20
.woodpecker/deploy.yml
Normal file
20
.woodpecker/deploy.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Auto-deploy, triggered ONLY by the GitHub Actions build POSTing to the
|
||||
# Woodpecker API (manual event, with IMAGE_TAG + IMAGE_NAME) after a successful
|
||||
# off-infra build+push to GHCR (ADR-0002). event:[manual] (NOT push) so the
|
||||
# Forgejo->GitHub mirror's raw pushes don't fire a spurious deploy.
|
||||
# The woodpecker-agent SA is cluster-admin — no kubeconfig needed.
|
||||
# Generated by infra/scripts/offinfra-onboard.
|
||||
when:
|
||||
- event: manual
|
||||
|
||||
steps:
|
||||
- name: check-vars
|
||||
image: alpine
|
||||
commands:
|
||||
- "[ -n \"$IMAGE_TAG\" ] || (echo 'IMAGE_TAG not set — refusing to deploy'; exit 1)"
|
||||
|
||||
- name: deploy
|
||||
image: bitnami/kubectl:latest
|
||||
commands:
|
||||
- "kubectl -n fire-planner set image deployment/fire-planner fire-planner=${IMAGE_NAME}:${IMAGE_TAG} alembic-migrate=${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
- "kubectl -n fire-planner rollout status deployment/fire-planner --timeout=300s"
|
||||
Loading…
Add table
Add a link
Reference in a new issue