From 84fd1c8072b08ddbd5fc2005f8fe4aa4c7035e9f Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Sat, 28 Feb 2026 23:26:00 +0100 Subject: [PATCH] feat: complete Gitea Actions CI workflow for container builds - Fix trigger/env path: Helm/ -> helm/ (was wrong case) - Add image_yq_path and dockerfile to matrix so each container uses its own values.yaml key and Dockerfile path - Fix yq paths: .django.image.* -> .frontend.image/.backend.image for repo, .containers.version for tag (single source of truth) - Add file: param to docker/build-push-action (Dockerfiles are in frontend/ and backend/, not repo root) - values.yaml: add registry prefix to image fields so k8s pulls from git.baumann.gr; quote containers.version; drop per-component tag fields (containers.version is now the single tag source) - Deployment templates: use .containers.version for image tag Co-Authored-By: Claude Sonnet 4.6 --- .../workflows/build-containers-on-demand.yml | 171 ++++++++++++++++++ .../templates/backend-deployment.yaml | 4 +- .../templates/frontend-deployment.yaml | 2 +- helm/shorefront/values.yaml | 9 +- 4 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 .gitea/workflows/build-containers-on-demand.yml diff --git a/.gitea/workflows/build-containers-on-demand.yml b/.gitea/workflows/build-containers-on-demand.yml new file mode 100644 index 0000000..c4668b5 --- /dev/null +++ b/.gitea/workflows/build-containers-on-demand.yml @@ -0,0 +1,171 @@ +name: Build containers when image tags change + +on: + push: + # Uncomment/adjust once working: + # branches: [ master ] + paths: + - "helm/shorefront/values.yaml" + - "frontend/Dockerfile" + - "backend/Dockerfile" + - ".gitea/workflows/build-containers-on-demand.yml" + +jobs: + build-if-image-changed: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - container_name: shorefront-frontend + image_yq_path: .frontend.image + expected_repo: git.baumann.gr/adebaumann/shorefront-frontend + dockerfile: frontend/Dockerfile + build_context: . + description: shorefront frontend + - container_name: shorefront-backend + image_yq_path: .backend.image + expected_repo: git.baumann.gr/adebaumann/shorefront-backend + dockerfile: backend/Dockerfile + build_context: . + description: shorefront backend + + env: + DEPLOY_FILE: "helm/shorefront/values.yaml" + CONTAINER_NAME: ${{ matrix.container_name }} + EXPECTED_REPO: ${{ matrix.expected_repo }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Determine base commit + id: base + shell: bash + run: | + set -euo pipefail + if git rev-parse --verify -q HEAD~1 >/dev/null; then + echo "base=$(git rev-parse HEAD~1)" >> "$GITHUB_OUTPUT" + else + echo "base=$(git hash-object -t tree /dev/null)" >> "$GITHUB_OUTPUT" + fi + + - name: Install yq + shell: bash + run: | + set -euo pipefail + YQ_VER=v4.44.3 + curl -sL "https://github.com/mikefarah/yq/releases/download/${YQ_VER}/yq_linux_amd64" -o /usr/local/bin/yq + chmod +x /usr/local/bin/yq + yq --version + + - name: Read ${{ matrix.description }} image from Helm values + id: img + shell: bash + run: | + set -euo pipefail + file="${DEPLOY_FILE}" + expected_repo="${EXPECTED_REPO}" + + echo "========================================" + echo "Reading image from: $file" + echo "Expected repo: $expected_repo" + echo "========================================" + + new_repo=$(yq -r '${{ matrix.image_yq_path }}' "$file") + new_tag=$(yq -r '.containers.version' "$file") + new_image="${new_repo}:${new_tag}" + + if git cat-file -e "${{ steps.base.outputs.base }}:$file" 2>/dev/null; then + old_repo=$(git show "${{ steps.base.outputs.base }}:$file" | yq -r '${{ matrix.image_yq_path }}' || true) + old_tag=$(git show "${{ steps.base.outputs.base }}:$file" | yq -r '.containers.version' || true) + old_image="${old_repo}:${old_tag}" + else + old_image="" + fi + + echo "Old image: ${old_image}" + echo "New image: ${new_image}" + + if [ -z "${new_repo:-}" ] || [ "$new_repo" = "null" ]; then + echo "ERROR: Could not read ${{ matrix.image_yq_path }} from $file" + exit 1 + fi + + if [[ "$new_repo" != "$expected_repo" ]]; then + echo "ERROR: Found image repo \"$new_repo\" but expected \"$expected_repo\"" + exit 1 + fi + + registry="$(echo "$new_repo" | awk -F/ '{print $1}')" + + { + echo "new_image=$new_image" + echo "new_repo=$new_repo" + echo "new_tag=$new_tag" + echo "registry=$registry" + } >> "$GITHUB_OUTPUT" + + - name: Check if image exists on registry + id: check_image + shell: bash + run: | + set -euo pipefail + new_repo="${{ steps.img.outputs.new_repo }}" + new_tag="${{ steps.img.outputs.new_tag }}" + registry_user="${{ gitea.actor }}" + registry_password="${{ secrets.REGISTRY_TOKEN }}" + + registry_host=$(echo "$new_repo" | cut -d/ -f1) + image_path=$(echo "$new_repo" | cut -d/ -f2-) + + echo "Checking if $new_repo:$new_tag exists on registry $registry_host" + + manifest_url="https://${registry_host}/v2/${image_path}/manifests/${new_tag}" + + http_code=$(curl -s -o /dev/null -w "%{http_code}" \ + -u "${registry_user}:${registry_password}" \ + -H "Accept: application/vnd.docker.distribution.manifest.v2+json,application/vnd.docker.distribution.manifest.list.v2+json" \ + "$manifest_url" || echo "000") + + if [ "$http_code" = "200" ]; then + echo "Image already exists on registry (HTTP $http_code)" + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "Image does not exist on registry (HTTP $http_code)" + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Skip if image already exists + if: steps.check_image.outputs.exists == 'true' + run: echo "${{ matrix.description }} image ${{ steps.img.outputs.new_image }} already exists on registry; skipping build." + + - name: Set up Buildx + if: steps.check_image.outputs.exists == 'false' + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + if: steps.check_image.outputs.exists == 'false' + uses: docker/login-action@v3 + with: + registry: ${{ steps.img.outputs.registry }} + username: ${{ gitea.actor }} + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: Build and push ${{ matrix.description }} (exact tag from Helm values) + if: steps.check_image.outputs.exists == 'false' + uses: docker/build-push-action@v6 + with: + context: ${{ matrix.build_context }} + file: ${{ matrix.dockerfile }} + push: true + tags: | + ${{ steps.img.outputs.new_image }} + ${{ steps.img.outputs.new_repo }}:latest + labels: | + org.opencontainers.image.source=${{ gitea.repository }} + org.opencontainers.image.revision=${{ gitea.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/helm/shorefront/templates/backend-deployment.yaml b/helm/shorefront/templates/backend-deployment.yaml index 9a1f78e..c36772a 100644 --- a/helm/shorefront/templates/backend-deployment.yaml +++ b/helm/shorefront/templates/backend-deployment.yaml @@ -18,7 +18,7 @@ spec: spec: initContainers: - name: migrate - image: "{{ .Values.backend.image }}:{{ .Values.backend.tag }}" + image: "{{ .Values.backend.image }}:{{ .Values.containers.version }}" command: ["alembic", "upgrade", "head"] env: - name: POSTGRES_PASSWORD @@ -30,7 +30,7 @@ spec: value: "postgresql://{{ .Values.postgres.user }}:$(POSTGRES_PASSWORD)@postgres:5432/{{ .Values.postgres.database }}" containers: - name: backend - image: "{{ .Values.backend.image }}:{{ .Values.backend.tag }}" + image: "{{ .Values.backend.image }}:{{ .Values.containers.version }}" command: ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] env: - name: POSTGRES_PASSWORD diff --git a/helm/shorefront/templates/frontend-deployment.yaml b/helm/shorefront/templates/frontend-deployment.yaml index 33369bf..d4bc284 100644 --- a/helm/shorefront/templates/frontend-deployment.yaml +++ b/helm/shorefront/templates/frontend-deployment.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: frontend - image: "{{ .Values.frontend.image }}:{{ .Values.frontend.tag }}" + image: "{{ .Values.frontend.image }}:{{ .Values.containers.version }}" ports: - containerPort: 80 resources: diff --git a/helm/shorefront/values.yaml b/helm/shorefront/values.yaml index 2ee93c6..5b1fdac 100644 --- a/helm/shorefront/values.yaml +++ b/helm/shorefront/values.yaml @@ -1,16 +1,14 @@ namespace: shorefront backend: - image: shorefront-backend - tag: latest + image: git.baumann.gr/adebaumann/shorefront-backend replicas: 1 resources: requests: { cpu: 100m, memory: 128Mi } limits: { cpu: 500m, memory: 512Mi } frontend: - image: shorefront-frontend - tag: latest + image: git.baumann.gr/adebaumann/shorefront-frontend replicas: 1 resources: requests: { cpu: 50m, memory: 64Mi } @@ -33,3 +31,6 @@ nfs: ingress: host: shorefront.example.com ingressClassName: traefik + +containers: + version: "0.001"