From 9ab4f1754c6f19f978d5383d237498148c6ca605 Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Mon, 20 Jul 2026 14:15:13 +0000 Subject: [PATCH 1/6] incluster-comp-pr-merged: split multi-arch build into per-arch matrix + merge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes 'error writing layer blob: failed to reserve cache' during docker/build-push-action, seen e.g. on kubevuln's pr-merged run (https://github.com/kubescape/kubevuln/actions/runs/29737569926/job/88346893168). Root cause: linux/amd64,linux/arm64 were built in one multi-platform build-push-action invocation (arm64 via QEMU) with cache-to/cache-from type=gha sharing a single cache scope across both platforms concurrently — a known trigger for GHA cache blob-reservation races (moby/buildkit#4188, docker/build-push-action#1044). Restructures the docker-build job into: - setup: prerelease tag + test names (unchanged logic, just relocated) - unit-test: go test, run once (was duplicated per platform before) - build: matrix over {amd64 -> ubuntu-large, arm64 -> ubuntu-large-arm64}, each leg builds natively (no QEMU) with its own GHA cache scope (component+arch scoped), pushing an arch-suffixed tag - merge-images: docker buildx imagetools create stitches the arch tags into the final manifest, then attestation/cosign move here to act on the merged digest Same pattern already validated in armosec/shared-workflows-v2's arc-runner-ci.yaml (PR #15/#16). workflow_call inputs schema is unchanged; this workflow declares no outputs and no consumer references internal job outputs, so the job-graph restructuring is contract-safe for all 6 known consumers (synchronizer, operator, prometheus-exporter, kubevuln, storage, http-request), all of which pass BUILD_PLATFORM: linux/amd64,linux/arm64. Single-platform callers (BUILD_PLATFORM without arm64) skip the arm64 leg via a job-level if, and merge-images degrades to a single-source manifest. Co-Authored-By: Claude Sonnet 5 --- .../workflows/incluster-comp-pr-merged.yaml | 140 ++++++++++++------ 1 file changed, 94 insertions(+), 46 deletions(-) diff --git a/.github/workflows/incluster-comp-pr-merged.yaml b/.github/workflows/incluster-comp-pr-merged.yaml index b8ee7e7..c0884b4 100644 --- a/.github/workflows/incluster-comp-pr-merged.yaml +++ b/.github/workflows/incluster-comp-pr-merged.yaml @@ -117,50 +117,73 @@ on: type: boolean jobs: - docker-build: + setup: if: ${{ ((contains(github.event.pull_request.labels.*.name, 'release') || contains( github.event.pull_request.labels.*.name, 'trigger-integration-test')) && github.repository_owner == 'kubescape') || inputs.FORCE }} - runs-on: ubuntu-large + runs-on: ubuntu-latest outputs: IMAGE_TAG_PRERELEASE: ${{ steps.image-prerelease-tag.outputs.IMAGE_TAG_PRERELEASE }} TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }} permissions: - actions: write - id-token: write - packages: write contents: read pull-requests: read - attestations: write - steps: - # - name: Exit if not on master branch - # if: github.ref == 'master' - # run: exit -1 + - name: Set prerelease image tag + id: image-prerelease-tag + run: echo "IMAGE_TAG_PRERELEASE=${{ inputs.IMAGE_TAG }}-prerelease" >> $GITHUB_OUTPUT + - id: export_tests_to_env + name: set test name + run: | + echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT + env: + input: ${{ inputs.REQUIRED_TESTS }} + + unit-test: + needs: setup + runs-on: ubuntu-large + permissions: + contents: read + steps: - uses: actions/checkout@v4 name: Checkout with: fetch-depth: 0 - # submodules: recursive - uses: actions/setup-go@v5 name: Installing go with: go-version: "${{ inputs.GO_VERSION }}" - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Run unit test + run: go test -v ./... + + build: + needs: [setup, unit-test] + if: ${{ matrix.arch == 'amd64' || contains(inputs.BUILD_PLATFORM, 'arm64') }} + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + arch: amd64 + runner: ubuntu-large + - platform: linux/arm64 + arch: arm64 + runner: ubuntu-large-arm64 + runs-on: ${{ matrix.runner }} + permissions: + actions: write + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + name: Checkout + with: + fetch-depth: 0 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Set prerelease image tag - id: image-prerelease-tag - run: echo "IMAGE_TAG_PRERELEASE=${{ inputs.IMAGE_TAG }}-prerelease" >> $GITHUB_OUTPUT - - - name: Run unit test - id: unit-test - run: go test -v ./... - - name: Login to Quay uses: docker/login-action@v3 with: @@ -169,23 +192,55 @@ jobs: password: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }} - name: Build and push - id: build uses: docker/build-push-action@v6 with: context: . file: build/Dockerfile - tags: ${{ inputs.IMAGE_NAME }}:${{ steps.image-prerelease-tag.outputs.IMAGE_TAG_PRERELEASE }} + tags: ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }}-${{ matrix.arch }} build-args: image_version=${{ inputs.IMAGE_TAG }} - platforms: ${{ inputs.BUILD_PLATFORM }} - cache-from: type=gha - cache-to: type=gha,mode=max + platforms: ${{ matrix.platform }} + cache-from: type=gha,scope=${{ inputs.COMPONENT_NAME }}-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=${{ inputs.COMPONENT_NAME }}-${{ matrix.arch }} push: true + merge-images: + needs: [setup, build] + runs-on: ubuntu-latest + permissions: + id-token: write + packages: write + contents: read + attestations: write + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Quay + uses: docker/login-action@v3 + with: + registry: quay.io + username: ${{ secrets.QUAYIO_REGISTRY_USERNAME }} + password: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }} + + - name: Create multi-arch manifest + id: merge + env: + IMAGE: ${{ inputs.IMAGE_NAME }} + TAG: ${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} + BUILD_PLATFORM: ${{ inputs.BUILD_PLATFORM }} + run: | + SOURCES="${IMAGE}:${TAG}-amd64" + if echo "$BUILD_PLATFORM" | grep -q arm64; then + SOURCES="$SOURCES ${IMAGE}:${TAG}-arm64" + fi + docker buildx imagetools create -t "${IMAGE}:${TAG}" --metadata-file metadata.json $SOURCES + echo "digest=$(jq -r '."containerimage.digest"' metadata.json)" >> "$GITHUB_OUTPUT" + - name: Attest image provenance uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-name: ${{ inputs.IMAGE_NAME }} - subject-digest: ${{ steps.build.outputs.digest }} + subject-digest: ${{ steps.merge.outputs.digest }} push-to-registry: true - name: Install cosign @@ -202,27 +257,20 @@ jobs: COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY_V1 }} run: | # Sign the image with keyless mode - cosign sign -y ${{ inputs.IMAGE_NAME }}:${{ steps.image-prerelease-tag.outputs.IMAGE_TAG_PRERELEASE }} + cosign sign -y ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} # Sign the image with key for verifier clients without keyless support # Put the key from environment variable to a file echo "$COSIGN_PRIVATE_KEY" > cosign.key - printf "$COSIGN_PRIVATE_KEY_PASSWORD" | cosign sign -key cosign.key -y ${{ inputs.IMAGE_NAME }}:${{ steps.image-prerelease-tag.outputs.IMAGE_TAG_PRERELEASE }} + printf "$COSIGN_PRIVATE_KEY_PASSWORD" | cosign sign -key cosign.key -y ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} rm cosign.key # Verify the image echo "$COSIGN_PUBLIC_KEY" > cosign.pub - cosign verify -key cosign.pub ${{ inputs.IMAGE_NAME }}:${{ steps.image-prerelease-tag.outputs.IMAGE_TAG_PRERELEASE }} - - - id: export_tests_to_env - name: set test name - run: | - echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT - env: - input: ${{ inputs.REQUIRED_TESTS }} + cosign verify -key cosign.pub ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} run-tests: - needs: docker-build - if: ${{ needs.docker-build.outputs.TEST_NAMES != '' && toJson(fromJson(needs.docker-build.outputs.TEST_NAMES)) != '[]' }} + needs: [setup, merge-images] + if: ${{ needs.setup.outputs.TEST_NAMES != '' && toJson(fromJson(needs.setup.outputs.TEST_NAMES)) != '[]' }} runs-on: ubuntu-latest permissions: actions: read @@ -250,7 +298,7 @@ jobs: env: GH_TOKEN: ${{ steps.app-token.outputs.token }} CORRELATION_ID: ${{ steps.dispatch-info.outputs.correlation_id }} - REQUIRED_TESTS: ${{ needs.docker-build.outputs.TEST_NAMES }} + REQUIRED_TESTS: ${{ needs.setup.outputs.TEST_NAMES }} run: | ADDITIONAL_TESTS=$(python3 - <<'PY' import json, os @@ -297,7 +345,7 @@ jobs: -f "client_payload[in_cluster_chart_branch]=${{ inputs.HELM_BRANCH }}" \ -f "client_payload[ks_branch]=release" \ -f "client_payload[charts_repo]=kubescape/helm-charts" \ - -f "client_payload[component_image_tags]=${{ inputs.COMPONENT_NAME }}-tag=${{ needs.docker-build.outputs.IMAGE_TAG_PRERELEASE }}" + -f "client_payload[component_image_tags]=${{ inputs.COMPONENT_NAME }}-tag=${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }}" echo "Dispatch completed" @@ -433,7 +481,7 @@ jobs: create-release-and-retag: if: ${{ contains(github.event.pull_request.labels.*.name, 'release') && always() && contains(needs.*.result, 'success') && !(contains(needs.*.result, 'failure')) && !(contains (needs.*.result,'cancelled')) || inputs.FORCE }} name: Docker retag and create release - needs: [run-tests, docker-build] + needs: [setup, merge-images, run-tests] outputs: upload_url: ${{ steps.create_release.outputs.upload_url }} runs-on: ubuntu-latest @@ -456,11 +504,11 @@ jobs: chmod 755 regctl if [ ${{ inputs.COSIGN }} = true ]; then - ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.docker-build.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:${{ inputs.IMAGE_TAG }} --digest-tags - ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.docker-build.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:latest --digest-tags + ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:${{ inputs.IMAGE_TAG }} --digest-tags + ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:latest --digest-tags else - ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.docker-build.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:${{ inputs.IMAGE_TAG }} - ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.docker-build.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:latest + ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:${{ inputs.IMAGE_TAG }} + ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:latest fi - name: Create Release From 0ff2c771117f1042054b103e3ea4b555c0dd4a8a Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Mon, 20 Jul 2026 14:51:04 +0000 Subject: [PATCH 2/6] incluster-comp-pr-merged: validate BUILD_PLATFORM in setup, gate legs explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the amd64 matrix leg's if-condition (matrix.arch == 'amd64') was unconditional, and merge-images always assumed an amd64 image existed — an arm64-only BUILD_PLATFORM would still build/reference an unrequested amd64 image. Adds a validation step in setup that resolves BUILD_PLATFORM into two explicit outputs (BUILD_AMD64, BUILD_ARM64), rejecting any value other than linux/amd64, linux/arm64, or linux/amd64,linux/arm64. build and merge-images now branch on those outputs instead of re-deriving arch membership from the raw string independently in two places. Co-Authored-By: Claude Sonnet 5 --- .../workflows/incluster-comp-pr-merged.yaml | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/.github/workflows/incluster-comp-pr-merged.yaml b/.github/workflows/incluster-comp-pr-merged.yaml index c0884b4..22c6e68 100644 --- a/.github/workflows/incluster-comp-pr-merged.yaml +++ b/.github/workflows/incluster-comp-pr-merged.yaml @@ -123,6 +123,8 @@ jobs: outputs: IMAGE_TAG_PRERELEASE: ${{ steps.image-prerelease-tag.outputs.IMAGE_TAG_PRERELEASE }} TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }} + BUILD_AMD64: ${{ steps.platforms.outputs.BUILD_AMD64 }} + BUILD_ARM64: ${{ steps.platforms.outputs.BUILD_ARM64 }} permissions: contents: read pull-requests: read @@ -131,6 +133,25 @@ jobs: id: image-prerelease-tag run: echo "IMAGE_TAG_PRERELEASE=${{ inputs.IMAGE_TAG }}-prerelease" >> $GITHUB_OUTPUT + - name: Validate and resolve build platforms + id: platforms + env: + BUILD_PLATFORM: ${{ inputs.BUILD_PLATFORM }} + run: | + AMD64=false + ARM64=false + case "$BUILD_PLATFORM" in + linux/amd64) AMD64=true ;; + linux/arm64) ARM64=true ;; + linux/amd64,linux/arm64|linux/arm64,linux/amd64) AMD64=true; ARM64=true ;; + *) + echo "::error::Unsupported BUILD_PLATFORM '$BUILD_PLATFORM' — expected one of: linux/amd64, linux/arm64, linux/amd64,linux/arm64" + exit 1 + ;; + esac + echo "BUILD_AMD64=$AMD64" >> "$GITHUB_OUTPUT" + echo "BUILD_ARM64=$ARM64" >> "$GITHUB_OUTPUT" + - id: export_tests_to_env name: set test name run: | @@ -159,7 +180,7 @@ jobs: build: needs: [setup, unit-test] - if: ${{ matrix.arch == 'amd64' || contains(inputs.BUILD_PLATFORM, 'arm64') }} + if: ${{ (matrix.arch == 'amd64' && needs.setup.outputs.BUILD_AMD64 == 'true') || (matrix.arch == 'arm64' && needs.setup.outputs.BUILD_ARM64 == 'true') }} strategy: fail-fast: false matrix: @@ -227,10 +248,14 @@ jobs: env: IMAGE: ${{ inputs.IMAGE_NAME }} TAG: ${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} - BUILD_PLATFORM: ${{ inputs.BUILD_PLATFORM }} + BUILD_AMD64: ${{ needs.setup.outputs.BUILD_AMD64 }} + BUILD_ARM64: ${{ needs.setup.outputs.BUILD_ARM64 }} run: | - SOURCES="${IMAGE}:${TAG}-amd64" - if echo "$BUILD_PLATFORM" | grep -q arm64; then + SOURCES="" + if [ "$BUILD_AMD64" = "true" ]; then + SOURCES="$SOURCES ${IMAGE}:${TAG}-amd64" + fi + if [ "$BUILD_ARM64" = "true" ]; then SOURCES="$SOURCES ${IMAGE}:${TAG}-arm64" fi docker buildx imagetools create -t "${IMAGE}:${TAG}" --metadata-file metadata.json $SOURCES From d918edfd751f77c7d9d5c07f0a897ae86e3fb9f7 Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Mon, 20 Jul 2026 14:56:44 +0000 Subject: [PATCH 3/6] review: address PR #88 feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix invalid matrix context in job-level if (build job): GH Actions does not expose the matrix context to a job's own if condition. Moved platform resolution into a setup step that emits a BUILD_MATRIX JSON output (only the requested arch entries), consumed directly by build's strategy.matrix via fromJson() — no job-level if needed. - Fix printf format-string vulnerability in cosign signing step: a COSIGN_PRIVATE_KEY_PASSWORD containing % or \ would have been misinterpreted as a format directive. Now passed as a %s argument. - Mitigate shell template-injection warnings by passing IMAGE_TAG, IMAGE_NAME, and IMAGE_TAG_PRERELEASE through env instead of direct ${{ }} interpolation inside run: blocks (image-prerelease-tag step, Docker retag step). - Use multiline EOF syntax for the TEST_NAMES GITHUB_OUTPUT write so a REQUIRED_TESTS value containing newlines doesn't corrupt the output file. Co-Authored-By: Claude Sonnet 5 Signed-off-by: Yuval Levy --- .../workflows/incluster-comp-pr-merged.yaml | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/.github/workflows/incluster-comp-pr-merged.yaml b/.github/workflows/incluster-comp-pr-merged.yaml index 22c6e68..5dfb5a8 100644 --- a/.github/workflows/incluster-comp-pr-merged.yaml +++ b/.github/workflows/incluster-comp-pr-merged.yaml @@ -125,13 +125,16 @@ jobs: TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }} BUILD_AMD64: ${{ steps.platforms.outputs.BUILD_AMD64 }} BUILD_ARM64: ${{ steps.platforms.outputs.BUILD_ARM64 }} + BUILD_MATRIX: ${{ steps.platforms.outputs.BUILD_MATRIX }} permissions: contents: read pull-requests: read steps: - name: Set prerelease image tag id: image-prerelease-tag - run: echo "IMAGE_TAG_PRERELEASE=${{ inputs.IMAGE_TAG }}-prerelease" >> $GITHUB_OUTPUT + env: + IMAGE_TAG: ${{ inputs.IMAGE_TAG }} + run: echo "IMAGE_TAG_PRERELEASE=${IMAGE_TAG}-prerelease" >> "$GITHUB_OUTPUT" - name: Validate and resolve build platforms id: platforms @@ -152,12 +155,25 @@ jobs: echo "BUILD_AMD64=$AMD64" >> "$GITHUB_OUTPUT" echo "BUILD_ARM64=$ARM64" >> "$GITHUB_OUTPUT" + MATRIX_INCLUDE="[]" + if [ "$AMD64" = "true" ]; then + MATRIX_INCLUDE=$(echo "$MATRIX_INCLUDE" | jq -c '. + [{"platform":"linux/amd64","arch":"amd64","runner":"ubuntu-large"}]') + fi + if [ "$ARM64" = "true" ]; then + MATRIX_INCLUDE=$(echo "$MATRIX_INCLUDE" | jq -c '. + [{"platform":"linux/arm64","arch":"arm64","runner":"ubuntu-large-arm64"}]') + fi + echo "BUILD_MATRIX=$(jq -c -n --argjson include "$MATRIX_INCLUDE" '{"include":$include}')" >> "$GITHUB_OUTPUT" + - id: export_tests_to_env name: set test name - run: | - echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT env: input: ${{ inputs.REQUIRED_TESTS }} + run: | + { + echo 'TEST_NAMES<> "$GITHUB_OUTPUT" unit-test: needs: setup @@ -180,17 +196,9 @@ jobs: build: needs: [setup, unit-test] - if: ${{ (matrix.arch == 'amd64' && needs.setup.outputs.BUILD_AMD64 == 'true') || (matrix.arch == 'arm64' && needs.setup.outputs.BUILD_ARM64 == 'true') }} strategy: fail-fast: false - matrix: - include: - - platform: linux/amd64 - arch: amd64 - runner: ubuntu-large - - platform: linux/arm64 - arch: arm64 - runner: ubuntu-large-arm64 + matrix: ${{ fromJson(needs.setup.outputs.BUILD_MATRIX) }} runs-on: ${{ matrix.runner }} permissions: actions: write @@ -287,7 +295,7 @@ jobs: # Sign the image with key for verifier clients without keyless support # Put the key from environment variable to a file echo "$COSIGN_PRIVATE_KEY" > cosign.key - printf "$COSIGN_PRIVATE_KEY_PASSWORD" | cosign sign -key cosign.key -y ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} + printf "%s" "$COSIGN_PRIVATE_KEY_PASSWORD" | cosign sign -key cosign.key -y ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} rm cosign.key # Verify the image echo "$COSIGN_PUBLIC_KEY" > cosign.pub @@ -524,16 +532,20 @@ jobs: password: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }} - name: Docker retag + env: + IMAGE_NAME: ${{ inputs.IMAGE_NAME }} + TAG_PRERELEASE: ${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} + TAG: ${{ inputs.IMAGE_TAG }} run: | curl -L https://github.com/regclient/regclient/releases/download/v0.4.4/regctl-linux-amd64 >regctl chmod 755 regctl if [ ${{ inputs.COSIGN }} = true ]; then - ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:${{ inputs.IMAGE_TAG }} --digest-tags - ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:latest --digest-tags + ./regctl image copy "${IMAGE_NAME}:${TAG_PRERELEASE}" "${IMAGE_NAME}:${TAG}" --digest-tags + ./regctl image copy "${IMAGE_NAME}:${TAG_PRERELEASE}" "${IMAGE_NAME}:latest" --digest-tags else - ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:${{ inputs.IMAGE_TAG }} - ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:latest + ./regctl image copy "${IMAGE_NAME}:${TAG_PRERELEASE}" "${IMAGE_NAME}:${TAG}" + ./regctl image copy "${IMAGE_NAME}:${TAG_PRERELEASE}" "${IMAGE_NAME}:latest" fi - name: Create Release From 66d613926163136e678ab3bbc8dc8fc0472372f9 Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Mon, 20 Jul 2026 15:14:24 +0000 Subject: [PATCH 4/6] incluster-comp-pr-merged: simplify platform resolution to a single per-token loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the enumerated-whole-string case statement (three literal combos) and the resulting BUILD_AMD64/BUILD_ARM64 booleans with a single per-token loop over BUILD_PLATFORM split on commas. BUILD_MATRIX becomes the sole source of truth, consumed directly by both the build job's strategy.matrix and merge-images' source-tag list (previously each independently re-derived arch membership from the two booleans). Validation is intentionally minimal: only the arch suffix is checked against a known runner mapping (amd64/arm64), just enough to avoid silently scheduling an unrecognized platform onto the wrong runner. Everything else about platform-string validity is left to docker/build-push-action itself, which already rejects what it doesn't support. No duplicate-platform guard by design — a caller passing the same platform twice is a malformed input and the resulting failure is an acceptable consequence. Co-Authored-By: Claude Sonnet 5 --- .../workflows/incluster-comp-pr-merged.yaml | 54 +++++++------------ 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/.github/workflows/incluster-comp-pr-merged.yaml b/.github/workflows/incluster-comp-pr-merged.yaml index 5dfb5a8..9eaaf2c 100644 --- a/.github/workflows/incluster-comp-pr-merged.yaml +++ b/.github/workflows/incluster-comp-pr-merged.yaml @@ -123,8 +123,6 @@ jobs: outputs: IMAGE_TAG_PRERELEASE: ${{ steps.image-prerelease-tag.outputs.IMAGE_TAG_PRERELEASE }} TEST_NAMES: ${{ steps.export_tests_to_env.outputs.TEST_NAMES }} - BUILD_AMD64: ${{ steps.platforms.outputs.BUILD_AMD64 }} - BUILD_ARM64: ${{ steps.platforms.outputs.BUILD_ARM64 }} BUILD_MATRIX: ${{ steps.platforms.outputs.BUILD_MATRIX }} permissions: contents: read @@ -136,33 +134,27 @@ jobs: IMAGE_TAG: ${{ inputs.IMAGE_TAG }} run: echo "IMAGE_TAG_PRERELEASE=${IMAGE_TAG}-prerelease" >> "$GITHUB_OUTPUT" - - name: Validate and resolve build platforms + - name: Build platform matrix id: platforms env: BUILD_PLATFORM: ${{ inputs.BUILD_PLATFORM }} run: | - AMD64=false - ARM64=false - case "$BUILD_PLATFORM" in - linux/amd64) AMD64=true ;; - linux/arm64) ARM64=true ;; - linux/amd64,linux/arm64|linux/arm64,linux/amd64) AMD64=true; ARM64=true ;; - *) - echo "::error::Unsupported BUILD_PLATFORM '$BUILD_PLATFORM' — expected one of: linux/amd64, linux/arm64, linux/amd64,linux/arm64" - exit 1 - ;; - esac - echo "BUILD_AMD64=$AMD64" >> "$GITHUB_OUTPUT" - echo "BUILD_ARM64=$ARM64" >> "$GITHUB_OUTPUT" - - MATRIX_INCLUDE="[]" - if [ "$AMD64" = "true" ]; then - MATRIX_INCLUDE=$(echo "$MATRIX_INCLUDE" | jq -c '. + [{"platform":"linux/amd64","arch":"amd64","runner":"ubuntu-large"}]') - fi - if [ "$ARM64" = "true" ]; then - MATRIX_INCLUDE=$(echo "$MATRIX_INCLUDE" | jq -c '. + [{"platform":"linux/arm64","arch":"arm64","runner":"ubuntu-large-arm64"}]') - fi - echo "BUILD_MATRIX=$(jq -c -n --argjson include "$MATRIX_INCLUDE" '{"include":$include}')" >> "$GITHUB_OUTPUT" + INCLUDE="[]" + IFS=',' read -ra PLATFORMS <<< "$BUILD_PLATFORM" + for PLATFORM in "${PLATFORMS[@]}"; do + ARCH="${PLATFORM##*/}" + case "$ARCH" in + amd64) RUNNER=ubuntu-large ;; + arm64) RUNNER=ubuntu-large-arm64 ;; + *) + echo "::error::Unsupported platform '$PLATFORM' in BUILD_PLATFORM — expected linux/amd64 and/or linux/arm64" + exit 1 + ;; + esac + INCLUDE=$(echo "$INCLUDE" | jq -c --arg platform "$PLATFORM" --arg arch "$ARCH" --arg runner "$RUNNER" \ + '. + [{"platform":$platform,"arch":$arch,"runner":$runner}]') + done + echo "BUILD_MATRIX=$(jq -c -n --argjson include "$INCLUDE" '{"include":$include}')" >> "$GITHUB_OUTPUT" - id: export_tests_to_env name: set test name @@ -256,16 +248,10 @@ jobs: env: IMAGE: ${{ inputs.IMAGE_NAME }} TAG: ${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} - BUILD_AMD64: ${{ needs.setup.outputs.BUILD_AMD64 }} - BUILD_ARM64: ${{ needs.setup.outputs.BUILD_ARM64 }} + BUILD_MATRIX: ${{ needs.setup.outputs.BUILD_MATRIX }} run: | - SOURCES="" - if [ "$BUILD_AMD64" = "true" ]; then - SOURCES="$SOURCES ${IMAGE}:${TAG}-amd64" - fi - if [ "$BUILD_ARM64" = "true" ]; then - SOURCES="$SOURCES ${IMAGE}:${TAG}-arm64" - fi + SOURCES=$(echo "$BUILD_MATRIX" | jq -r --arg image "$IMAGE" --arg tag "$TAG" \ + '.include[] | "\($image):\($tag)-\(.arch)"' | tr '\n' ' ') docker buildx imagetools create -t "${IMAGE}:${TAG}" --metadata-file metadata.json $SOURCES echo "digest=$(jq -r '."containerimage.digest"' metadata.json)" >> "$GITHUB_OUTPUT" From 34d3b1de27de183e70379761436aa6ee49c72928 Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Mon, 20 Jul 2026 15:18:06 +0000 Subject: [PATCH 5/6] Revert multiline EOF change to export_tests_to_env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Out of scope for this PR — the cache-race fix and the platform-matrix simplification shouldn't be bundled with an unrelated CodeRabbit nitpick fix. REQUIRED_TESTS is always passed as compact single-line JSON by every known consumer today, so the single-line echo is correct in practice; can be revisited separately if it's ever needed. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/incluster-comp-pr-merged.yaml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/incluster-comp-pr-merged.yaml b/.github/workflows/incluster-comp-pr-merged.yaml index 9eaaf2c..635c3d8 100644 --- a/.github/workflows/incluster-comp-pr-merged.yaml +++ b/.github/workflows/incluster-comp-pr-merged.yaml @@ -158,14 +158,10 @@ jobs: - id: export_tests_to_env name: set test name + run: | + echo "TEST_NAMES=$input" >> $GITHUB_OUTPUT env: input: ${{ inputs.REQUIRED_TESTS }} - run: | - { - echo 'TEST_NAMES<> "$GITHUB_OUTPUT" unit-test: needs: setup From 985e26aa9542417b60c23550d631d99bda39d281 Mon Sep 17 00:00:00 2001 From: Yuval Levy Date: Mon, 20 Jul 2026 15:19:46 +0000 Subject: [PATCH 6/6] Revert env: passthrough nitpicks (IMAGE_TAG, Docker retag) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Out of scope for this PR. Keeping only the printf %s fix for COSIGN_PRIVATE_KEY_PASSWORD (a real, critical-severity finding) from CodeRabbit's review — these two were lower-severity nitpicks unrelated to the cache-race fix or platform-matrix simplification. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/incluster-comp-pr-merged.yaml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/incluster-comp-pr-merged.yaml b/.github/workflows/incluster-comp-pr-merged.yaml index 635c3d8..f2e3c63 100644 --- a/.github/workflows/incluster-comp-pr-merged.yaml +++ b/.github/workflows/incluster-comp-pr-merged.yaml @@ -130,9 +130,7 @@ jobs: steps: - name: Set prerelease image tag id: image-prerelease-tag - env: - IMAGE_TAG: ${{ inputs.IMAGE_TAG }} - run: echo "IMAGE_TAG_PRERELEASE=${IMAGE_TAG}-prerelease" >> "$GITHUB_OUTPUT" + run: echo "IMAGE_TAG_PRERELEASE=${{ inputs.IMAGE_TAG }}-prerelease" >> $GITHUB_OUTPUT - name: Build platform matrix id: platforms @@ -514,20 +512,16 @@ jobs: password: ${{ secrets.QUAYIO_REGISTRY_PASSWORD }} - name: Docker retag - env: - IMAGE_NAME: ${{ inputs.IMAGE_NAME }} - TAG_PRERELEASE: ${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} - TAG: ${{ inputs.IMAGE_TAG }} run: | curl -L https://github.com/regclient/regclient/releases/download/v0.4.4/regctl-linux-amd64 >regctl chmod 755 regctl if [ ${{ inputs.COSIGN }} = true ]; then - ./regctl image copy "${IMAGE_NAME}:${TAG_PRERELEASE}" "${IMAGE_NAME}:${TAG}" --digest-tags - ./regctl image copy "${IMAGE_NAME}:${TAG_PRERELEASE}" "${IMAGE_NAME}:latest" --digest-tags + ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:${{ inputs.IMAGE_TAG }} --digest-tags + ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:latest --digest-tags else - ./regctl image copy "${IMAGE_NAME}:${TAG_PRERELEASE}" "${IMAGE_NAME}:${TAG}" - ./regctl image copy "${IMAGE_NAME}:${TAG_PRERELEASE}" "${IMAGE_NAME}:latest" + ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:${{ inputs.IMAGE_TAG }} + ./regctl image copy ${{ inputs.IMAGE_NAME }}:${{ needs.setup.outputs.IMAGE_TAG_PRERELEASE }} ${{ inputs.IMAGE_NAME }}:latest fi - name: Create Release