From 71fc10902462f8476d712aa879adbe8c85aee03c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 15:34:53 +1000 Subject: [PATCH 01/12] chore: bump step-security/harden-runner from 2.19.2 to 2.21.0 (#839) --- .github/workflows/codespell.yml | 2 +- .github/workflows/workflow-lint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 27b6f5379..35f0d7383 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 with: egress-policy: audit diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml index 67febc7a6..87423fa1c 100644 --- a/.github/workflows/workflow-lint.yml +++ b/.github/workflows/workflow-lint.yml @@ -22,7 +22,7 @@ jobs: timeout-minutes: 10 steps: - name: Harden Runner - uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 + uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0 with: egress-policy: audit From 8a5e79d826a813d3721340a0b10feeed25f499af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:11:31 -0700 Subject: [PATCH 02/12] chore: bump github/codeql-action/autobuild from 4.37.6 to 4.37.7 (#842) * chore: bump github/codeql-action/autobuild from 4.37.6 to 4.37.7 Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.37.6 to 4.37.7. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore: bump codeql-action init and analyze to 4.37.7 Co-authored-by: michaelawyu <14261500+michaelawyu@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: michaelawyu <14261500+michaelawyu@users.noreply.github.com> --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index edaa4ff37..23b855f38 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -56,7 +56,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 + uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -69,4 +69,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 From caae3101f3b4e4144323867c299dedb888f5def2 Mon Sep 17 00:00:00 2001 From: Yetkin Timocin Date: Wed, 26 Aug 2026 14:30:33 -0700 Subject: [PATCH 03/12] feat: build multi-arch (amd64/arm64) images and publish both tags in a single buildx (#745) * feat: build multi-arch (amd64/arm64) images and publish both tags in a single buildx Make the hub-agent, member-agent, and refresh-token images multi-arch (linux/amd64 + linux/arm64) and collapse the publish path into a single dual-tag `docker buildx build`, removing the separate `imagetools create` retag step. - Makefile: add PLATFORMS / RELEASE_PLATFORMS; `push` now builds a multi-arch manifest. New `setup-qemu` target registers QEMU emulation for any multi-platform build regardless of whether the buildx builder already exists (the old recipe registered it only when creating the builder, so a pre-existing builder silently skipped emulation and the foreign-arch build failed with "exec format error"). IMAGE_EXTRA_TAG publishes the short alias within the same buildx push. - Dockerfiles: rely on BuildKit-provided TARGETOS/TARGETARCH for per-arch native builds (CGO + GOEXPERIMENT=systemcrypto links against the target arch's OpenSSL, so cross-compilation is not possible); add `# syntax=docker/dockerfile:1`, `COPY --link`, and package-path `go build` so a future second source file is not silently dropped. - release.yml: a single dual-tag `make push` replaces the imagetools retag (stable releases publish both vX.Y.Z and the short X.Y.Z; RC tags publish only the full form). The verify step now asserts each published tag is a real multi-arch manifest instead of only echoing tag strings. Part of #693 (Phase 1: add linux/arm64 to make push; replace the imagetools create retag with a single dual-tag buildx invocation). Signed-off-by: Yetkin Timocin * fix: pin QEMU binfmt images by digest and always select the buildx builder Address review feedback on the multi-arch build path: - Both privileged binfmt registration containers now come from Microsoft-vetted MCR mirrors and are pinned by tag AND digest. The binfmt mirror digest was cross-verified byte-identical to the upstream docker.io/tonistiigi/binfmt tag it mirrors. - docker-buildx-builder now runs 'buildx use' + 'inspect --bootstrap' unconditionally, not only on builder creation: if the named builder existed but another builder was selected, builds would silently run on the wrong builder (potentially without multi-platform support). Co-Authored-By: Claude Fable 5 Signed-off-by: Yetkin Timocin * fix: register QEMU for single-platform cross builds and drop colon from tag examples Address review nits: setup-qemu previously skipped emulation for any single-platform build, but a single-platform cross build (e.g. PLATFORMS=linux/arm64 on an amd64 host) still needs binfmt handlers because the builder stage runs per-target under emulation. Skip only when PLATFORMS is exactly the native platform. Signed-off-by: Yetkin Timocin --------- Signed-off-by: Yetkin Timocin Co-authored-by: Claude Fable 5 --- .github/workflows/release.yml | 52 +++++++++------- Makefile | 104 +++++++++++++++++++++----------- docker/hub-agent.Dockerfile | 24 +++++--- docker/member-agent.Dockerfile | 22 ++++--- docker/refresh-token.Dockerfile | 24 +++++--- 5 files changed, 146 insertions(+), 80 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07762796d..030e9b91b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -60,39 +60,47 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + # A single multi-arch `docker buildx build` per image publishes the full + # tag (e.g. "v0.4.0") and, for stable releases, the short alias + # (e.g. "0.4.0") in the same push - no separate retag step. RC images are + # published under the long form ("v0.4.0-rc.1") for testers only: the + # short-tag namespace is deliberately reserved for stable releases that + # consumers can safely pin to, so RC tags get no short alias. - name: Build and push images with tag ${{ env.TAG }} - run: | - make push - - # The short-tag (e.g. ":0.4.0") aliases the long form for stable releases - # only. RC images are published under the long form (":v0.4.0-rc.1") for - # testers, but we deliberately do NOT alias them to a short tag - the - # short-tag namespace is reserved for stable releases that consumers can - # safely pin to (and "imagetools create" with an RC alias would publish - # "0.4.0-rc.1" into that namespace, muddying it). - - name: Tag and push images without v prefix - if: ${{ !contains(needs.export-registry.outputs.tag, '-rc.') }} env: VERSION: ${{ needs.export-registry.outputs.version }} run: | set -euo pipefail - for IMAGE in ${{ env.HUB_AGENT_IMAGE_NAME }} ${{ env.MEMBER_AGENT_IMAGE_NAME }} ${{ env.REFRESH_TOKEN_IMAGE_NAME }}; do - docker buildx imagetools create \ - --tag "${{ env.REGISTRY }}/${IMAGE}:${VERSION}" \ - "${{ env.REGISTRY }}/${IMAGE}:${{ env.TAG }}" - done + if [[ "${TAG}" == *-rc.* ]]; then + make push + else + make push IMAGE_EXTRA_TAG="${VERSION}" + fi - - name: Verify images + # Confirm every published tag is a real multi-arch manifest list rather + # than a silently degraded single-arch image: a build that pushed only one + # architecture would otherwise go unnoticed until a consumer on the other + # architecture failed to pull. Stable releases also carry the short alias. + - name: Verify images are multi-arch env: VERSION: ${{ needs.export-registry.outputs.version }} run: | set -euo pipefail - echo "✅ Published images:" + tags="${TAG}" + if [[ "${TAG}" != *-rc.* ]]; then + tags="${tags} ${VERSION}" + fi + echo "✅ Verifying published images:" for IMAGE in ${{ env.HUB_AGENT_IMAGE_NAME }} ${{ env.MEMBER_AGENT_IMAGE_NAME }} ${{ env.REFRESH_TOKEN_IMAGE_NAME }}; do - echo " - ${{ env.REGISTRY }}/${IMAGE}:${{ env.TAG }}" - if [[ "${TAG}" != *-rc.* ]]; then - echo " - ${{ env.REGISTRY }}/${IMAGE}:${VERSION}" - fi + for tag in ${tags}; do + ref="${{ env.REGISTRY }}/${IMAGE}:${tag}" + echo " - ${ref}" + manifest="$(docker buildx imagetools inspect "${ref}")" + for platform in linux/amd64 linux/arm64; do + grep -q "Platform:.*${platform}" <<<"${manifest}" \ + || { echo "::error::${ref} is missing platform ${platform}"; exit 1; } + done + done done # Publish the raw CRDs as a standalone release asset so consumers can install diff --git a/Makefile b/Makefile index 5f0dccf6a..392ff6179 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,12 @@ HUB_AGENT_IMAGE_VERSION ?= $(TAG) MEMBER_AGENT_IMAGE_VERSION ?= $(TAG) REFRESH_TOKEN_IMAGE_VERSION ?= $(TAG) +# Optional additional tag applied to every image within the same `docker buildx +# build` invocation. A stable release sets this to the short, v-less version +# (e.g. "0.4.0") so both "v0.4.0" and "0.4.0" are published from one build, +# which replaces a separate `docker buildx imagetools create` retag step. +IMAGE_EXTRA_TAG ?= + HUB_AGENT_IMAGE_NAME ?= hub-agent MEMBER_AGENT_IMAGE_NAME ?= member-agent REFRESH_TOKEN_IMAGE_NAME := refresh-token @@ -240,12 +246,28 @@ run-memberagent: manifests generate fmt vet ## Run member-agent from your host OUTPUT_TYPE ?= type=registry BUILDX_BUILDER_NAME ?= img-builder -QEMU_VERSION ?= 7.2.0-1 BUILDKIT_VERSION ?= v0.18.1 +# QEMU binfmt registration images (see setup-qemu). Both run --privileged, so +# they come from the Microsoft-vetted MCR mirrors (no direct third-party pulls) +# and are pinned by digest as well as tag: a floating reference on a privileged +# release-path container is a supply-chain risk. The binfmt mirror digest is +# byte-identical to the upstream docker.io/tonistiigi/binfmt tag it mirrors. +QEMU_VERSION ?= 7.2.0-1 +QEMU_IMAGE ?= mcr.microsoft.com/mirror/docker/multiarch/qemu-user-static:$(QEMU_VERSION)@sha256:cb0dff994856c640b6080bfbd5983352e7856221a989625ea3e232cb7eff4507 +BINFMT_VERSION ?= qemu-v9.2.2-52 +BINFMT_IMAGE ?= mcr.microsoft.com/mirror/docker/tonistiigi/binfmt:$(BINFMT_VERSION)@sha256:1b804311fe87047a4c96d38b4b3ef6f62fca8cd125265917a9e3dc3c996c39e6 + +# Platforms to build container images for. Defaults to the host/target platform +# so local single-arch builds (e.g. loading into kind, which cannot load a +# multi-platform image) keep working unchanged; `push` overrides this to build a +# multi-arch manifest for the release. +PLATFORMS ?= $(TARGET_OS)/$(TARGET_ARCH) +RELEASE_PLATFORMS ?= linux/amd64,linux/arm64 + .PHONY: push -push: ## Build and push all Docker images - $(MAKE) OUTPUT_TYPE="type=registry" docker-build-hub-agent docker-build-member-agent docker-build-refresh-token +push: ## Build and push all Docker images as multi-arch manifests + $(MAKE) OUTPUT_TYPE="type=registry" PLATFORMS="$(RELEASE_PLATFORMS)" docker-build-hub-agent docker-build-member-agent docker-build-refresh-token .PHONY: helm-push helm-push: ## Package and push Helm charts to OCI registry @@ -295,64 +317,78 @@ crd-verify: ## Verify the chart CRD directories cover every CRD in config/crd/ba fi; \ echo "crd-verify: chart CRD directories cover all CRDs in config/crd/bases" -# By default, docker buildx create will pull image moby/buildkit:buildx-stable-1 and hit the too many requests error -.PHONY: docker-buildx-builder -# Note (chenyu1): the step below sets up emulation for building/running non-native binaries on the host. The original -# setup assumes that the Makefile is always run on an x86_64 platform, and adds support for non-x86_64 hosts. Here -# we keep the original setup if the build target is x86_64 platforms (default) for compatibility reasons, but will switch to -# a more general setup for non-x86_64 hosts. +# Register QEMU binfmt handlers so the host can build/run non-native binaries. +# This must run for any build that targets a non-native platform - whether a +# multi-platform build or a single-platform cross build (e.g. PLATFORMS=linux/arm64 +# on an amd64 host) - regardless of whether the buildx builder already exists (a +# pre-existing builder would otherwise skip emulation setup and the foreign-arch +# build would fail with "exec format error"). The registration is idempotent, so +# it is safe to re-run. It is skipped only when PLATFORMS is exactly the native +# platform, which never needs emulation. # -# On some systems the emulation setup might not work at all (e.g., macOS on Apple Silicon -> Rosetta 2 will be used -# by Docker Desktop as the default emulation option for AMD64 on ARM64 container compatibility). -docker-buildx-builder: +# On some systems the emulation setup might not work at all (e.g., macOS on Apple +# Silicon -> Rosetta 2 will be used by Docker Desktop as the default emulation +# option for AMD64 on ARM64 container compatibility). +.PHONY: setup-qemu +setup-qemu: ## Register QEMU emulation for multi-architecture image builds $(info Auto-detected system architecture: $(TARGET_ARCH)) - @if ! docker buildx ls | grep $(BUILDX_BUILDER_NAME); then \ - if [ "$(TARGET_ARCH)" = "amd64" ] ; then \ - echo "The target is an x86_64 platform; setting up emulation for other known architectures"; \ - docker run --rm --privileged mcr.microsoft.com/mirror/docker/multiarch/qemu-user-static:$(QEMU_VERSION) --reset -p yes; \ - else \ - echo "Setting up emulation for known architectures"; \ - docker run --rm --privileged tonistiigi/binfmt --install all; \ - fi ;\ - docker buildx create --driver-opt image=mcr.microsoft.com/oss/v2/moby/buildkit:$(BUILDKIT_VERSION) --name $(BUILDX_BUILDER_NAME) --use; \ - docker buildx inspect $(BUILDX_BUILDER_NAME) --bootstrap; \ + @case "$(PLATFORMS)" in \ + "$(TARGET_OS)/$(TARGET_ARCH)") \ + echo "Native single-platform build ($(PLATFORMS)); skipping QEMU setup" ;; \ + *) \ + echo "Build targets non-native platforms ($(PLATFORMS)); registering QEMU emulation"; \ + if [ "$(TARGET_ARCH)" = "amd64" ] ; then \ + docker run --rm --privileged $(QEMU_IMAGE) --reset -p yes; \ + else \ + docker run --rm --privileged $(BINFMT_IMAGE) --install all; \ + fi ;; \ + esac + +# By default, docker buildx create will pull image moby/buildkit:buildx-stable-1 and hit the too many requests error +# Always select and bootstrap the named builder, not only on creation: if the +# builder already exists but another builder is current, the subsequent +# `docker buildx build` would silently run on the wrong builder (potentially a +# docker-driver one with no multi-platform support). +.PHONY: docker-buildx-builder +docker-buildx-builder: setup-qemu + @if ! docker buildx ls | grep -q "$(BUILDX_BUILDER_NAME)"; then \ + docker buildx create --driver-opt image=mcr.microsoft.com/oss/v2/moby/buildkit:$(BUILDKIT_VERSION) --name $(BUILDX_BUILDER_NAME); \ fi + docker buildx use $(BUILDX_BUILDER_NAME) + docker buildx inspect $(BUILDX_BUILDER_NAME) --bootstrap .PHONY: docker-build-hub-agent docker-build-hub-agent: docker-buildx-builder ## Build hub-agent image docker buildx build \ --file docker/$(HUB_AGENT_IMAGE_NAME).Dockerfile \ --output=$(OUTPUT_TYPE) \ - --platform=$(TARGET_OS)/$(TARGET_ARCH) \ + --platform=$(PLATFORMS) \ --pull \ --tag $(REGISTRY)/$(HUB_AGENT_IMAGE_NAME):$(HUB_AGENT_IMAGE_VERSION) \ - --progress=$(BUILDKIT_PROGRESS_TYPE) \ - --build-arg GOARCH=$(TARGET_ARCH) \ - --build-arg GOOS=$(TARGET_OS) . + $(if $(IMAGE_EXTRA_TAG),--tag $(REGISTRY)/$(HUB_AGENT_IMAGE_NAME):$(IMAGE_EXTRA_TAG)) \ + --progress=$(BUILDKIT_PROGRESS_TYPE) . .PHONY: docker-build-member-agent docker-build-member-agent: docker-buildx-builder ## Build member-agent image docker buildx build \ --file docker/$(MEMBER_AGENT_IMAGE_NAME).Dockerfile \ --output=$(OUTPUT_TYPE) \ - --platform=$(TARGET_OS)/$(TARGET_ARCH) \ + --platform=$(PLATFORMS) \ --pull \ --tag $(REGISTRY)/$(MEMBER_AGENT_IMAGE_NAME):$(MEMBER_AGENT_IMAGE_VERSION) \ - --progress=$(BUILDKIT_PROGRESS_TYPE) \ - --build-arg GOARCH=$(TARGET_ARCH) \ - --build-arg GOOS=$(TARGET_OS) . + $(if $(IMAGE_EXTRA_TAG),--tag $(REGISTRY)/$(MEMBER_AGENT_IMAGE_NAME):$(IMAGE_EXTRA_TAG)) \ + --progress=$(BUILDKIT_PROGRESS_TYPE) . .PHONY: docker-build-refresh-token docker-build-refresh-token: docker-buildx-builder ## Build refresh-token image docker buildx build \ --file docker/$(REFRESH_TOKEN_IMAGE_NAME).Dockerfile \ --output=$(OUTPUT_TYPE) \ - --platform=$(TARGET_OS)/$(TARGET_ARCH) \ + --platform=$(PLATFORMS) \ --pull \ --tag $(REGISTRY)/$(REFRESH_TOKEN_IMAGE_NAME):$(REFRESH_TOKEN_IMAGE_VERSION) \ - --progress=$(BUILDKIT_PROGRESS_TYPE) \ - --build-arg GOARCH=$(TARGET_ARCH) \ - --build-arg GOOS=${TARGET_OS} . + $(if $(IMAGE_EXTRA_TAG),--tag $(REGISTRY)/$(REFRESH_TOKEN_IMAGE_NAME):$(IMAGE_EXTRA_TAG)) \ + --progress=$(BUILDKIT_PROGRESS_TYPE) . ## ----------------------------------- ## Cleanup diff --git a/docker/hub-agent.Dockerfile b/docker/hub-agent.Dockerfile index 62e5ed9c5..5a33ab8a9 100644 --- a/docker/hub-agent.Dockerfile +++ b/docker/hub-agent.Dockerfile @@ -1,8 +1,12 @@ +# syntax=docker/dockerfile:1 # Build the hubagent binary FROM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder -ARG GOOS=linux -ARG GOARCH=amd64 +# TARGETOS and TARGETARCH are populated automatically by BuildKit for each +# platform being built, so a single multi-platform `docker buildx build` +# produces a correctly built binary per architecture. +ARG TARGETOS +ARG TARGETARCH WORKDIR /workspace # Copy the Go Modules manifests @@ -13,19 +17,23 @@ COPY go.sum go.sum RUN go mod download # Copy the go source -COPY cmd/hubagent/ cmd/hubagent/ +COPY cmd/hubagent/ cmd/hubagent/ COPY apis/ apis/ COPY pkg/ pkg/ -# Build -RUN echo "Building images with GOOS=$GOOS GOARCH=$GOARCH" -RUN CGO_ENABLED=1 GOOS=$GOOS GOARCH=$GOARCH GOEXPERIMENT=systemcrypto GO111MODULE=on go build -o hubagent cmd/hubagent/main.go +# Build. CGO + systemcrypto compiles against the target architecture's OpenSSL, +# which is why the builder runs per-target under emulation (see the Makefile's +# docker-buildx-builder / setup-qemu targets) rather than cross-compiling. +RUN echo "Building hubagent with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ + CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o hubagent ./cmd/hubagent/ -# Use distroless as minimal base image to package the hubagent binary +# Use distroless as minimal base image to package the hubagent binary. +# The pinned digest must reference a multi-arch image index so BuildKit can +# resolve the matching base layer for each target architecture. # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/base:nonroot@sha256:97b9d04bed1c754b756c3c4b6a04915c22fb0b5d96a59944eb3bf78c26e6e157 WORKDIR / -COPY --from=builder /workspace/hubagent . +COPY --link --from=builder /workspace/hubagent . USER 65532:65532 ENTRYPOINT ["/hubagent"] diff --git a/docker/member-agent.Dockerfile b/docker/member-agent.Dockerfile index a2efd1282..5059820df 100644 --- a/docker/member-agent.Dockerfile +++ b/docker/member-agent.Dockerfile @@ -1,8 +1,12 @@ +# syntax=docker/dockerfile:1 # Build the memberagent binary FROM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder -ARG GOOS=linux -ARG GOARCH=amd64 +# TARGETOS and TARGETARCH are populated automatically by BuildKit for each +# platform being built, so a single multi-platform `docker buildx build` +# produces a correctly built binary per architecture. +ARG TARGETOS +ARG TARGETARCH WORKDIR /workspace # Copy the Go Modules manifests @@ -17,15 +21,19 @@ COPY cmd/memberagent cmd/memberagent/ COPY apis/ apis/ COPY pkg/ pkg/ -# Build -RUN echo "Building images with GOOS=$GOOS GOARCH=$GOARCH" -RUN CGO_ENABLED=1 GOOS=$GOOS GOARCH=$GOARCH GOEXPERIMENT=systemcrypto GO111MODULE=on go build -o memberagent cmd/memberagent/main.go +# Build. CGO + systemcrypto compiles against the target architecture's OpenSSL, +# which is why the builder runs per-target under emulation (see the Makefile's +# docker-buildx-builder / setup-qemu targets) rather than cross-compiling. +RUN echo "Building memberagent with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ + CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o memberagent ./cmd/memberagent/ -# Use distroless as minimal base image to package the memberagent binary +# Use distroless as minimal base image to package the memberagent binary. +# The pinned digest must reference a multi-arch image index so BuildKit can +# resolve the matching base layer for each target architecture. # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/base:nonroot@sha256:97b9d04bed1c754b756c3c4b6a04915c22fb0b5d96a59944eb3bf78c26e6e157 WORKDIR / -COPY --from=builder /workspace/memberagent . +COPY --link --from=builder /workspace/memberagent . USER 65532:65532 ENTRYPOINT ["/memberagent"] diff --git a/docker/refresh-token.Dockerfile b/docker/refresh-token.Dockerfile index a42e04473..b7918db7f 100644 --- a/docker/refresh-token.Dockerfile +++ b/docker/refresh-token.Dockerfile @@ -1,8 +1,12 @@ +# syntax=docker/dockerfile:1 # Build the refreshtoken binary FROM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder -ARG GOOS="linux" -ARG GOARCH="amd64" +# TARGETOS and TARGETARCH are populated automatically by BuildKit for each +# platform being built, so a single multi-platform `docker buildx build` +# produces a correctly built binary per architecture. +ARG TARGETOS +ARG TARGETARCH WORKDIR /workspace # Copy the Go Modules manifests @@ -18,17 +22,19 @@ COPY pkg/authtoken pkg/authtoken # writefile is a dependency of pkg/authtoken for secure file creation (0600 permissions) COPY pkg/utils/writefile pkg/utils/writefile -ARG TARGETARCH - -# Build -RUN echo "Building images with GOOS=${GOOS} GOARCH=${GOARCH}" -RUN CGO_ENABLED=1 GOOS=$GOOS GOARCH=$GOARCH GOEXPERIMENT=systemcrypto GO111MODULE=on go build -o refreshtoken main.go +# Build. CGO + systemcrypto compiles against the target architecture's OpenSSL, +# which is why the builder runs per-target under emulation (see the Makefile's +# docker-buildx-builder / setup-qemu targets) rather than cross-compiling. +RUN echo "Building refreshtoken with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ + CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o refreshtoken . -# Use distroless as minimal base image to package the refreshtoken binary +# Use distroless as minimal base image to package the refreshtoken binary. +# The pinned digest must reference a multi-arch image index so BuildKit can +# resolve the matching base layer for each target architecture. # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/base:nonroot@sha256:97b9d04bed1c754b756c3c4b6a04915c22fb0b5d96a59944eb3bf78c26e6e157 WORKDIR / -COPY --from=builder /workspace/refreshtoken . +COPY --link --from=builder /workspace/refreshtoken . USER 65532:65532 ENTRYPOINT ["/refreshtoken"] From 9bcf5b232af4887e9df2d9ff5a5cae6b918ecdf2 Mon Sep 17 00:00:00 2001 From: Yetkin Timocin Date: Wed, 26 Aug 2026 16:10:59 -0700 Subject: [PATCH 04/12] fix: repin qemu-user-static to a content-addressable digest (#851) The setup-qemu target added in #745 pinned the MCR qemu-user-static mirror to sha256:cb0dff99, which no registry can serve: docker: Error response from daemon: manifest for mcr.microsoft.com/ mirror/docker/multiarch/qemu-user-static@sha256:cb0dff99... not found: manifest unknown The image has no manifest list, upstream or mirrored. When a request carries no Accept header, or names only the manifest-list media type, MCR answers with a deprecated schema-1 `v1+prettyjws` view synthesised on demand from the schema-2 manifest. The digest that view advertises is stable, but it is never stored as a manifest revision, so fetching by it 404s: GET /manifests/7.2.0-1 (Accept: manifest.list.v2) -> cb0dff99, v1+prettyjws GET /manifests/sha256:cb0dff99... -> HTTP 404 GET /manifests/7.2.0-1 (Accept: manifest.v2) -> fe60359c, schema 2 GET /manifests/sha256:fe60359c... -> HTTP 200 Repin to the real schema-2 manifest (linux/amd64, entrypoint /register), which is all setup-qemu needs since the QEMU_IMAGE branch runs only when TARGET_ARCH is amd64. The new digest is the one Docker Hub also serves for multiarch/qemu-user-static:7.2.0-1, so the mirror and upstream agree. Document how to obtain the digest, and which tools report it correctly, so the next version bump does not reintroduce the schema-1 value. Signed-off-by: Yetkin Timocin --- Makefile | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 392ff6179..089512c02 100644 --- a/Makefile +++ b/Makefile @@ -253,8 +253,29 @@ BUILDKIT_VERSION ?= v0.18.1 # and are pinned by digest as well as tag: a floating reference on a privileged # release-path container is a supply-chain risk. The binfmt mirror digest is # byte-identical to the upstream docker.io/tonistiigi/binfmt tag it mirrors. +# +# qemu-user-static has no manifest list - neither upstream nor on the mirror. It +# is a single linux/amd64 schema-2 manifest, which is all setup-qemu needs: the +# QEMU_IMAGE branch runs only when TARGET_ARCH is amd64. Obtain its digest with a +# request that names a schema-2 or OCI media type (substitute QEMU_VERSION for the +# tag): +# +# curl -sI -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \ +# https://mcr.microsoft.com/v2/mirror/docker/multiarch/qemu-user-static/manifests/7.2.0-1 \ +# | grep -i docker-content-digest +# +# `docker manifest inspect -v` and `docker buildx imagetools inspect` also report +# the correct digest. What does NOT is a bare `curl -sI` sending no Accept header, +# or one naming only the manifest-list type: MCR then answers with a deprecated +# schema-1 `v1+prettyjws` view synthesised on demand from the schema-2 manifest. +# The digest it advertises is stable, but it is never stored as a manifest +# revision, so fetching by it returns "manifest unknown" and every pull of a pin +# taken that way fails - most likely how the previous pin here was produced. +# +# Both mirrors carry exactly one tag today, so bumping either version below needs +# MCR to onboard the new tag first. QEMU_VERSION ?= 7.2.0-1 -QEMU_IMAGE ?= mcr.microsoft.com/mirror/docker/multiarch/qemu-user-static:$(QEMU_VERSION)@sha256:cb0dff994856c640b6080bfbd5983352e7856221a989625ea3e232cb7eff4507 +QEMU_IMAGE ?= mcr.microsoft.com/mirror/docker/multiarch/qemu-user-static:$(QEMU_VERSION)@sha256:fe60359c92e86a43cc87b3d906006245f77bfc0565676b80004cc666e4feb9f0 BINFMT_VERSION ?= qemu-v9.2.2-52 BINFMT_IMAGE ?= mcr.microsoft.com/mirror/docker/tonistiigi/binfmt:$(BINFMT_VERSION)@sha256:1b804311fe87047a4c96d38b4b3ef6f62fca8cd125265917a9e3dc3c996c39e6 From 898e0bbbf6950fe5ec12c2cbb5be29b56cca38cc Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Thu, 27 Aug 2026 07:56:14 +0800 Subject: [PATCH 05/12] fix: skip access provider population in cluster profiles if no endpoint/CA data exists (#849) --- apis/cluster/v1beta1/zz_generated.deepcopy.go | 2 +- .../v1alpha1/zz_generated.deepcopy.go | 2 +- .../v1alpha1/zz_generated.deepcopy.go | 2 +- .../v1beta1/zz_generated.deepcopy.go | 2 +- .../clusterprofile/controller.go | 44 +++++----- .../controller_integration_test.go | 4 + .../clusterprofile/controller_test.go | 80 ++++++++++++++++++- test/apis/v1alpha1/zz_generated.deepcopy.go | 2 +- test/e2e/join_and_leave_test.go | 13 +-- 9 files changed, 111 insertions(+), 40 deletions(-) diff --git a/apis/cluster/v1beta1/zz_generated.deepcopy.go b/apis/cluster/v1beta1/zz_generated.deepcopy.go index a51641c3f..cec52aa39 100644 --- a/apis/cluster/v1beta1/zz_generated.deepcopy.go +++ b/apis/cluster/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/apis/kubefleet.dev/placement/v1alpha1/zz_generated.deepcopy.go b/apis/kubefleet.dev/placement/v1alpha1/zz_generated.deepcopy.go index 429b4e7fa..19f036041 100644 --- a/apis/kubefleet.dev/placement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/kubefleet.dev/placement/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1alpha1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) diff --git a/apis/placement/v1alpha1/zz_generated.deepcopy.go b/apis/placement/v1alpha1/zz_generated.deepcopy.go index 6d1656d18..df9f5e6d7 100644 --- a/apis/placement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/placement/v1alpha1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1alpha1 import ( "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) diff --git a/apis/placement/v1beta1/zz_generated.deepcopy.go b/apis/placement/v1beta1/zz_generated.deepcopy.go index 73d66c8fa..b9ff2e710 100644 --- a/apis/placement/v1beta1/zz_generated.deepcopy.go +++ b/apis/placement/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) diff --git a/pkg/controllers/clusterinventory/clusterprofile/controller.go b/pkg/controllers/clusterinventory/clusterprofile/controller.go index 94517e40d..66c23f342 100644 --- a/pkg/controllers/clusterinventory/clusterprofile/controller.go +++ b/pkg/controllers/clusterinventory/clusterprofile/controller.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" "k8s.io/client-go/util/workqueue" "k8s.io/klog/v2" clusterinventory "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" @@ -180,8 +181,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{}, nil } -// fillInClusterStatus fills in the ClusterProfile status fields from the MemberCluster status. -// Currently, it only fills in the Kubernetes version field. +// fillInClusterStatus fills in the ClusterProfile status fields from the MemberCluster status, +// including the Kubernetes version and, when available, the cluster access provider. func (r *Reconciler) fillInClusterStatus(mc *clusterv1beta1.MemberCluster, cp *clusterinventory.ClusterProfile) { clusterPropertyCondition := meta.FindStatusCondition(mc.Status.Conditions, string(clusterv1beta1.ConditionTypeClusterPropertyCollectionSucceeded)) if !condition.IsConditionStatusTrue(clusterPropertyCondition, mc.Generation) { @@ -196,27 +197,26 @@ func (r *Reconciler) fillInClusterStatus(mc *clusterv1beta1.MemberCluster, cp *c Kubernetes: k8sversion.Value, } } - // Add the class access provider, we only have one so far - cp.Status.AccessProviders = []clusterinventory.AccessProvider{ - { - Name: controller.ClusterManagerName, - }, - } - // TODO throw and unexpected error if clusterEntryPoint is not found - // We don't have a way to get it yet - clusterEntry, exists := mc.Status.Properties[propertyprovider.ClusterEntryPointProperty] - if exists { - klog.V(3).InfoS("Get Kubernetes cluster entry point from member cluster status", "clusterEntryPoint", clusterEntry.Value, "clusterProfile", klog.KObj(cp)) - cp.Status.AccessProviders[0].Cluster.Server = clusterEntry.Value - } - // Get the CA Data - certificateAuthorityData, exists := mc.Status.Properties[propertyprovider.ClusterCertificateAuthorityProperty] - if exists { - klog.V(3).InfoS("Get Kubernetes cluster certificate authority data from member cluster status", "clusterProfile", klog.KObj(cp)) - cp.Status.AccessProviders[0].Cluster.CertificateAuthorityData = []byte(certificateAuthorityData.Value) + + // Add cluster access provider, if and only if a cluster entry point and the CA data exist as part of the + // cluster properties. + clusterEntrypoint, entryPtExists := mc.Status.Properties[propertyprovider.ClusterEntryPointProperty] + caData, caDataExists := mc.Status.Properties[propertyprovider.ClusterCertificateAuthorityProperty] + if entryPtExists && caDataExists && len(clusterEntrypoint.Value) > 0 && len(caData.Value) > 0 { + cp.Status.AccessProviders = []clusterinventory.AccessProvider{ + { + Name: controller.ClusterManagerName, + Cluster: clientcmdv1.Cluster{ + Server: clusterEntrypoint.Value, + CertificateAuthorityData: []byte(caData.Value), + }, + }, + } } else { - // throw an alert - _ = controller.NewUnexpectedBehaviorError(fmt.Errorf("cluster certificate authority data not found in member cluster %s status", mc.Name)) + cp.Status.AccessProviders = nil + klog.V(2).InfoS("Cluster entry point and/or CA data is missing or empty; reset cluster access provider to cluster profile status", + "memberCluster", klog.KObj(mc), "clusterProfile", klog.KObj(cp), + "clusterEntryPointExists", entryPtExists, "caDataExists", caDataExists) } } diff --git a/pkg/controllers/clusterinventory/clusterprofile/controller_integration_test.go b/pkg/controllers/clusterinventory/clusterprofile/controller_integration_test.go index 49c13c571..b855c6165 100644 --- a/pkg/controllers/clusterinventory/clusterprofile/controller_integration_test.go +++ b/pkg/controllers/clusterinventory/clusterprofile/controller_integration_test.go @@ -171,6 +171,10 @@ var _ = Describe("Test ClusterProfile Controller", func() { }, } mc.Status.Properties = map[clusterv1beta1.PropertyName]clusterv1beta1.PropertyValue{ + propertyprovider.ClusterEntryPointProperty: { + Value: "https://dummy-cluster-endpoint", + ObservationTime: metav1.Time{Time: time.Now()}, + }, propertyprovider.ClusterCertificateAuthorityProperty: { Value: "dummy-ca-data", ObservationTime: metav1.Time{Time: time.Now()}, diff --git a/pkg/controllers/clusterinventory/clusterprofile/controller_test.go b/pkg/controllers/clusterinventory/clusterprofile/controller_test.go index b51ca2a2a..fed86ef32 100644 --- a/pkg/controllers/clusterinventory/clusterprofile/controller_test.go +++ b/pkg/controllers/clusterinventory/clusterprofile/controller_test.go @@ -23,6 +23,7 @@ import ( "github.com/google/go-cmp/cmp" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientcmdv1 "k8s.io/client-go/tools/clientcmd/api/v1" clusterinventory "sigs.k8s.io/cluster-inventory-api/apis/v1alpha1" clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" @@ -84,7 +85,7 @@ func TestFillInClusterStatus(t *testing.T) { }, clusterProfile: &clusterinventory.ClusterProfile{}, expectVersion: false, - expectAccessProvider: true, + expectAccessProvider: false, }, { name: "Cluster property collection succeeded with k8s version only", @@ -111,7 +112,7 @@ func TestFillInClusterStatus(t *testing.T) { clusterProfile: &clusterinventory.ClusterProfile{}, expectVersion: true, expectedK8sVersion: "v1.28.0", - expectAccessProvider: true, + expectAccessProvider: false, }, { name: "Cluster property collection succeeded with all properties", @@ -176,8 +177,79 @@ func TestFillInClusterStatus(t *testing.T) { clusterProfile: &clusterinventory.ClusterProfile{}, expectVersion: true, expectedK8sVersion: "v1.27.5", - expectAccessProvider: true, - expectedServer: "https://api.partial-cluster.example.com:6443", + expectAccessProvider: false, + }, + { + name: "Cluster property collection succeeded but access provider properties are empty", + memberCluster: &clusterv1beta1.MemberCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Generation: 1, + }, + Status: clusterv1beta1.MemberClusterStatus{ + Conditions: []metav1.Condition{ + { + Type: string(clusterv1beta1.ConditionTypeClusterPropertyCollectionSucceeded), + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + }, + Properties: map[clusterv1beta1.PropertyName]clusterv1beta1.PropertyValue{ + propertyprovider.K8sVersionProperty: { + Value: "v1.30.0", + }, + propertyprovider.ClusterEntryPointProperty: { + Value: "", + }, + propertyprovider.ClusterCertificateAuthorityProperty: { + Value: "", + }, + }, + }, + }, + clusterProfile: &clusterinventory.ClusterProfile{}, + expectVersion: true, + expectedK8sVersion: "v1.30.0", + expectAccessProvider: false, + }, + { + name: "Access provider properties missing resets a previously populated access provider", + memberCluster: &clusterv1beta1.MemberCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Generation: 1, + }, + Status: clusterv1beta1.MemberClusterStatus{ + Conditions: []metav1.Condition{ + { + Type: string(clusterv1beta1.ConditionTypeClusterPropertyCollectionSucceeded), + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + }, + }, + Properties: map[clusterv1beta1.PropertyName]clusterv1beta1.PropertyValue{ + propertyprovider.K8sVersionProperty: { + Value: "v1.31.0", + }, + }, + }, + }, + clusterProfile: &clusterinventory.ClusterProfile{ + Status: clusterinventory.ClusterProfileStatus{ + AccessProviders: []clusterinventory.AccessProvider{ + { + Name: controller.ClusterManagerName, + Cluster: clientcmdv1.Cluster{ + Server: "https://api.stale-cluster.example.com:6443", + CertificateAuthorityData: []byte("c3RhbGUtY2EtZGF0YQ=="), + }, + }, + }, + }, + }, + expectVersion: true, + expectedK8sVersion: "v1.31.0", + expectAccessProvider: false, }, } diff --git a/test/apis/v1alpha1/zz_generated.deepcopy.go b/test/apis/v1alpha1/zz_generated.deepcopy.go index 081bec913..143bdee7b 100644 --- a/test/apis/v1alpha1/zz_generated.deepcopy.go +++ b/test/apis/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1alpha1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/test/e2e/join_and_leave_test.go b/test/e2e/join_and_leave_test.go index ce715bf2d..4cfc8260b 100644 --- a/test/e2e/join_and_leave_test.go +++ b/test/e2e/join_and_leave_test.go @@ -35,7 +35,6 @@ import ( clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" "github.com/kubefleet-dev/kubefleet/pkg/utils" - "github.com/kubefleet-dev/kubefleet/pkg/utils/controller" ) const ( @@ -462,14 +461,10 @@ var _ = Describe("Test member cluster join and leave with clusterProfile", Label if cp.Status.Version.Kubernetes == "" { return fmt.Errorf("cluster profile %s Kubernetes version should not be empty", cp.Name) } - if len(cp.Status.AccessProviders) != 1 { - return fmt.Errorf("cluster profile %s has no access providers %+v", cp.Name, cp.Status.AccessProviders) - } - if cp.Status.AccessProviders[0].Name != controller.ClusterManagerName { - return fmt.Errorf("cluster profile %s access provider name %s doesn't match expected %s", cp.Name, cp.Status.AccessProviders[0].Name, controller.ClusterManagerName) - } - if len(cp.Status.AccessProviders[0].Cluster.CertificateAuthorityData) == 0 { - return fmt.Errorf("cluster profile %s access provider certificate authority data should not be empty", allMemberClusterNames[idx]) + if len(cp.Status.AccessProviders) != 0 { + // Note: at this moment the Azure property provider does not expose cluster FQDNs, and + // as a result no access provider will be populated. + return fmt.Errorf("cluster profile %s has access providers %+v", cp.Name, cp.Status.AccessProviders) } } return nil From 431ba73fa9a934d38a4d76d7560cca011ed0c25e Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Thu, 27 Aug 2026 08:16:45 +0800 Subject: [PATCH 06/12] interface: [FEP-0001] renamed Cluster Requests API (now Cluster Claims) (#803) Renamed API types Signed-off-by: michaelawyu --- apis/cluster/v1beta1/zz_generated.deepcopy.go | 2 +- ...request_types.go => clusterclaim_types.go} | 46 ++-- .../v1alpha1/placementpolicy_types.go | 22 +- .../v1alpha1/zz_generated.deepcopy.go | 240 +++++++++--------- .../v1alpha1/zz_generated.deepcopy.go | 2 +- .../v1beta1/zz_generated.deepcopy.go | 2 +- ...lacement.kubefleet.dev_clusterclaims.yaml} | 36 +-- ...ubefleet.dev_clusterplacementpolicies.yaml | 24 +- ...ement.kubefleet.dev_placementpolicies.yaml | 24 +- test/apis/v1alpha1/zz_generated.deepcopy.go | 2 +- 10 files changed, 200 insertions(+), 200 deletions(-) rename apis/kubefleet.dev/placement/v1alpha1/{clusterrequest_types.go => clusterclaim_types.go} (68%) rename config/crd/bases/{placement.kubefleet.dev_clusterrequests.yaml => placement.kubefleet.dev_clusterclaims.yaml} (93%) diff --git a/apis/cluster/v1beta1/zz_generated.deepcopy.go b/apis/cluster/v1beta1/zz_generated.deepcopy.go index cec52aa39..a51641c3f 100644 --- a/apis/cluster/v1beta1/zz_generated.deepcopy.go +++ b/apis/cluster/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - v1 "k8s.io/api/core/v1" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/apis/kubefleet.dev/placement/v1alpha1/clusterrequest_types.go b/apis/kubefleet.dev/placement/v1alpha1/clusterclaim_types.go similarity index 68% rename from apis/kubefleet.dev/placement/v1alpha1/clusterrequest_types.go rename to apis/kubefleet.dev/placement/v1alpha1/clusterclaim_types.go index 601185319..c9f3f9ab6 100644 --- a/apis/kubefleet.dev/placement/v1alpha1/clusterrequest_types.go +++ b/apis/kubefleet.dev/placement/v1alpha1/clusterclaim_types.go @@ -21,10 +21,10 @@ import ( ) const ( - ClusterRequestCondTypeCompleted = "Completed" + ClusterClaimCondTypeCompleted = "Completed" ) -// ClusterRequest is a KubeFleet API that represents a request for a member cluster to be provisioned. +// ClusterClaim is a KubeFleet API that represents a claim for a new member cluster. // It is created by KubeFleet when it fails to find a member cluster that can fulfill some scheduling // requirements as specified in a PlacementPolicy or ClusterPlacementPolicy object. // @@ -33,21 +33,21 @@ const ( // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Cluster,categories={kubefleet, kubefleet-placement} // +kubebuilder:storageversion -type ClusterRequest struct { +type ClusterClaim struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // The specification of the cluster request. + // The specification of the cluster claim. // +kubebuilder:validation:Required - Spec ClusterRequestSpec `json:"spec,omitempty"` + Spec ClusterClaimSpec `json:"spec,omitempty"` - // The observed status of the cluster request. + // The observed status of the cluster claim. // +kubebuilder:validation:Optional - Status ClusterRequestStatus `json:"status,omitempty"` + Status ClusterClaimStatus `json:"status,omitempty"` } -type ClusterRequestSpec struct { - // The reference to the placement policy that submits the cluster request. +type ClusterClaimSpec struct { + // The reference to the placement policy that adds the cluster claim. // // This field is immutable after creation. // @@ -57,7 +57,7 @@ type ClusterRequestSpec struct { // The cluster selector terms that describe the requirements for a new member cluster. // - // If not specified, any member cluster can satisfy the request. + // If not specified, any member cluster can satisfy the claim. // // This field is immutable after creation. // @@ -66,42 +66,42 @@ type ClusterRequestSpec struct { ClusterSelectorTerms []ClusterLabelAndPropertySelectorTerm `json:"clusterSelectorTerms,omitempty"` } -type ClusterRequestStatus struct { - // A list of observed conditions of the cluster request. +type ClusterClaimStatus struct { + // A list of observed conditions of the cluster claim. // // +kubebuilder:validation:Optional Conditions []metav1.Condition `json:"conditions,omitempty"` - // The name of the cluster that has been provisioned for this cluster request, if any. + // The name of the cluster that has been provisioned for this cluster claim, if any. // // +kubebuilder:validation:Optional ProvisionedClusterName *string `json:"provisionedClusterName,omitempty"` // The last observed most recent creation timestamp across all the member clusters. This field is used - // as an expedient solution to verify if a cluster request is still valid for consideration, i.e., + // as an expedient solution to verify if a cluster claim is still valid for consideration, i.e., // if the currently observed most recent cluster creation timestamp is later than this timestamp in the - // status, a new member cluster must have been created after the cluster request was created, - // and thus the cluster request should be considered stale and can be ignored. The placement policy - // that submits the cluster request is responsible for updating this field if the new cluster does not - // meet the need of the associated cluster selector, so that the cluster request can be re-evaluated again; - // it may instead withdraw the cluster request if the new cluster has fulfilled the associated cluster selector. + // status, a new member cluster must have been created after the cluster claim was created, + // and thus the cluster claim should be considered stale and can be ignored. The placement policy + // that adds the cluster claim is responsible for updating this field if the new cluster does not + // meet the need of the associated cluster selector, so that the cluster claim can be re-evaluated again; + // it may instead withdraw the cluster claim if the new cluster has fulfilled the associated cluster selector. // // +kubebuilder:validation:Optional LastObservedMostRecentClusterCreationTimestamp *metav1.Time `json:"lastObservedMostRecentClusterCreationTimestamp,omitempty"` } -// ClusterRequestList contains a list of ClusterRequest. +// ClusterClaimList contains a list of ClusterClaim. // // +kubebuilder:object:root=true // +kubebuilder:resource:scope="Cluster" // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ClusterRequestList struct { +type ClusterClaimList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` - Items []ClusterRequest `json:"items"` + Items []ClusterClaim `json:"items"` } func init() { - SchemeBuilder.Register(&ClusterRequest{}, &ClusterRequestList{}) + SchemeBuilder.Register(&ClusterClaim{}, &ClusterClaimList{}) } diff --git a/apis/kubefleet.dev/placement/v1alpha1/placementpolicy_types.go b/apis/kubefleet.dev/placement/v1alpha1/placementpolicy_types.go index 5d47fa4af..f29abb1cf 100644 --- a/apis/kubefleet.dev/placement/v1alpha1/placementpolicy_types.go +++ b/apis/kubefleet.dev/placement/v1alpha1/placementpolicy_types.go @@ -184,16 +184,16 @@ type ClusterSelector struct { // The action to take when KubeFleet is not able to find the desired (minimum) number of clusters based on the given terms. // // Available options are: - // * RequestCluster: KubeFleet will submit a cluster request to signal that a new cluster is needed to complete the placement. - // It is up to the platform/cloud provider to fulfill the request. - // * KeepSearching: KubeFleet will keep searching for clusters that match the given terms silently; no cluster request will be - // submitted. + // * AddClusterClaim: KubeFleet will add a cluster claim to signal that a new cluster is needed to complete the placement. + // It is up to the platform/cloud provider to fulfill the claim. + // * KeepSearching: KubeFleet will keep searching for clusters that match the given terms silently; no cluster claim will be + // added. // - // This field takes effect only when cluster requests are enabled in KubeFleet. + // This field takes effect only when cluster claims are enabled in KubeFleet. // // +kubebuilder:validation:Optional - // +kubebuilder:default=RequestCluster - // +kubebuilder:validation:Enum=RequestCluster;KeepSearching + // +kubebuilder:default=AddClusterClaim + // +kubebuilder:validation:Enum=AddClusterClaim;KeepSearching WhenUnfulfilled WhenUnfulfilledOption `json:"whenUnfulfilled,omitempty"` } @@ -271,8 +271,8 @@ const ( type WhenUnfulfilledOption string const ( - WhenUnfulfilledOptionRequestCluster WhenUnfulfilledOption = "RequestCluster" - WhenUnfulfilledOptionKeepSearching WhenUnfulfilledOption = "KeepSearching" + WhenUnfulfilledOptionAddClusterClaim WhenUnfulfilledOption = "AddClusterClaim" + WhenUnfulfilledOptionKeepSearching WhenUnfulfilledOption = "KeepSearching" ) type ResourceSelector struct { @@ -547,8 +547,8 @@ type PlacementPolicyStatus struct { // The number of clusters that have resources in the available state, as verified by KubeFleet's availability check. ResourcesAvailableClusters *int32 `json:"resourcesAvailableClusters,omitempty"` - // The number of ongoing cluster requests that have been submitted by this placement. - OngoingClusterRequests *int32 `json:"ongoingClusterRequests,omitempty"` + // The number of active cluster claims that have been submitted by this placement. + ActiveClusterClaims *int32 `json:"activeClusterClaims,omitempty"` // The binding manager that is currently managing the bindings for this placement. // +kubebuilder:validation:Optional diff --git a/apis/kubefleet.dev/placement/v1alpha1/zz_generated.deepcopy.go b/apis/kubefleet.dev/placement/v1alpha1/zz_generated.deepcopy.go index 19f036041..8dac3f795 100644 --- a/apis/kubefleet.dev/placement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/kubefleet.dev/placement/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1alpha1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -46,6 +46,123 @@ func (in *BindingManager) DeepCopy() *BindingManager { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterClaim) DeepCopyInto(out *ClusterClaim) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterClaim. +func (in *ClusterClaim) DeepCopy() *ClusterClaim { + if in == nil { + return nil + } + out := new(ClusterClaim) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterClaim) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterClaimList) DeepCopyInto(out *ClusterClaimList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ClusterClaim, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterClaimList. +func (in *ClusterClaimList) DeepCopy() *ClusterClaimList { + if in == nil { + return nil + } + out := new(ClusterClaimList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ClusterClaimList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterClaimSpec) DeepCopyInto(out *ClusterClaimSpec) { + *out = *in + if in.PlacementPolicyRef != nil { + in, out := &in.PlacementPolicyRef, &out.PlacementPolicyRef + *out = new(ObjectReference) + **out = **in + } + if in.ClusterSelectorTerms != nil { + in, out := &in.ClusterSelectorTerms, &out.ClusterSelectorTerms + *out = make([]ClusterLabelAndPropertySelectorTerm, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterClaimSpec. +func (in *ClusterClaimSpec) DeepCopy() *ClusterClaimSpec { + if in == nil { + return nil + } + out := new(ClusterClaimSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClusterClaimStatus) DeepCopyInto(out *ClusterClaimStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ProvisionedClusterName != nil { + in, out := &in.ProvisionedClusterName, &out.ProvisionedClusterName + *out = new(string) + **out = **in + } + if in.LastObservedMostRecentClusterCreationTimestamp != nil { + in, out := &in.LastObservedMostRecentClusterCreationTimestamp, &out.LastObservedMostRecentClusterCreationTimestamp + *out = (*in).DeepCopy() + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterClaimStatus. +func (in *ClusterClaimStatus) DeepCopy() *ClusterClaimStatus { + if in == nil { + return nil + } + out := new(ClusterClaimStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterLabelAndPropertySelectorTerm) DeepCopyInto(out *ClusterLabelAndPropertySelectorTerm) { *out = *in @@ -258,123 +375,6 @@ func (in *ClusterPlacementResourceSnapshotList) DeepCopyObject() runtime.Object return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRequest) DeepCopyInto(out *ClusterRequest) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRequest. -func (in *ClusterRequest) DeepCopy() *ClusterRequest { - if in == nil { - return nil - } - out := new(ClusterRequest) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterRequest) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRequestList) DeepCopyInto(out *ClusterRequestList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterRequest, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRequestList. -func (in *ClusterRequestList) DeepCopy() *ClusterRequestList { - if in == nil { - return nil - } - out := new(ClusterRequestList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterRequestList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRequestSpec) DeepCopyInto(out *ClusterRequestSpec) { - *out = *in - if in.PlacementPolicyRef != nil { - in, out := &in.PlacementPolicyRef, &out.PlacementPolicyRef - *out = new(ObjectReference) - **out = **in - } - if in.ClusterSelectorTerms != nil { - in, out := &in.ClusterSelectorTerms, &out.ClusterSelectorTerms - *out = make([]ClusterLabelAndPropertySelectorTerm, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRequestSpec. -func (in *ClusterRequestSpec) DeepCopy() *ClusterRequestSpec { - if in == nil { - return nil - } - out := new(ClusterRequestSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRequestStatus) DeepCopyInto(out *ClusterRequestStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ProvisionedClusterName != nil { - in, out := &in.ProvisionedClusterName, &out.ProvisionedClusterName - *out = new(string) - **out = **in - } - if in.LastObservedMostRecentClusterCreationTimestamp != nil { - in, out := &in.LastObservedMostRecentClusterCreationTimestamp, &out.LastObservedMostRecentClusterCreationTimestamp - *out = (*in).DeepCopy() - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRequestStatus. -func (in *ClusterRequestStatus) DeepCopy() *ClusterRequestStatus { - if in == nil { - return nil - } - out := new(ClusterRequestStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterSelector) DeepCopyInto(out *ClusterSelector) { *out = *in @@ -860,8 +860,8 @@ func (in *PlacementPolicyStatus) DeepCopyInto(out *PlacementPolicyStatus) { *out = new(int32) **out = **in } - if in.OngoingClusterRequests != nil { - in, out := &in.OngoingClusterRequests, &out.OngoingClusterRequests + if in.ActiveClusterClaims != nil { + in, out := &in.ActiveClusterClaims, &out.ActiveClusterClaims *out = new(int32) **out = **in } diff --git a/apis/placement/v1alpha1/zz_generated.deepcopy.go b/apis/placement/v1alpha1/zz_generated.deepcopy.go index df9f5e6d7..6d1656d18 100644 --- a/apis/placement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/placement/v1alpha1/zz_generated.deepcopy.go @@ -22,7 +22,7 @@ package v1alpha1 import ( "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) diff --git a/apis/placement/v1beta1/zz_generated.deepcopy.go b/apis/placement/v1beta1/zz_generated.deepcopy.go index b9ff2e710..73d66c8fa 100644 --- a/apis/placement/v1beta1/zz_generated.deepcopy.go +++ b/apis/placement/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) diff --git a/config/crd/bases/placement.kubefleet.dev_clusterrequests.yaml b/config/crd/bases/placement.kubefleet.dev_clusterclaims.yaml similarity index 93% rename from config/crd/bases/placement.kubefleet.dev_clusterrequests.yaml rename to config/crd/bases/placement.kubefleet.dev_clusterclaims.yaml index 98eca4564..2bd4a6ad8 100644 --- a/config/crd/bases/placement.kubefleet.dev_clusterrequests.yaml +++ b/config/crd/bases/placement.kubefleet.dev_clusterclaims.yaml @@ -4,24 +4,24 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.20.0 - name: clusterrequests.placement.kubefleet.dev + name: clusterclaims.placement.kubefleet.dev spec: group: placement.kubefleet.dev names: categories: - kubefleet - kubefleet-placement - kind: ClusterRequest - listKind: ClusterRequestList - plural: clusterrequests - singular: clusterrequest + kind: ClusterClaim + listKind: ClusterClaimList + plural: clusterclaims + singular: clusterclaim scope: Cluster versions: - name: v1alpha1 schema: openAPIV3Schema: description: |- - ClusterRequest is a KubeFleet API that represents a request for a member cluster to be provisioned. + ClusterClaim is a KubeFleet API that represents a claim for a new member cluster. It is created by KubeFleet when it fails to find a member cluster that can fulfill some scheduling requirements as specified in a PlacementPolicy or ClusterPlacementPolicy object. properties: @@ -43,13 +43,13 @@ spec: metadata: type: object spec: - description: The specification of the cluster request. + description: The specification of the cluster claim. properties: clusterSelectorTerms: description: |- The cluster selector terms that describe the requirements for a new member cluster. - If not specified, any member cluster can satisfy the request. + If not specified, any member cluster can satisfy the claim. This field is immutable after creation. items: @@ -190,7 +190,7 @@ spec: rule: self == oldSelf placementPolicyRef: description: |- - The reference to the placement policy that submits the cluster request. + The reference to the placement policy that adds the cluster claim. This field is immutable after creation. properties: @@ -221,10 +221,10 @@ spec: - placementPolicyRef type: object status: - description: The observed status of the cluster request. + description: The observed status of the cluster claim. properties: conditions: - description: A list of observed conditions of the cluster request. + description: A list of observed conditions of the cluster claim. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -283,18 +283,18 @@ spec: lastObservedMostRecentClusterCreationTimestamp: description: |- The last observed most recent creation timestamp across all the member clusters. This field is used - as an expedient solution to verify if a cluster request is still valid for consideration, i.e., + as an expedient solution to verify if a cluster claim is still valid for consideration, i.e., if the currently observed most recent cluster creation timestamp is later than this timestamp in the - status, a new member cluster must have been created after the cluster request was created, - and thus the cluster request should be considered stale and can be ignored. The placement policy - that submits the cluster request is responsible for updating this field if the new cluster does not - meet the need of the associated cluster selector, so that the cluster request can be re-evaluated again; - it may instead withdraw the cluster request if the new cluster has fulfilled the associated cluster selector. + status, a new member cluster must have been created after the cluster claim was created, + and thus the cluster claim should be considered stale and can be ignored. The placement policy + that adds the cluster claim is responsible for updating this field if the new cluster does not + meet the need of the associated cluster selector, so that the cluster claim can be re-evaluated again; + it may instead withdraw the cluster claim if the new cluster has fulfilled the associated cluster selector. format: date-time type: string provisionedClusterName: description: The name of the cluster that has been provisioned for - this cluster request, if any. + this cluster claim, if any. type: string type: object required: diff --git a/config/crd/bases/placement.kubefleet.dev_clusterplacementpolicies.yaml b/config/crd/bases/placement.kubefleet.dev_clusterplacementpolicies.yaml index 8b1f91fa3..d528f88bd 100644 --- a/config/crd/bases/placement.kubefleet.dev_clusterplacementpolicies.yaml +++ b/config/crd/bases/placement.kubefleet.dev_clusterplacementpolicies.yaml @@ -221,19 +221,19 @@ spec: maxItems: 5 type: array whenUnfulfilled: - default: RequestCluster + default: AddClusterClaim description: |- The action to take when KubeFleet is not able to find the desired (minimum) number of clusters based on the given terms. Available options are: - * RequestCluster: KubeFleet will submit a cluster request to signal that a new cluster is needed to complete the placement. - It is up to the platform/cloud provider to fulfill the request. - * KeepSearching: KubeFleet will keep searching for clusters that match the given terms silently; no cluster request will be - submitted. + * AddClusterClaim: KubeFleet will add a cluster claim to signal that a new cluster is needed to complete the placement. + It is up to the platform/cloud provider to fulfill the claim. + * KeepSearching: KubeFleet will keep searching for clusters that match the given terms silently; no cluster claim will be + added. - This field takes effect only when cluster requests are enabled in KubeFleet. + This field takes effect only when cluster claims are enabled in KubeFleet. enum: - - RequestCluster + - AddClusterClaim - KeepSearching type: string type: object @@ -549,6 +549,11 @@ spec: status: description: The observed status of the cluster placement policy. properties: + activeClusterClaims: + description: The number of active cluster claims that have been submitted + by this placement. + format: int32 + type: integer bindingManager: description: The binding manager that is currently managing the bindings for this placement. @@ -653,11 +658,6 @@ spec: description: The name of the latest revision of the resources selected by this placement policy, in the form of a resource snapshot. type: string - ongoingClusterRequests: - description: The number of ongoing cluster requests that have been - submitted by this placement. - format: int32 - type: integer resourcesAvailableClusters: description: The number of clusters that have resources in the available state, as verified by KubeFleet's availability check. diff --git a/config/crd/bases/placement.kubefleet.dev_placementpolicies.yaml b/config/crd/bases/placement.kubefleet.dev_placementpolicies.yaml index 972186615..578a6eb5f 100644 --- a/config/crd/bases/placement.kubefleet.dev_placementpolicies.yaml +++ b/config/crd/bases/placement.kubefleet.dev_placementpolicies.yaml @@ -221,19 +221,19 @@ spec: maxItems: 5 type: array whenUnfulfilled: - default: RequestCluster + default: AddClusterClaim description: |- The action to take when KubeFleet is not able to find the desired (minimum) number of clusters based on the given terms. Available options are: - * RequestCluster: KubeFleet will submit a cluster request to signal that a new cluster is needed to complete the placement. - It is up to the platform/cloud provider to fulfill the request. - * KeepSearching: KubeFleet will keep searching for clusters that match the given terms silently; no cluster request will be - submitted. + * AddClusterClaim: KubeFleet will add a cluster claim to signal that a new cluster is needed to complete the placement. + It is up to the platform/cloud provider to fulfill the claim. + * KeepSearching: KubeFleet will keep searching for clusters that match the given terms silently; no cluster claim will be + added. - This field takes effect only when cluster requests are enabled in KubeFleet. + This field takes effect only when cluster claims are enabled in KubeFleet. enum: - - RequestCluster + - AddClusterClaim - KeepSearching type: string type: object @@ -549,6 +549,11 @@ spec: status: description: The observed status of the placement policy. properties: + activeClusterClaims: + description: The number of active cluster claims that have been submitted + by this placement. + format: int32 + type: integer bindingManager: description: The binding manager that is currently managing the bindings for this placement. @@ -653,11 +658,6 @@ spec: description: The name of the latest revision of the resources selected by this placement policy, in the form of a resource snapshot. type: string - ongoingClusterRequests: - description: The number of ongoing cluster requests that have been - submitted by this placement. - format: int32 - type: integer resourcesAvailableClusters: description: The number of clusters that have resources in the available state, as verified by KubeFleet's availability check. diff --git a/test/apis/v1alpha1/zz_generated.deepcopy.go b/test/apis/v1alpha1/zz_generated.deepcopy.go index 143bdee7b..081bec913 100644 --- a/test/apis/v1alpha1/zz_generated.deepcopy.go +++ b/test/apis/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1alpha1 import ( - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) From 10e88344841847542ba959db636c4847010ab678 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:38:18 +1000 Subject: [PATCH 07/12] chore: bump distroless/base from `97b9d04` to `2d7d29b` in /docker (#852) Bumps distroless/base from `97b9d04` to `2d7d29b`. --- updated-dependencies: - dependency-name: distroless/base dependency-version: nonroot dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docker/hub-agent.Dockerfile | 2 +- docker/member-agent.Dockerfile | 2 +- docker/refresh-token.Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/hub-agent.Dockerfile b/docker/hub-agent.Dockerfile index 5a33ab8a9..6ae49a944 100644 --- a/docker/hub-agent.Dockerfile +++ b/docker/hub-agent.Dockerfile @@ -31,7 +31,7 @@ RUN echo "Building hubagent with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ # The pinned digest must reference a multi-arch image index so BuildKit can # resolve the matching base layer for each target architecture. # Refer to https://github.com/GoogleContainerTools/distroless for more details -FROM gcr.io/distroless/base:nonroot@sha256:97b9d04bed1c754b756c3c4b6a04915c22fb0b5d96a59944eb3bf78c26e6e157 +FROM gcr.io/distroless/base:nonroot@sha256:2d7d29b504e7166f6d0c7655a18ebf5def5b37b029f8c4f8667e434ba774844f WORKDIR / COPY --link --from=builder /workspace/hubagent . USER 65532:65532 diff --git a/docker/member-agent.Dockerfile b/docker/member-agent.Dockerfile index 5059820df..f0670a42d 100644 --- a/docker/member-agent.Dockerfile +++ b/docker/member-agent.Dockerfile @@ -31,7 +31,7 @@ RUN echo "Building memberagent with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ # The pinned digest must reference a multi-arch image index so BuildKit can # resolve the matching base layer for each target architecture. # Refer to https://github.com/GoogleContainerTools/distroless for more details -FROM gcr.io/distroless/base:nonroot@sha256:97b9d04bed1c754b756c3c4b6a04915c22fb0b5d96a59944eb3bf78c26e6e157 +FROM gcr.io/distroless/base:nonroot@sha256:2d7d29b504e7166f6d0c7655a18ebf5def5b37b029f8c4f8667e434ba774844f WORKDIR / COPY --link --from=builder /workspace/memberagent . USER 65532:65532 diff --git a/docker/refresh-token.Dockerfile b/docker/refresh-token.Dockerfile index b7918db7f..0998fc474 100644 --- a/docker/refresh-token.Dockerfile +++ b/docker/refresh-token.Dockerfile @@ -32,7 +32,7 @@ RUN echo "Building refreshtoken with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ # The pinned digest must reference a multi-arch image index so BuildKit can # resolve the matching base layer for each target architecture. # Refer to https://github.com/GoogleContainerTools/distroless for more details -FROM gcr.io/distroless/base:nonroot@sha256:97b9d04bed1c754b756c3c4b6a04915c22fb0b5d96a59944eb3bf78c26e6e157 +FROM gcr.io/distroless/base:nonroot@sha256:2d7d29b504e7166f6d0c7655a18ebf5def5b37b029f8c4f8667e434ba774844f WORKDIR / COPY --link --from=builder /workspace/refreshtoken . USER 65532:65532 From e5f16f29c1a9a32699025e53f27d9a313392c165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 15:38:40 +1000 Subject: [PATCH 08/12] chore: bump azure/setup-helm from 5.0.0 to 5.0.1 (#855) Bumps [azure/setup-helm](https://github.com/azure/setup-helm) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/azure/setup-helm/releases) - [Changelog](https://github.com/Azure/setup-helm/blob/main/CHANGELOG.md) - [Commits](https://github.com/azure/setup-helm/compare/dda3372f752e03dde6b3237bc9431cdc2f7a02a2...9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310) --- updated-dependencies: - dependency-name: azure/setup-helm dependency-version: 5.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/code-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code-lint.yml b/.github/workflows/code-lint.yml index fd0445d21..cfe5ee9ac 100644 --- a/.github/workflows/code-lint.yml +++ b/.github/workflows/code-lint.yml @@ -79,7 +79,7 @@ jobs: uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up Helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5 + uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5 with: version: v3.17.0 From 48bde0d8eb719d60b247910d4a680e3ed4483e8d Mon Sep 17 00:00:00 2001 From: Chen Yu Date: Wed, 2 Sep 2026 01:12:01 +0800 Subject: [PATCH 09/12] chore: upgrade GOLANGCI Linter (v1.64.7 -> v2.13.2) (#862) Upgraded Go Linter version Signed-off-by: michaelawyu --- .golangci.yml | 36 ++++++++++++++++++------------------ Makefile | 8 ++++---- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c1e6993c4..aff61ff82 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,30 +1,28 @@ +version: "2" + run: timeout: 15m go: '1.26.6' -linters-settings: - stylecheck: - checks: [ "all", "-ST1001" ] # Disables dot-import warnings - revive: - rules: - - name: dot-imports - disabled: true - gosec: - excludes: - - G404 #Use of weak random number generator (math/rand or math/rand/v2). It is only used in tests. - linters: - disable-all: true + default: none + settings: + staticcheck: + checks: [ "all", "-ST1001" ] # Disables dot-import warnings + revive: + rules: + - name: dot-imports + disabled: true + gosec: + excludes: + - G404 #Use of weak random number generator (math/rand or math/rand/v2). It is only used in tests. enable: - decorder - errcheck - errorlint - goconst - gocyclo - - gofmt - - goimports - gosec - - gosimple - govet - ineffassign - misspell @@ -34,9 +32,11 @@ linters: - revive - staticcheck - tparallel - - typecheck - unconvert - unused - whitespace - # Run with --fast=false for more extensive checks - fast: true + +formatters: + enable: + - gofmt + - goimports diff --git a/Makefile b/Makefile index 089512c02..8065cba00 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ GOIMPORTS_VER := v0.42.0 GOIMPORTS_BIN := goimports GOIMPORTS := $(abspath $(TOOLS_BIN_DIR)/$(GOIMPORTS_BIN)-$(GOIMPORTS_VER)) -GOLANGCI_LINT_VER := v1.64.7 +GOLANGCI_LINT_VER := v2.13.2 GOLANGCI_LINT_BIN := golangci-lint GOLANGCI_LINT := $(abspath $(TOOLS_BIN_DIR)/$(GOLANGCI_LINT_BIN)-$(GOLANGCI_LINT_VER)) @@ -87,7 +87,7 @@ GO_INSTALL := ./hack/go-install.sh ## -------------------------------------- $(GOLANGCI_LINT): - GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) github.com/golangci/golangci-lint/cmd/golangci-lint $(GOLANGCI_LINT_BIN) $(GOLANGCI_LINT_VER) + GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) github.com/golangci/golangci-lint/v2/cmd/golangci-lint $(GOLANGCI_LINT_BIN) $(GOLANGCI_LINT_VER) $(CONTROLLER_GEN): GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) sigs.k8s.io/controller-tools/cmd/controller-gen $(CONTROLLER_GEN_BIN) $(CONTROLLER_GEN_VER) @@ -115,11 +115,11 @@ help: ## Display this help .PHONY: lint lint: $(GOLANGCI_LINT) ## Run fast linting - $(GOLANGCI_LINT) run -v + $(GOLANGCI_LINT) run -v --fast-only .PHONY: lint-full lint-full: $(GOLANGCI_LINT) ## Run slower linters to detect possible issues - $(GOLANGCI_LINT) run -v --fast=false + $(GOLANGCI_LINT) run -v ## -------------------------------------- ## Development From 55b40b8aa6ea1c9ad6576a7064630b30250de36d Mon Sep 17 00:00:00 2001 From: Britania Rodriguez Reyes Date: Tue, 1 Sep 2026 11:30:17 -0700 Subject: [PATCH 10/12] chore: adapt September backport for Azure Fleet Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...2026-09-01-1119-kubefleet-main-backport.md | 34 +++++++++++++++++++ test/e2e/join_and_leave_test.go | 6 ++-- 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 .github/.copilot/breadcrumbs/2026-09-01-1119-kubefleet-main-backport.md diff --git a/.github/.copilot/breadcrumbs/2026-09-01-1119-kubefleet-main-backport.md b/.github/.copilot/breadcrumbs/2026-09-01-1119-kubefleet-main-backport.md new file mode 100644 index 000000000..a7bdd20e4 --- /dev/null +++ b/.github/.copilot/breadcrumbs/2026-09-01-1119-kubefleet-main-backport.md @@ -0,0 +1,34 @@ +# Implementation: September 2026 KubeFleet Main Backport + +## Overview + +Merge the latest `kubefleet-dev/kubefleet` main branch into `Azure/fleet` main while preserving Fleet-specific module paths, build settings, chart layout, and link-check configuration. + +## Plan + +1. Confirm `cncf` and `upstream` remotes, fetch both main branches, and fast-forward the session branch to `upstream/main`. +2. Merge `cncf/main` with a merge commit, preferring incoming conflict resolutions and retaining incoming modify/delete files. +3. Restore Fleet conventions for Go module/import paths, repository links, Docker builder versions, CRD chart symlinks, and authenticated Slack link checking. +4. Run `make reviewable` in WSL and fix backport-related failures. +5. Commit any post-merge fixes, push the branch, and open a new pull request against `Azure/fleet:main`. + +## Success Criteria + +- [x] The branch begins at the latest `upstream/main`. +- [x] `cncf/main` is merged with a merge commit. +- [x] Go module and import references use `go.goms.io/fleet`. +- [x] Dockerfiles use the latest incoming Microsoft Go builder consistently. +- [x] No new CRD template symlinks remain. +- [x] `make reviewable` passes in WSL. +- [ ] A new pull request targets `Azure/fleet:main` from the pushed fork branch. + +## Implementation Notes + +- Base: `upstream/main` at `f21dbd0cfd3cb10923472fa8557e4c9717ba3597`. +- Incoming: `cncf/main` at `48bde0d8`. +- Merge: `533a4a8a`. +- Restored incoming Go imports in `test/e2e/join_and_leave_test.go` to `go.goms.io/fleet`. +- Confirmed all four Dockerfiles use `mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1`. +- Confirmed the Slack archives URL remains covered by `ignorePatterns`. +- Confirmed no new CRD template symlinks were introduced relative to the first merge parent. +- Ran `make reviewable` successfully in WSL with `GOTOOLCHAIN=go1.26.6`; shell scripts were normalized only in the working tree for validation and restored to the checkout line-ending convention afterward. diff --git a/test/e2e/join_and_leave_test.go b/test/e2e/join_and_leave_test.go index 4cfc8260b..269e6ff17 100644 --- a/test/e2e/join_and_leave_test.go +++ b/test/e2e/join_and_leave_test.go @@ -32,9 +32,9 @@ import ( fleetnetworkingv1alpha1 "go.goms.io/fleet-networking/api/v1alpha1" - clusterv1beta1 "github.com/kubefleet-dev/kubefleet/apis/cluster/v1beta1" - placementv1beta1 "github.com/kubefleet-dev/kubefleet/apis/placement/v1beta1" - "github.com/kubefleet-dev/kubefleet/pkg/utils" + clusterv1beta1 "go.goms.io/fleet/apis/cluster/v1beta1" + placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1" + "go.goms.io/fleet/pkg/utils" ) const ( From 81194d7df10ab178a8e780e6472ede334d7a9342 Mon Sep 17 00:00:00 2001 From: Yetkin Timocin Date: Tue, 1 Sep 2026 13:41:22 -0700 Subject: [PATCH 11/12] fix: cross-compile images instead of emulating the builder stage (#858) Since #745 the release images are built for linux/amd64 and linux/arm64 in one buildx invocation. The Dockerfiles ran the whole builder stage per target platform, so on the amd64 runner the arm64 half executed the Go toolchain, cgo and gcc under qemu-user-static. gcc does not survive that: the Trivy workflow on main fails in the arm64 builder with runtime/cgo: gcc: internal compiler error: Segmentation fault net: cgo: gcc produced no output or, when cc1 hangs instead of crashing, sits in the build step until the six-hour job timeout. Before #745 the whole three-image build step took under five minutes. Pin the builder stage to $BUILDPLATFORM and cross-compile. When the target architecture differs from the one the stage runs on, install Debian's cross toolchain (gcc-, libc6-dev--cross) and point CC at it via a target-gcc symlink; otherwise use the native gcc. BUILDARCH is the stage's own architecture because the stage is pinned to BUILDPLATFORM. GOEXPERIMENT=systemcrypto needs CGO_ENABLED=1 but no OpenSSL headers (its backend dlopen()s libcrypto at run time - the builder image has no libssl-dev), so the cross libc headers are all the compile requires. The module download now precedes the target-specific ARGs so BuildKit shares that layer across the platforms of one build. Verified on an amd64 Ubuntu 24.04 VM mirroring ubuntu-latest, running the Makefile target with setup-qemu and qemu-user-static 7.2.0-1 registered: main (431ba73f) fails after 426s with the segmentation fault above; with this change hub-agent builds for both platforms in 189s, and both binaries execute. Signed-off-by: Yetkin Timocin --- docker/hub-agent.Dockerfile | 57 +++++++++++++++++++++++++-------- docker/member-agent.Dockerfile | 57 +++++++++++++++++++++++++-------- docker/refresh-token.Dockerfile | 57 +++++++++++++++++++++++++-------- 3 files changed, 132 insertions(+), 39 deletions(-) diff --git a/docker/hub-agent.Dockerfile b/docker/hub-agent.Dockerfile index 6ae49a944..99a813377 100644 --- a/docker/hub-agent.Dockerfile +++ b/docker/hub-agent.Dockerfile @@ -1,12 +1,14 @@ # syntax=docker/dockerfile:1 -# Build the hubagent binary -FROM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder - -# TARGETOS and TARGETARCH are populated automatically by BuildKit for each -# platform being built, so a single multi-platform `docker buildx build` -# produces a correctly built binary per architecture. -ARG TARGETOS -ARG TARGETARCH +# Build the hubagent binary. +# +# The builder stage always runs on the build host's own platform +# (--platform=$BUILDPLATFORM) and cross-compiles for each target platform, so +# the Go toolchain, cgo and gcc never execute under QEMU. Running the builder +# per target under emulation crashed gcc's cc1 ("internal compiler error: +# Segmentation fault" / "cgo: gcc produced no output" in runtime/cgo and net) +# and, when it did not crash, took over an hour per image. Only the final +# distroless stage is per-target, and it runs no commands. +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder WORKDIR /workspace # Copy the Go Modules manifests @@ -16,16 +18,45 @@ COPY go.sum go.sum # and so that source changes don't invalidate our downloaded layer RUN go mod download +# The module download above is declared before the target-specific ARGs so +# that BuildKit shares its layer across every target platform of one build. +# TARGETOS and TARGETARCH are populated automatically by BuildKit for each +# platform being built, so a single multi-platform `docker buildx build` +# produces a correctly built binary per architecture. +ARG TARGETOS +ARG TARGETARCH +ARG BUILDARCH + +# GOEXPERIMENT=systemcrypto requires CGO_ENABLED=1. Its OpenSSL backend +# dlopen()s libcrypto at run time, so no OpenSSL headers are needed to build - +# only a C compiler and libc headers for the target architecture. When the +# target differs from the architecture this stage runs on, install Debian's +# cross toolchain for it. Either way /usr/local/bin/target-gcc ends up pointing +# at the right compiler. BUILDARCH is this stage's own architecture because the +# stage is pinned to --platform=$BUILDPLATFORM above. +RUN set -eu; \ + if [ "${TARGETARCH}" = "${BUILDARCH}" ]; then \ + ln -s /usr/bin/gcc /usr/local/bin/target-gcc; \ + else \ + case "${TARGETARCH}" in \ + arm64) pkg=gcc-aarch64-linux-gnu; triple=aarch64-linux-gnu ;; \ + amd64) pkg=gcc-x86-64-linux-gnu; triple=x86_64-linux-gnu ;; \ + *) echo "unsupported TARGETARCH ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + apt-get update; \ + apt-get install -y --no-install-recommends "${pkg}" "libc6-dev-${TARGETARCH}-cross"; \ + rm -rf /var/lib/apt/lists/*; \ + ln -s "/usr/bin/${triple}-gcc" /usr/local/bin/target-gcc; \ + fi + # Copy the go source COPY cmd/hubagent/ cmd/hubagent/ COPY apis/ apis/ COPY pkg/ pkg/ -# Build. CGO + systemcrypto compiles against the target architecture's OpenSSL, -# which is why the builder runs per-target under emulation (see the Makefile's -# docker-buildx-builder / setup-qemu targets) rather than cross-compiling. -RUN echo "Building hubagent with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ - CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o hubagent ./cmd/hubagent/ +# Build for the target platform with the compiler selected above. +RUN echo "Building hubagent with GOOS=${TARGETOS} GOARCH=${TARGETARCH} CC=$(readlink -f /usr/local/bin/target-gcc)" && \ + CGO_ENABLED=1 CC=target-gcc GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o hubagent ./cmd/hubagent/ # Use distroless as minimal base image to package the hubagent binary. # The pinned digest must reference a multi-arch image index so BuildKit can diff --git a/docker/member-agent.Dockerfile b/docker/member-agent.Dockerfile index f0670a42d..f05cf953b 100644 --- a/docker/member-agent.Dockerfile +++ b/docker/member-agent.Dockerfile @@ -1,12 +1,14 @@ # syntax=docker/dockerfile:1 -# Build the memberagent binary -FROM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder - -# TARGETOS and TARGETARCH are populated automatically by BuildKit for each -# platform being built, so a single multi-platform `docker buildx build` -# produces a correctly built binary per architecture. -ARG TARGETOS -ARG TARGETARCH +# Build the memberagent binary. +# +# The builder stage always runs on the build host's own platform +# (--platform=$BUILDPLATFORM) and cross-compiles for each target platform, so +# the Go toolchain, cgo and gcc never execute under QEMU. Running the builder +# per target under emulation crashed gcc's cc1 ("internal compiler error: +# Segmentation fault" / "cgo: gcc produced no output" in runtime/cgo and net) +# and, when it did not crash, took over an hour per image. Only the final +# distroless stage is per-target, and it runs no commands. +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder WORKDIR /workspace # Copy the Go Modules manifests @@ -16,16 +18,45 @@ COPY go.sum go.sum # and so that source changes don't invalidate our downloaded layer RUN go mod download +# The module download above is declared before the target-specific ARGs so +# that BuildKit shares its layer across every target platform of one build. +# TARGETOS and TARGETARCH are populated automatically by BuildKit for each +# platform being built, so a single multi-platform `docker buildx build` +# produces a correctly built binary per architecture. +ARG TARGETOS +ARG TARGETARCH +ARG BUILDARCH + +# GOEXPERIMENT=systemcrypto requires CGO_ENABLED=1. Its OpenSSL backend +# dlopen()s libcrypto at run time, so no OpenSSL headers are needed to build - +# only a C compiler and libc headers for the target architecture. When the +# target differs from the architecture this stage runs on, install Debian's +# cross toolchain for it. Either way /usr/local/bin/target-gcc ends up pointing +# at the right compiler. BUILDARCH is this stage's own architecture because the +# stage is pinned to --platform=$BUILDPLATFORM above. +RUN set -eu; \ + if [ "${TARGETARCH}" = "${BUILDARCH}" ]; then \ + ln -s /usr/bin/gcc /usr/local/bin/target-gcc; \ + else \ + case "${TARGETARCH}" in \ + arm64) pkg=gcc-aarch64-linux-gnu; triple=aarch64-linux-gnu ;; \ + amd64) pkg=gcc-x86-64-linux-gnu; triple=x86_64-linux-gnu ;; \ + *) echo "unsupported TARGETARCH ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + apt-get update; \ + apt-get install -y --no-install-recommends "${pkg}" "libc6-dev-${TARGETARCH}-cross"; \ + rm -rf /var/lib/apt/lists/*; \ + ln -s "/usr/bin/${triple}-gcc" /usr/local/bin/target-gcc; \ + fi + # Copy the go source COPY cmd/memberagent cmd/memberagent/ COPY apis/ apis/ COPY pkg/ pkg/ -# Build. CGO + systemcrypto compiles against the target architecture's OpenSSL, -# which is why the builder runs per-target under emulation (see the Makefile's -# docker-buildx-builder / setup-qemu targets) rather than cross-compiling. -RUN echo "Building memberagent with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ - CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o memberagent ./cmd/memberagent/ +# Build for the target platform with the compiler selected above. +RUN echo "Building memberagent with GOOS=${TARGETOS} GOARCH=${TARGETARCH} CC=$(readlink -f /usr/local/bin/target-gcc)" && \ + CGO_ENABLED=1 CC=target-gcc GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o memberagent ./cmd/memberagent/ # Use distroless as minimal base image to package the memberagent binary. # The pinned digest must reference a multi-arch image index so BuildKit can diff --git a/docker/refresh-token.Dockerfile b/docker/refresh-token.Dockerfile index 0998fc474..75b2a98a8 100644 --- a/docker/refresh-token.Dockerfile +++ b/docker/refresh-token.Dockerfile @@ -1,12 +1,14 @@ # syntax=docker/dockerfile:1 -# Build the refreshtoken binary -FROM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder - -# TARGETOS and TARGETARCH are populated automatically by BuildKit for each -# platform being built, so a single multi-platform `docker buildx build` -# produces a correctly built binary per architecture. -ARG TARGETOS -ARG TARGETARCH +# Build the refreshtoken binary. +# +# The builder stage always runs on the build host's own platform +# (--platform=$BUILDPLATFORM) and cross-compiles for each target platform, so +# the Go toolchain, cgo and gcc never execute under QEMU. Running the builder +# per target under emulation crashed gcc's cc1 ("internal compiler error: +# Segmentation fault" / "cgo: gcc produced no output" in runtime/cgo and net) +# and, when it did not crash, took over an hour per image. Only the final +# distroless stage is per-target, and it runs no commands. +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1 AS builder WORKDIR /workspace # Copy the Go Modules manifests @@ -16,17 +18,46 @@ COPY go.sum go.sum # and so that source changes don't invalidate our downloaded layer RUN go mod download +# The module download above is declared before the target-specific ARGs so +# that BuildKit shares its layer across every target platform of one build. +# TARGETOS and TARGETARCH are populated automatically by BuildKit for each +# platform being built, so a single multi-platform `docker buildx build` +# produces a correctly built binary per architecture. +ARG TARGETOS +ARG TARGETARCH +ARG BUILDARCH + +# GOEXPERIMENT=systemcrypto requires CGO_ENABLED=1. Its OpenSSL backend +# dlopen()s libcrypto at run time, so no OpenSSL headers are needed to build - +# only a C compiler and libc headers for the target architecture. When the +# target differs from the architecture this stage runs on, install Debian's +# cross toolchain for it. Either way /usr/local/bin/target-gcc ends up pointing +# at the right compiler. BUILDARCH is this stage's own architecture because the +# stage is pinned to --platform=$BUILDPLATFORM above. +RUN set -eu; \ + if [ "${TARGETARCH}" = "${BUILDARCH}" ]; then \ + ln -s /usr/bin/gcc /usr/local/bin/target-gcc; \ + else \ + case "${TARGETARCH}" in \ + arm64) pkg=gcc-aarch64-linux-gnu; triple=aarch64-linux-gnu ;; \ + amd64) pkg=gcc-x86-64-linux-gnu; triple=x86_64-linux-gnu ;; \ + *) echo "unsupported TARGETARCH ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + apt-get update; \ + apt-get install -y --no-install-recommends "${pkg}" "libc6-dev-${TARGETARCH}-cross"; \ + rm -rf /var/lib/apt/lists/*; \ + ln -s "/usr/bin/${triple}-gcc" /usr/local/bin/target-gcc; \ + fi + # Copy the go source COPY cmd/authtoken/main.go main.go COPY pkg/authtoken pkg/authtoken # writefile is a dependency of pkg/authtoken for secure file creation (0600 permissions) COPY pkg/utils/writefile pkg/utils/writefile -# Build. CGO + systemcrypto compiles against the target architecture's OpenSSL, -# which is why the builder runs per-target under emulation (see the Makefile's -# docker-buildx-builder / setup-qemu targets) rather than cross-compiling. -RUN echo "Building refreshtoken with GOOS=${TARGETOS} GOARCH=${TARGETARCH}" && \ - CGO_ENABLED=1 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o refreshtoken . +# Build for the target platform with the compiler selected above. +RUN echo "Building refreshtoken with GOOS=${TARGETOS} GOARCH=${TARGETARCH} CC=$(readlink -f /usr/local/bin/target-gcc)" && \ + CGO_ENABLED=1 CC=target-gcc GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOEXPERIMENT=systemcrypto go build -o refreshtoken . # Use distroless as minimal base image to package the refreshtoken binary. # The pinned digest must reference a multi-arch image index so BuildKit can From 1a771a41cf50bffeb6a787f2262f9efa8d653e72 Mon Sep 17 00:00:00 2001 From: Britania Rodriguez Reyes Date: Tue, 1 Sep 2026 13:46:51 -0700 Subject: [PATCH 12/12] docs: record refreshed September backport Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../2026-09-01-1119-kubefleet-main-backport.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/.copilot/breadcrumbs/2026-09-01-1119-kubefleet-main-backport.md b/.github/.copilot/breadcrumbs/2026-09-01-1119-kubefleet-main-backport.md index a7bdd20e4..0daf1f970 100644 --- a/.github/.copilot/breadcrumbs/2026-09-01-1119-kubefleet-main-backport.md +++ b/.github/.copilot/breadcrumbs/2026-09-01-1119-kubefleet-main-backport.md @@ -20,15 +20,17 @@ Merge the latest `kubefleet-dev/kubefleet` main branch into `Azure/fleet` main w - [x] Dockerfiles use the latest incoming Microsoft Go builder consistently. - [x] No new CRD template symlinks remain. - [x] `make reviewable` passes in WSL. -- [ ] A new pull request targets `Azure/fleet:main` from the pushed fork branch. +- [x] A new pull request targets `Azure/fleet:main` from the pushed fork branch. ## Implementation Notes - Base: `upstream/main` at `f21dbd0cfd3cb10923472fa8557e4c9717ba3597`. -- Incoming: `cncf/main` at `48bde0d8`. -- Merge: `533a4a8a`. +- Initial incoming: `cncf/main` at `48bde0d8`, merged as `533a4a8a`. +- Refreshed incoming: `cncf/main` at `81194d7d`, merged as `ccd19190`. - Restored incoming Go imports in `test/e2e/join_and_leave_test.go` to `go.goms.io/fleet`. - Confirmed all four Dockerfiles use `mcr.microsoft.com/oss/go/microsoft/golang:1.26.6-1`. - Confirmed the Slack archives URL remains covered by `ignorePatterns`. - Confirmed no new CRD template symlinks were introduced relative to the first merge parent. - Ran `make reviewable` successfully in WSL with `GOTOOLCHAIN=go1.26.6`; shell scripts were normalized only in the working tree for validation and restored to the checkout line-ending convention afterward. +- Refetched `cncf/main` after PR #858 landed, merged its cross-compilation changes, and reran `make reviewable` successfully. +- Published the backport as `Azure/fleet` PR #1338.