From a47aeab0ab043c07a476c876250945a7d44c94b9 Mon Sep 17 00:00:00 2001 From: Luca Ghersi Date: Mon, 14 Sep 2026 17:15:41 +0200 Subject: [PATCH] feat(gitops): add an Artifact Registry push target artifact-registry: true switches the push to europe-docker.pkg.dev/staffbase-artifacts/images-publish and authenticates with a service account key instead of the Harbor credentials, which are ignored when it is set. A key rather than Workload Identity Federation, because a job's permissions cannot be raised from inside a called workflow: adding id-token: write here would fail every caller that has not granted it. The key needs no permission, so a service migrates by passing one secret. The condition is negated on each expression rather than selecting an empty string on the true branch, because empty is falsy and would fall through to the Harbor value. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/template_gitops.yml | 46 ++++++++++++++++++--------- README.md | 17 ++++++++++ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.github/workflows/template_gitops.yml b/.github/workflows/template_gitops.yml index 27a00de7..2d269b5e 100644 --- a/.github/workflows/template_gitops.yml +++ b/.github/workflows/template_gitops.yml @@ -90,6 +90,11 @@ on: type: boolean default: false description: "Create GitHub Deployments on the source repository and write tracking annotations to the GitOps CRs" + artifact-registry: + required: false + type: boolean + default: false + description: "Push to Google Artifact Registry instead of Harbor. Overrides docker-registry and prefixes docker-image with the publish repository. Pass the gcp-service-account-key secret alongside it." multi-arch: required: false type: boolean @@ -106,6 +111,8 @@ on: required: false docker-password: required: false + gcp-service-account-key: + required: false docker-build-secrets: required: false docker-build-secret-files: @@ -148,11 +155,14 @@ jobs: - name: GitOps (build, push and deploy a new Docker image) id: gitops - uses: Staffbase/gitops-github-action@158275454d56fd8fa923995220ee1b9eb1030032 # v8.2.1 + uses: Staffbase/gitops-github-action@2c7f44d08b1e3e03357a10cfd703bea1c55051c9 # UNRELEASED - re-pin to the release tag before merging with: - docker-registry: ${{ inputs.docker-registry }} - docker-username: ${{ secrets.docker-username }} - docker-password: ${{ secrets.docker-password }} + # Empty is falsy in an expression, so the condition is negated rather than + # selecting '' on the true branch, which would fall through to the Harbor value. + docker-registry: ${{ inputs.artifact-registry && 'europe-docker.pkg.dev' || inputs.docker-registry }} + docker-username: ${{ !inputs.artifact-registry && secrets.docker-username || '' }} + docker-password: ${{ !inputs.artifact-registry && secrets.docker-password || '' }} + gcp-service-account-key: ${{ secrets.gcp-service-account-key }} docker-build-args: | ${{ inputs.docker-build-args }} GONOSUMDB=${{ vars.gonosumdb }} @@ -164,7 +174,7 @@ jobs: docker-build-provenance: ${{ inputs.docker-build-provenance }} docker-disable-retagging: ${{ inputs.docker-disable-retagging }} docker-file: ${{ inputs.docker-file }} - docker-image: ${{ inputs.docker-image }} + docker-image: ${{ inputs.artifact-registry && format('staffbase-artifacts/images-publish/{0}', inputs.docker-image) || inputs.docker-image }} docker-custom-tag: ${{ inputs.docker-custom-tag }} docker-tag-timestamp: ${{ inputs.docker-tag-timestamp }} gitops-organization: ${{ inputs.gitops-organization }} @@ -200,13 +210,16 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: GitOps (build and push by digest) - uses: Staffbase/gitops-github-action@158275454d56fd8fa923995220ee1b9eb1030032 # v8.2.1 + uses: Staffbase/gitops-github-action@2c7f44d08b1e3e03357a10cfd703bea1c55051c9 # UNRELEASED - re-pin to the release tag before merging with: multiarch-mode: build multiarch-artifact-name: ${{ inputs.multiarch-artifact-name }} - docker-registry: ${{ inputs.docker-registry }} - docker-username: ${{ secrets.docker-username }} - docker-password: ${{ secrets.docker-password }} + # Empty is falsy in an expression, so the condition is negated rather than + # selecting '' on the true branch, which would fall through to the Harbor value. + docker-registry: ${{ inputs.artifact-registry && 'europe-docker.pkg.dev' || inputs.docker-registry }} + docker-username: ${{ !inputs.artifact-registry && secrets.docker-username || '' }} + docker-password: ${{ !inputs.artifact-registry && secrets.docker-password || '' }} + gcp-service-account-key: ${{ secrets.gcp-service-account-key }} docker-build-args: | ${{ inputs.docker-build-args }} GONOSUMDB=${{ vars.gonosumdb }} @@ -216,7 +229,7 @@ jobs: docker-build-provenance: ${{ inputs.docker-build-provenance }} docker-disable-retagging: ${{ inputs.docker-disable-retagging }} docker-file: ${{ inputs.docker-file }} - docker-image: ${{ inputs.docker-image }} + docker-image: ${{ inputs.artifact-registry && format('staffbase-artifacts/images-publish/{0}', inputs.docker-image) || inputs.docker-image }} docker-custom-tag: ${{ inputs.docker-custom-tag }} docker-tag-timestamp: ${{ inputs.docker-tag-timestamp }} working-directory: ${{ inputs.working-directory }} @@ -248,13 +261,16 @@ jobs: - name: GitOps (merge manifests and deploy) id: gitops - uses: Staffbase/gitops-github-action@158275454d56fd8fa923995220ee1b9eb1030032 # v8.2.1 + uses: Staffbase/gitops-github-action@2c7f44d08b1e3e03357a10cfd703bea1c55051c9 # UNRELEASED - re-pin to the release tag before merging with: multiarch-mode: merge multiarch-artifact-name: ${{ inputs.multiarch-artifact-name }} - docker-registry: ${{ inputs.docker-registry }} - docker-username: ${{ secrets.docker-username }} - docker-password: ${{ secrets.docker-password }} + # Empty is falsy in an expression, so the condition is negated rather than + # selecting '' on the true branch, which would fall through to the Harbor value. + docker-registry: ${{ inputs.artifact-registry && 'europe-docker.pkg.dev' || inputs.docker-registry }} + docker-username: ${{ !inputs.artifact-registry && secrets.docker-username || '' }} + docker-password: ${{ !inputs.artifact-registry && secrets.docker-password || '' }} + gcp-service-account-key: ${{ secrets.gcp-service-account-key }} docker-build-args: | ${{ inputs.docker-build-args }} GONOSUMDB=${{ vars.gonosumdb }} @@ -265,7 +281,7 @@ jobs: docker-build-provenance: ${{ inputs.docker-build-provenance }} docker-disable-retagging: ${{ inputs.docker-disable-retagging }} docker-file: ${{ inputs.docker-file }} - docker-image: ${{ inputs.docker-image }} + docker-image: ${{ inputs.artifact-registry && format('staffbase-artifacts/images-publish/{0}', inputs.docker-image) || inputs.docker-image }} docker-custom-tag: ${{ inputs.docker-custom-tag }} docker-tag-timestamp: ${{ inputs.docker-tag-timestamp }} gitops-organization: ${{ inputs.gitops-organization }} diff --git a/README.md b/README.md index c8877080..7d77069b 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,23 @@ jobs: +#### Pushing to Google Artifact Registry + +Set `artifact-registry: true` and pass the `GCP_ARTIFACT_PUBLISHER_KEY` organization secret. The +registry becomes `europe-docker.pkg.dev` and `docker-image` is prefixed with the publish +repository, so `sb-images/my-service` is pushed as +`europe-docker.pkg.dev/staffbase-artifacts/images-publish/sb-images/my-service`. + +```yaml + with: + artifact-registry: true + secrets: + gcp-service-account-key: ${{ secrets.GCP_ARTIFACT_PUBLISHER_KEY }} +``` + +Drop `docker-username` and `docker-password`: they are ignored when `artifact-registry` is set. +Pulls are unaffected either way, because the `images` repository serves Harbor as an upstream. + ### Jira Ticket Tagging