From 798b48bc7857e42e27e5751662f256e56cf7f5bb Mon Sep 17 00:00:00 2001 From: David Fridrich Date: Wed, 5 Aug 2026 17:31:16 +0200 Subject: [PATCH] feat: pin func-utils image to a per-release-branch tag Co-Authored-By: Claude Fable 5 --- .github/workflows/functions.yaml | 29 ++++++++++--- Makefile | 26 ++++++++++- docs/README.md | 1 + docs/func-utils-image.md | 74 ++++++++++++++++++++++++++++++++ hack/images.sh | 7 ++- 5 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 docs/func-utils-image.md diff --git a/.github/workflows/functions.yaml b/.github/workflows/functions.yaml index 66f38b7b41..ac2accdb10 100644 --- a/.github/workflows/functions.yaml +++ b/.github/workflows/functions.yaml @@ -1,11 +1,6 @@ name: Functions # To run this workflow's tests locally, see ./hack/test-full.sh -# -# Fork CI note (Hamr#395 external-dev / Functions#57): coverage upload goes through -# ./.github/actions/codecov — no-ops when CODECOV_TOKEN is empty so forks without -# the secret stay green after tests. Upstream knative/func still uploads when set. - permissions: id-token: write # Required for signing @@ -460,7 +455,9 @@ jobs: - test-e2e-podman - test-e2e-runtimes - test-e2e-config-ci - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + if: >- + (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && + (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-')) runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -507,6 +504,9 @@ jobs: publish-utils-image: name: Publish Utils Image needs: build + if: >- + (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && + (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-')) runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -519,6 +519,20 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Derive Image Tag + id: image-tag + # main publishes the floating tag v2; a release branch release-X.Y + # publishes the per-minor pinned tag X.Y (see docs/func-utils-image.md). + run: | + if [[ "${GITHUB_REF_NAME}" == "main" ]]; then + tag="v2" + elif [[ "${GITHUB_REF_NAME}" =~ ^release-([0-9]+\.[0-9]+)$ ]]; then + tag="${BASH_REMATCH[1]}" + else + echo "::error::refusing to publish func-utils from ref '${GITHUB_REF_NAME}'" + exit 1 + fi + echo "tag=${tag}" >> "$GITHUB_OUTPUT" - name: Build and push id: build-images run: | @@ -532,7 +546,7 @@ jobs: --provenance=false --sbom=false \ --platform=linux/ppc64le,linux/s390x,linux/amd64,linux/arm64 \ --push \ - -t "ghcr.io/knative/func-utils:v2" \ + -t "ghcr.io/knative/func-utils:${{ steps.image-tag.outputs.tag }}" \ --annotation index:org.opencontainers.image.description="Knative Func Utils Image" \ --annotation index:org.opencontainers.image.source="https://github.com/knative/func" \ --annotation index:org.opencontainers.image.vendor="https://github.com/knative/func" \ @@ -550,6 +564,7 @@ jobs: publish-image: name: Publish as Image needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 30 steps: diff --git a/Makefile b/Makefile index 5341acdd7e..0395cdc051 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,27 @@ KVER ?= $(shell git describe --tags --match 'knative-*' 2>/dev/null) LDFLAGS := -X knative.dev/func/pkg/version.Vers=$(VERS) -X knative.dev/func/pkg/version.Kver=$(KVER) -X knative.dev/func/pkg/version.Hash=$(HASH) -FUNC_UTILS_IMG ?= ghcr.io/knative/func-utils:v2 +# func-utils image (multi-call binary for on-cluster tasks; see +# docs/func-utils-image.md for the tag and compatibility policy). +# +# Release builds pin to a per-minor tag "X.Y" so released CLIs are not +# broken by later changes on main; everything else (main, nightly, PR, +# local dev) floats on "v2". Pinning triggers when either: +# - KVER is an exact release version ("v1.24.0" as passed by Prow via +# hack/release.sh, or "knative-v1.24.0" when HEAD is exactly tagged), or +# - the current git branch is a release branch ("release-X.Y"). +# Nightly KVERs ("vYYYYMMDD-hash") and git-describe suffixes +# ("knative-v1.24.0-3-gabc") intentionally do not match. +# An explicitly provided FUNC_UTILS_IMG always wins (used downstream). +FUNC_UTILS_TAG := $(shell \ + if echo "$(KVER)" | grep -qE '^(knative-)?v[0-9]+\.[0-9]+\.[0-9]+$$'; then \ + echo "$(KVER)" | sed -E 's/^(knative-)?v([0-9]+\.[0-9]+)\.[0-9]+$$/\2/'; \ + elif git branch --show-current 2>/dev/null | grep -qE '^release-[0-9]+\.[0-9]+$$'; then \ + git branch --show-current | sed -e 's/^release-//'; \ + else \ + echo v2; \ + fi) +FUNC_UTILS_IMG ?= ghcr.io/knative/func-utils:$(FUNC_UTILS_TAG) LDFLAGS += -X knative.dev/func/pkg/k8s.SocatImage=$(FUNC_UTILS_IMG) LDFLAGS += -X knative.dev/func/pkg/k8s.TarImage=$(FUNC_UTILS_IMG) LDFLAGS += -X knative.dev/func/pkg/pipelines/tekton.FuncUtilImage=$(FUNC_UTILS_IMG) @@ -341,6 +361,10 @@ func-instrumented-bin: # func binary instrumented with coverage reporting ##@ Release Artifacts ###################### +.PHONY: func-utils-image +func-utils-image: ## Print the func-utils image reference embedded into builds + @echo "$(FUNC_UTILS_IMG)" + .PHONY: cross-platform cross-platform: darwin-arm64 darwin-amd64 linux-amd64 linux-arm64 linux-ppc64le linux-s390x windows ## Build all distributable (cross-platform) binaries diff --git a/docs/README.md b/docs/README.md index efaed440e2..cb3c863a03 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,6 +25,7 @@ Functions can be deployed on the following platforms: [Function Developer's Guide](https://knative.dev/docs/functions/) [Function Integrator's Guide](integrators_guide.md). [CLI Command Reference](reference/func.md) +[The func-utils Image](func-utils-image.md) ## Contributing diff --git a/docs/func-utils-image.md b/docs/func-utils-image.md new file mode 100644 index 0000000000..dcc2ea678a --- /dev/null +++ b/docs/func-utils-image.md @@ -0,0 +1,74 @@ +# The func-utils image + +`ghcr.io/knative/func-utils` packages `cmd/func-util`, a single Go binary +that dispatches on its invocation name (`deploy`, `scaffold`, `s2i`, +`s2i-generate`, `socat`, `sh`). The `func` CLI does not run this image +itself; it creates pods and Tekton TaskRuns from it: Tekton build steps, +the socat dialer, and the tar-based volume upload. + +The consumers are already-shipped CLI binaries. Each released CLI embeds +one image reference at build time (see below) and then invokes the image +with fixed argv shapes, parses fixed stderr strings, and shares files such +as `middleware-version` with it. This interface is an ABI between old +binaries and a mutable tag: changing the image under a tag changes +behavior for every CLI that embeds that tag, including ones released long +ago. + +## Tag scheme + +| Tag | Meaning | +| --- | --- | +| `latest` | Frozen legacy tag for pre-2025 CLIs. Never moves. | +| `v2` | Floating tag. Published from every green push to `main`. Embedded by dev, nightly, and main builds. | +| `X.Y` (for example `1.24`) | Per-minor pinned tag. Published from every green push to the `release-X.Y` branch. Embedded by all released `vX.Y.*` CLIs. | + +Backports merged to a `release-X.Y` branch re-publish `X.Y`, so fixes +still reach every CLI of that minor. Pushes to `main` never touch `X.Y`, +so main development cannot break released CLIs. + +The per-minor tags carry no `v` prefix. This keeps them in a separate +namespace from the contract-version tags `v1`/`v2`, so the two schemes +cannot collide. + +## Compatibility rule + +Within any published tag, the sub-command contracts are frozen: argv +shapes, exit codes, parsed stderr strings, and shared files. + +- A change that breaks any of these must never be backported to a + `release-X.Y` branch. +- On `main`, such a change requires bumping the floating tag (`v2` to + `v3`) in the same PR as the new source defaults, so older embedded + references keep resolving to a compatible image. + +Motivation: [#2686](https://github.com/knative/func/pull/2686) (the +image became incompatible with shipped CLIs, so `latest` was frozen and +`v2` introduced in the same change) and +[#3237](https://github.com/knative/func/pull/3237) (`scaffold` grew a +third argument with no tag bump, silently breaking on-cluster builds for +every older released CLI). + +## Build-time derivation + +The Makefile derives `FUNC_UTILS_IMG` and embeds it into three variables +(`pkg/k8s.SocatImage`, `pkg/k8s.TarImage`, +`pkg/pipelines/tekton.FuncUtilImage`) via ldflags. Release builds (an +exact release `KVER`, or a `release-X.Y` branch checkout) pin to `X.Y`; +everything else floats on `v2`. An explicitly provided `FUNC_UTILS_IMG` +always wins. + +`make func-utils-image` prints the effective reference. `hack/images.sh` +uses it to push the locally built image under the same tag for e2e tests. + +## Release-cut checklist + +After cutting a `release-X.Y` branch: + +1. Verify the Functions workflow run for the branch push succeeded. +2. Verify the tag exists: + `docker manifest inspect ghcr.io/knative/func-utils:X.Y`. +3. If either failed, fix and re-push, or trigger the Functions workflow + manually (`workflow_dispatch`) on the branch. + +The `X.Y` tag must exist before the `vX.Y.0` release is published; +otherwise the released CLI embeds a reference that does not resolve. diff --git a/hack/images.sh b/hack/images.sh index d509207c0f..36eafafbde 100755 --- a/hack/images.sh +++ b/hack/images.sh @@ -4,7 +4,12 @@ set -o errexit set -o nounset set -o pipefail -FUNC_UTILS_IMG="registry.localtest.me/knative/func-utils:v2" +# Push the locally built func-util image under the same tag the func binary +# embeds (FUNC_UTILS_IMG in Makefile: v2 on main/PRs, X.Y on a release-X.Y +# branch). The kind cluster mirrors ghcr.io to the local registry +# (hack/cluster.sh), so repo path and tag must match for in-cluster pulls. +EMBEDDED_IMG="$(make -C "$(dirname "$0")/.." --no-print-directory -s func-utils-image)" +FUNC_UTILS_IMG="registry.localtest.me/knative/func-utils:${EMBEDDED_IMG##*:}" CGO_ENABLED=0 go build -o "func-util" -trimpath -ldflags '-w -s' ./cmd/func-util