From b906f61ac30f7e53019181aa7985abfa534a0777 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 13 Jun 2026 15:21:35 +0000 Subject: [PATCH] k8s-portal: build off-infra GHA -> ghcr + Keel; remove Woodpecker build (no-local-builds) The last in-cluster image build. GHA build-k8s-portal.yml builds ghcr.io/viktorbarzin/k8s-portal:latest+sha (path-filtered on the Dockerfile dir); Keel (force/poll/match-tag) rolls the deployment. Stack image repointed to ghcr (ignore_changed); .woodpecker/k8s-portal.yml deleted. Co-Authored-By: Claude Fable 5 --- .github/workflows/build-k8s-portal.yml | 36 ++++++++++++++ .woodpecker/k8s-portal.yml | 49 -------------------- stacks/k8s-portal/modules/k8s-portal/main.tf | 18 +++++-- 3 files changed, 50 insertions(+), 53 deletions(-) create mode 100644 .github/workflows/build-k8s-portal.yml delete mode 100644 .woodpecker/k8s-portal.yml diff --git a/.github/workflows/build-k8s-portal.yml b/.github/workflows/build-k8s-portal.yml new file mode 100644 index 00000000..c2679d43 --- /dev/null +++ b/.github/workflows/build-k8s-portal.yml @@ -0,0 +1,36 @@ +name: Build k8s-portal + +# ADR-0002 / no-local-builds: k8s-portal (infra-owned Go portal) builds off-infra +# on GHA → public ghcr; Keel polls ghcr:latest and rolls the deployment. Replaces +# the in-cluster .woodpecker/k8s-portal.yml build. +on: + push: + branches: [master] + paths: + - 'stacks/platform/modules/k8s-portal/files/**' + workflow_dispatch: {} + +permissions: + contents: read + packages: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/build-push-action@v6 + with: + context: stacks/platform/modules/k8s-portal/files + platforms: linux/amd64 + provenance: false + push: true + tags: | + ghcr.io/viktorbarzin/k8s-portal:latest + ghcr.io/viktorbarzin/k8s-portal:${{ github.sha }} diff --git a/.woodpecker/k8s-portal.yml b/.woodpecker/k8s-portal.yml deleted file mode 100644 index 39c9ff17..00000000 --- a/.woodpecker/k8s-portal.yml +++ /dev/null @@ -1,49 +0,0 @@ -when: - event: push - branch: master - path: - include: - - "stacks/platform/modules/k8s-portal/files/**" - -clone: - git: - image: woodpeckerci/plugin-git - settings: - attempts: 5 - backoff: 10s - -steps: - - name: build-and-push - image: woodpeckerci/plugin-docker-buildx - settings: - username: "viktorbarzin" - password: - from_secret: dockerhub-pat - repo: viktorbarzin/k8s-portal - dockerfile: stacks/platform/modules/k8s-portal/files/Dockerfile - context: stacks/platform/modules/k8s-portal/files - platforms: - - linux/amd64 - tag: ["${CI_PIPELINE_NUMBER}", "latest"] - cache_from: "viktorbarzin/k8s-portal:latest" - cache_to: "type=inline" - - - name: deploy - image: bitnami/kubectl:latest - commands: - - "kubectl set image deployment/k8s-portal portal=viktorbarzin/k8s-portal:${CI_PIPELINE_NUMBER} -n k8s-portal" - - "kubectl rollout status deployment/k8s-portal -n k8s-portal --timeout=120s" - - "echo 'k8s-portal deployed successfully (build ${CI_PIPELINE_NUMBER})'" - - - name: slack - image: curlimages/curl - commands: - - | - curl -s -X POST -H 'Content-type: application/json' \ - --data "{\"text\":\"K8s Portal: build #${CI_PIPELINE_NUMBER} ${CI_PIPELINE_STATUS}\"}" \ - "$SLACK_WEBHOOK" || true - environment: - SLACK_WEBHOOK: - from_secret: slack_webhook - when: - status: [success, failure] diff --git a/stacks/k8s-portal/modules/k8s-portal/main.tf b/stacks/k8s-portal/modules/k8s-portal/main.tf index 60057635..908fca49 100644 --- a/stacks/k8s-portal/modules/k8s-portal/main.tf +++ b/stacks/k8s-portal/modules/k8s-portal/main.tf @@ -9,7 +9,7 @@ resource "kubernetes_namespace" "k8s_portal" { metadata { name = "k8s-portal" labels = { - tier = var.tier + tier = var.tier "keel.sh/enrolled" = "true" } } @@ -40,6 +40,15 @@ resource "kubernetes_deployment" "k8s_portal" { metadata { name = "k8s-portal" namespace = kubernetes_namespace.k8s_portal.metadata[0].name + # ADR-0002 / no-local-builds: image now GHA-built -> ghcr:latest + # (.github/workflows/build-k8s-portal.yml). Keel polls ghcr:latest and rolls + # this deployment (replaces the removed Woodpecker in-cluster build+deploy). + annotations = { + "keel.sh/policy" = "force" + "keel.sh/trigger" = "poll" + "keel.sh/pollSchedule" = "@every 5m" + "keel.sh/match-tag" = "true" + } labels = { app = "k8s-portal" tier = var.tier @@ -68,7 +77,7 @@ resource "kubernetes_deployment" "k8s_portal" { spec { container { name = "portal" - image = "viktorbarzin/k8s-portal:latest" + image = "ghcr.io/viktorbarzin/k8s-portal:latest" port { container_port = 3000 } @@ -121,7 +130,8 @@ resource "kubernetes_deployment" "k8s_portal" { # DRIFT_WORKAROUND: CI pipeline owns image tag (kubectl set image from Woodpecker/GHA); Kyverno mutates dns_config for ndots. Reviewed 2026-04-18. ignore_changes = [ spec[0].template[0].spec[0].dns_config, # KYVERNO_LIFECYCLE_V1 - spec[0].template[0].spec[0].container[0].image, # CI updates image tag + spec[0].template[0].spec[0].container[0].image, # Keel manages ghcr:latest digest + metadata[0].annotations["keel.sh/update-time"], # KEEL_LIFECYCLE_V1 (Keel stamps on roll) ] } } @@ -172,5 +182,5 @@ module "ingress_setup_script" { ingress_path = ["/setup/script", "/agent"] tls_secret_name = var.tls_secret_name # auth = "none": Setup script + agent endpoint must be curl-able without auth (no cookies preserved in automation). - auth = "none" + auth = "none" }