Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions .github/workflows/functions.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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: |
Expand All @@ -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" \
Expand All @@ -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:
Expand Down
26 changes: 25 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
74 changes: 74 additions & 0 deletions docs/func-utils-image.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 6 additions & 1 deletion hack/images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down