From 87ae5d14c2a4615ca044791e9dec981a4d107f91 Mon Sep 17 00:00:00 2001 From: Alex Rodriguez <131964409+ezekiel-alexrod@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:27:43 +0200 Subject: [PATCH 1/3] ci: publish the agent image on ghcr.io A new reusable workflow builds the agent image for linux/amd64 and pushes it to ghcr.io//image-cache-agent. The version tag comes from git describe. Post Merge runs it on each push to main that touches agent/, and moves latest-dev. Promote runs it before the release is created, so a broken image blocks the release. A stable tag moves latest, a pre-release tag moves latest-dev. The caller jobs grant packages: write. No secret is passed: the login uses GITHUB_TOKEN. Relates-to: MK8S-446 --- .github/workflows/agent-image.yaml | 72 ++++++++++++++++++++++++++++++ .github/workflows/post-merge.yaml | 20 +++++++++ .github/workflows/promote.yaml | 11 +++++ 3 files changed, 103 insertions(+) create mode 100644 .github/workflows/agent-image.yaml create mode 100644 .github/workflows/post-merge.yaml diff --git a/.github/workflows/agent-image.yaml b/.github/workflows/agent-image.yaml new file mode 100644 index 0000000..9872713 --- /dev/null +++ b/.github/workflows/agent-image.yaml @@ -0,0 +1,72 @@ +name: agent-image + +on: + workflow_call: + inputs: + is-latest: + description: "Also move the latest (or latest-dev) tag" + required: false + default: false + type: boolean + is-stable: + description: "Move latest instead of latest-dev" + required: false + default: false + type: boolean + +# One publish at a time: latest-dev is moved by both a merge to main and a +# pre-release, and the last run to finish must be the newest. +concurrency: + group: agent-image + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # git describe needs the tags and the history. + fetch-depth: 0 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + - name: Log in to ghcr.io + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Compute tags + id: tags + env: + IMAGE: ghcr.io/${{ github.repository_owner }}/image-cache-agent + IS_LATEST: ${{ inputs.is-latest }} + IS_STABLE: ${{ inputs.is-stable }} + run: | + # A tag push names the image after that tag: two tags can sit on + # the same commit, and describe would pick one of them. + if [ "$GITHUB_REF_TYPE" = "tag" ]; then + version="$GITHUB_REF_NAME" + else + version="$(git describe --tags --always --match='v[0-9]*')" + fi + tags="$IMAGE:$version" + if [ "$IS_LATEST" = "true" ]; then + if [ "$IS_STABLE" = "true" ]; then + tags="$tags,$IMAGE:latest" + else + tags="$tags,$IMAGE:latest-dev" + fi + fi + echo "tags=$tags" >> "$GITHUB_OUTPUT" + - name: Build and push + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: agent + # The agent only ships for amd64 (see agent/DESIGN.md). + platforms: linux/amd64 + push: true + tags: ${{ steps.tags.outputs.tags }} + cache-from: type=gha,scope=image-cache-agent + cache-to: type=gha,mode=max,scope=image-cache-agent diff --git a/.github/workflows/post-merge.yaml b/.github/workflows/post-merge.yaml new file mode 100644 index 0000000..f7697b5 --- /dev/null +++ b/.github/workflows/post-merge.yaml @@ -0,0 +1,20 @@ +--- +name: Post Merge + +on: + push: + branches: [main] + paths: + - 'agent/**' + - '.github/workflows/agent-image.yaml' + - '.github/workflows/post-merge.yaml' + +jobs: + agent-image: + uses: ./.github/workflows/agent-image.yaml + permissions: + contents: read + packages: write + with: + is-latest: true + is-stable: false diff --git a/.github/workflows/promote.yaml b/.github/workflows/promote.yaml index da261a2..0749b7f 100644 --- a/.github/workflows/promote.yaml +++ b/.github/workflows/promote.yaml @@ -8,7 +8,18 @@ on: - "v*" jobs: + # Runs before the release is created, so a broken image blocks the release. + build: + uses: ./.github/workflows/agent-image.yaml + permissions: + contents: read + packages: write + with: + is-latest: true + is-stable: ${{ ! contains(github.ref_name, '-') }} + create-release: + needs: build uses: scality/workflows/.github/workflows/promote.yaml@f05282f5bd6d7e90e8536400cf468f0a54681a48 # v2.10.0 permissions: contents: write From eaa7737789bb3f966c1cfd8fd19ff473859c325c Mon Sep 17 00:00:00 2001 From: Alex Rodriguez <131964409+ezekiel-alexrod@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:27:43 +0200 Subject: [PATCH 2/3] docs: point to the agent image published on ghcr.io The READMEs no longer ask the reader to build the image first. The agent README lists the tags, and the other files link to it. Relates-to: MK8S-446 --- CONTRIBUTING.md | 6 ++++-- README.md | 27 ++++++++++++++------------- agent/AGENTS.md | 10 ++++++---- agent/README.md | 22 ++++++++++++++++++++-- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fcd6c7c..34baa17 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,8 +22,10 @@ The repository holds two toolchains, and three things built from them: Keep a change inside one component when you can. Reviewers read the repository that way, and the toolchains have nothing in common. Releases do not follow -that split yet: a tag cuts one version for the whole repository, and the RPMs -are what is attached to it. +that split yet: a tag cuts one version for the whole repository. The RPMs are +attached to it, and the agent image is published under the same tag. A merge +to `main` that touches `agent/` also publishes an image; the tags are listed +in [agent/README.md](agent/README.md#deploying). ## Development environment diff --git a/README.md b/README.md index 7d8aaa9..487e3fd 100644 --- a/README.md +++ b/README.md @@ -95,20 +95,21 @@ happens when an import fails, and how the package is built. ### The agent -Build the image, then deploy: `deploy` applies the CRD along with the rest. No -image is published yet, so the first step is yours. +The image is published at `ghcr.io/scality/image-cache-agent`. Deploy it from +a checkout of the same tag: `deploy` applies the CRD along with the rest, and +takes them from the checkout. ```console -make -C agent docker-build docker-push IMG=/image-cache-agent: -make -C agent deploy IMG=/image-cache-agent: +git checkout +make -C agent deploy IMG=ghcr.io/scality/image-cache-agent: ``` -Those two commands are enough from an amd64 machine, deploying into a -namespace that enforces the `privileged` Pod Security Standard. Anything else -takes a step or two, and [agent/README.md](agent/README.md#deploying) has them: -building for amd64 from another architecture, and the label the manifests leave -off the namespace they create. The flags, `--cache-path` and `--resync-period` -included, are under [Configuration](agent/README.md#configuration). +Those commands are enough to deploy into a namespace that enforces the +`privileged` Pod Security Standard. Anything else takes a step or two, and +[agent/README.md](agent/README.md#deploying) has them: which image tags exist, +building your own image, and the label the manifests leave off the namespace +they create. The flags, `--cache-path` and `--resync-period` included, are +under [Configuration](agent/README.md#configuration). Then declare what each node should cache: @@ -246,9 +247,9 @@ Each component is built and tested independently, and both CI workflows run on every pull request, since a required check that never runs leaves the pull request waiting forever; on pushes to `main` they are scoped by path. -Releases are not independent yet: a tag cuts one version for the repository, -and the artifacts attached are the two RPMs, for both EL versions. Publishing -the agent image is still to come. +Releases are not independent yet: a tag cuts one version for the repository. +It attaches the two RPMs, for both EL versions, and publishes the agent image +under the same tag. ## Development diff --git a/agent/AGENTS.md b/agent/AGENTS.md index a353c0d..0c0010b 100644 --- a/agent/AGENTS.md +++ b/agent/AGENTS.md @@ -104,10 +104,12 @@ Do not scaffold them back in: because controller-runtime defaults to `true` and would gate it the day someone turns leader election on. Every agent converges its own node, so there is no leader to elect. -- **No helm chart.** `make build-installer` renders the manifests into - `dist/install.yaml`, which is gitignored and attached to nothing: today the - way to deploy is this checkout's `make deploy`. Publishing an artifact is a - separate piece of work, not a missing chart. +- **No helm chart.** The image is published on ghcr.io (see + [README.md](README.md#deploying)), but the manifests are not. + `make build-installer` renders them into `dist/install.yaml`, which is + gitignored and attached to nothing: the way to deploy is this checkout's + `make deploy`. Publishing the manifests is a separate piece of work, not a + missing chart. ## Traps worth knowing diff --git a/agent/README.md b/agent/README.md index 8c91efb..a94d26c 100644 --- a/agent/README.md +++ b/agent/README.md @@ -28,8 +28,26 @@ All the commands below run from this directory. ## Deploying -Build and push the image, then deploy. `deploy` builds `config/default`, which -includes the CRD, so it covers what `install` does on its own. +The image is published at `ghcr.io/scality/image-cache-agent`, for amd64 only: + +| Tag | Image | +| --- | ----- | +| `vX.Y.Z`, `vX.Y.Z-
` | built from that release tag |
+| `latest` | the last release without a pre-release suffix to be built: re-running an older Promote moves it back |
+| `latest-dev` | the last pre-release, or the last merge to `main` that touches `agent/` |
+| `--g` | a merge to `main` that touches `agent/`, named by `git describe --tags`: `` commits after the last release tag |
+
+Deploy it from a checkout of the same tag. `deploy` builds `config/default`
+from this checkout, and only the image comes from ``: on another commit,
+the CRD and RBAC may not match the binary. `config/default` includes the CRD,
+so `deploy` covers what `install` does on its own.
+
+```console
+git checkout 
+make deploy IMG=ghcr.io/scality/image-cache-agent:
+```
+
+To deploy your own build, build and push it first:
 
 ```console
 make docker-build docker-push IMG=/image-cache-agent:

From fc97ec3ebebc18dddefdd9f0a5985bf5c02fbbad Mon Sep 17 00:00:00 2001
From: Alex Rodriguez <131964409+ezekiel-alexrod@users.noreply.github.com>
Date: Fri, 9 Oct 2026 09:43:47 +0200
Subject: [PATCH 3/3] ci: give each ref its own agent-image concurrency group

GitHub keeps one pending run per concurrency group and cancels it when
a newer run arrives. With a single shared group, a merge to main could
cancel a pending release build, and the release was then never created.

Merges to main still publish in order. A pre-release can now race with
a merge to main on latest-dev; the workflow comment says so.

Relates-to: MK8S-446
---
 .github/workflows/agent-image.yaml | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/agent-image.yaml b/.github/workflows/agent-image.yaml
index 9872713..50b042c 100644
--- a/.github/workflows/agent-image.yaml
+++ b/.github/workflows/agent-image.yaml
@@ -14,10 +14,13 @@ on:
         default: false
         type: boolean
 
-# One publish at a time: latest-dev is moved by both a merge to main and a
-# pre-release, and the last run to finish must be the newest.
+# One publish at a time per ref. GitHub keeps only one pending run per group
+# and cancels it when a newer one arrives, so a shared group could drop a
+# release build. Merges to main still run in order. A pre-release can race
+# with a merge to main on latest-dev: if it finishes last, latest-dev points
+# to the pre-release until the next merge that touches agent/.
 concurrency:
-  group: agent-image
+  group: agent-image-${{ github.ref }}
   cancel-in-progress: false
 
 jobs: