From 83abe681fe61bc41654c16af120d1fa15dbcc21f Mon Sep 17 00:00:00 2001 From: Sylvain Niles Date: Mon, 10 Aug 2026 16:26:28 -0700 Subject: [PATCH 1/2] Add target-cluster architecture-aware container image builds Radius.Compute/containerImages builds run in an in-cluster BuildKit compiled for the runner's architecture (amd64 on standard GitHub-hosted runners). When an app leaves build.platforms unset the recipe defaults to multi-arch (linux/amd64,linux/arm64), so the arm64 half builds under QEMU emulation -- much slower and prone to emulation crashes. This lets the deploy workflows build only the platform(s) the target cluster actually runs. Contract (consumed by ai-extensions, radius-project/ai-extensions#300) via two template placeholders on the Azure and AWS run-rad-commands workflows: {{TARGET_CLUSTER_ARCH_MODE}} -> vars.RADIUS_BUILD_ARCH_MODE || 'detect' {{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}} -> vars.RADIUS_BUILD_PLATFORMS || 'linux/amd64,linux/arm64' Behavior (compute-build-platforms.sh, invoked by the shared run-rad-commands action after the target kubeconfig is configured and before the deploy): - mode 'detect', single-arch cluster -> build that one platform (no emulation) - mode 'detect', mixed or undetermined -> fallback platform list - explicit platform list (contains '/') -> honored verbatim, no detection - empty / unsubstituted placeholder -> feature off, recipe default applies The computed list is exported as RADIUS_EFFECTIVE_BUILD_PLATFORMS and injected as `--parameters platforms=` only when the app declares a `platforms` parameter and it was not already supplied via RADIUS_DEPLOY_PARAMS, flowing through the same conditional path as the existing app-image parameter. Apps that do not opt in are unaffected. Tests: - compute-build-platforms_test.sh: mode/detection/override/fallback matrix plus workflow and action wiring assertions (make test-build-platforms). - deploy-parameters_test.sh: platforms injection is gated on declaration, an empty computed list, and RADIUS_DEPLOY_PARAMS precedence. Signed-off-by: Sylvain Niles --- .../actions/run-rad-commands/action.yml | 60 ++++++++ .../compute-build-platforms.sh | 136 ++++++++++++++++++ .../compute-build-platforms_test.sh | 97 +++++++++++++ .../run-rad-commands/deploy-parameters.sh | 14 ++ .../deploy-parameters_test.sh | 24 ++++ .github/extension/run-rad-commands-aws.yml | 10 ++ .github/extension/run-rad-commands-azure.yml | 10 ++ build/test.mk | 6 +- 8 files changed, 356 insertions(+), 1 deletion(-) create mode 100755 .github/extension/actions/run-rad-commands/compute-build-platforms.sh create mode 100755 .github/extension/actions/run-rad-commands/compute-build-platforms_test.sh diff --git a/.github/extension/actions/run-rad-commands/action.yml b/.github/extension/actions/run-rad-commands/action.yml index b8634dc51a..077a2b1b88 100644 --- a/.github/extension/actions/run-rad-commands/action.yml +++ b/.github/extension/actions/run-rad-commands/action.yml @@ -35,6 +35,24 @@ inputs: registry-password: description: Password/token passed when the app declares the registryPassword parameter (feeds its Radius.Security/secrets registry Secret). required: true + build-arch-mode: + description: >- + Target-cluster architecture mode for Radius.Compute/containerImages builds. + 'detect' probes the target cluster node architectures; a single-arch cluster + builds only that platform, a mixed or undetermined cluster falls back to + build-fallback-platforms. An explicit platform list (any value containing + '/', e.g. 'linux/amd64') is honored verbatim. Empty or an unsubstituted + '{{TARGET_CLUSTER_ARCH_MODE}}' placeholder disables the feature and preserves + the recipe's default platforms. + required: false + default: "" + build-fallback-platforms: + description: >- + Comma-separated platform list used when build-arch-mode is 'detect' and the + target cluster is mixed-arch or its architecture cannot be determined. Empty + defaults to 'linux/amd64,linux/arm64'. + required: false + default: "" runs: using: composite @@ -64,6 +82,48 @@ runs: kubectl --kubeconfig "$TARGET_KUBECONFIG" create namespace "$APP_NS" fi + - name: Compute container build platforms from target cluster architecture + shell: bash + env: + BUILD_ARCH_MODE: ${{ inputs.build-arch-mode }} + BUILD_FALLBACK_PLATFORMS: ${{ inputs.build-fallback-platforms }} + run: | + set -euo pipefail + + # Feature off when the mode is empty or still an unsubstituted template + # placeholder: emit nothing and preserve the recipe's default platforms. + case "$BUILD_ARCH_MODE" in + "" | "{{TARGET_CLUSTER_ARCH_MODE}}") + echo "Architecture-aware container builds disabled; using recipe default platforms." + exit 0 + ;; + esac + + # Only 'detect' consults the cluster; an explicit platform list is honored + # verbatim by the helper without any kubectl call. + ARCHES="" + if [ "$BUILD_ARCH_MODE" = "detect" ]; then + TARGET_KUBECONFIG="$RADIUS_TARGET_KUBECONFIG" + if [ -n "${TARGET_KUBECONFIG:-}" ] && [ -f "$TARGET_KUBECONFIG" ]; then + # One architecture token per node; failures degrade to empty, which the + # helper treats as "undetermined" and resolves to the fallback list. + ARCHES=$(kubectl --kubeconfig "$TARGET_KUBECONFIG" get nodes \ + -o jsonpath='{range .items[*]}{.status.nodeInfo.architecture}{"\n"}{end}' \ + 2>/dev/null || true) + echo "Detected target cluster node architectures: $(echo "$ARCHES" | tr '\n' ' ')" + else + echo "No target kubeconfig; cluster architecture undetermined, will use fallback." + fi + fi + + EFFECTIVE=$(bash "$GITHUB_ACTION_PATH/compute-build-platforms.sh" \ + "$BUILD_ARCH_MODE" "$BUILD_FALLBACK_PLATFORMS" "$ARCHES") + + if [ -n "$EFFECTIVE" ]; then + echo "RADIUS_EFFECTIVE_BUILD_PLATFORMS=$EFFECTIVE" >> "$GITHUB_ENV" + echo "Effective container build platforms: $EFFECTIVE" + fi + - name: Run rad commands shell: bash env: diff --git a/.github/extension/actions/run-rad-commands/compute-build-platforms.sh b/.github/extension/actions/run-rad-commands/compute-build-platforms.sh new file mode 100755 index 0000000000..b61190647d --- /dev/null +++ b/.github/extension/actions/run-rad-commands/compute-build-platforms.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +# Computes the effective container image build platforms for a Radius deploy, +# given the requested arch mode, a fallback platform list, and the set of node +# architectures detected on the target cluster. +# +# This exists because Radius.Compute/containerImages builds run in an in-cluster +# BuildKit that is compiled for the runner's architecture (amd64 on standard +# GitHub-hosted runners). When an app leaves build.platforms unset, the recipe +# defaults to a multi-arch build (linux/amd64,linux/arm64), so the arm64 half is +# produced under QEMU emulation -- an order of magnitude slower and prone to +# emulation crashes. When the target cluster is single-arch, building only that +# one platform avoids emulation entirely; when it is mixed (or we cannot tell), +# a multi-arch fallback preserves portability. +# +# Contract (kept intentionally small and explicit): +# MODE +# - '' feature disabled -> emit nothing (callers +# - '{{TARGET_CLUSTER_ARCH_MODE}}' keep existing behavior: the recipe's own +# default platforms apply). An unsubstituted +# template placeholder is treated as disabled. +# - 'detect' probe the target cluster node architectures. +# - an explicit platform list any value containing '/', e.g. +# 'linux/amd64' or 'linux/amd64,linux/arm64', +# is honored verbatim with no detection. +# FALLBACK +# - comma-separated platform list used when detection is inconclusive +# (mixed-arch or undetermined). Empty or an unsubstituted placeholder +# defaults to 'linux/amd64,linux/arm64'. +# ARCHES +# - whitespace/newline-separated node architecture tokens as reported by +# kubectl (.status.nodeInfo.architecture), e.g. 'amd64' or 'amd64 arm64'. +# +# Output: the effective comma-separated platform list on stdout, or nothing when +# the feature is disabled. Detection outcomes: +# - exactly one recognized arch -> that single platform (no emulation) +# - multiple archs, or none/unknown -> the FALLBACK list +# +# The script is both sourceable (exposes compute_build_platforms) and directly +# executable (compute-build-platforms.sh MODE FALLBACK ARCHES). + +set -euo pipefail + +readonly DEFAULT_FALLBACK_PLATFORMS="linux/amd64,linux/arm64" +readonly MODE_PLACEHOLDER="{{TARGET_CLUSTER_ARCH_MODE}}" +readonly FALLBACK_PLACEHOLDER="{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}" + +# Map a node architecture token to an OCI platform, or empty when unrecognized. +_arch_to_platform() { + case "$1" in + amd64 | x86_64) printf 'linux/amd64' ;; + arm64 | aarch64) printf 'linux/arm64' ;; + *) printf '' ;; + esac +} + +# Normalize a comma/whitespace-separated platform list: trim each entry, drop +# blanks, de-duplicate, and re-join with commas in sorted (deterministic) order. +_normalize_platforms() { + # Turn commas into spaces, then let unquoted expansion word-split on all IFS + # whitespace (space, tab, newline) so mixed separators collapse to one token + # per line without relying on multi-character tr sets. + # shellcheck disable=SC2086 + printf '%s\n' ${1//,/ } | + sed '/^$/d' | + sort -u | + paste -sd, - +} + +# Resolve the fallback list, applying the default when empty or a placeholder. +_resolve_fallback() { + local fallback="$1" + if [[ -z "$fallback" || "$fallback" == "$FALLBACK_PLACEHOLDER" ]]; then + fallback="$DEFAULT_FALLBACK_PLATFORMS" + fi + _normalize_platforms "$fallback" +} + +# compute_build_platforms MODE FALLBACK ARCHES -> effective platforms on stdout. +compute_build_platforms() { + local mode="${1:-}" + local fallback="${2:-}" + local arches="${3:-}" + + # Feature disabled: empty or an unsubstituted placeholder. Emit nothing so + # callers inject no platform parameter and the recipe default applies. + if [[ -z "$mode" || "$mode" == "$MODE_PLACEHOLDER" ]]; then + return 0 + fi + + # Explicit override: any value that looks like a platform list (contains a + # '/') is honored verbatim, no detection. + if [[ "$mode" == */* ]]; then + _normalize_platforms "$mode" + return 0 + fi + + if [[ "$mode" != "detect" ]]; then + # Unrecognized mode keyword. Fail safe to the portable fallback rather + # than guessing, and warn so the misconfiguration is visible. + echo "compute-build-platforms: unrecognized mode '${mode}', using fallback platforms" >&2 + _resolve_fallback "$fallback" + return 0 + fi + + # detect: collect the distinct recognized platforms across all nodes. + local platforms=() + local unknown=0 + local token platform + for token in $arches; do + platform=$(_arch_to_platform "$token") + if [[ -z "$platform" ]]; then + unknown=1 + continue + fi + platforms+=("$platform") + done + + local distinct + distinct=$(_normalize_platforms "$(printf '%s\n' "${platforms[@]:-}")") + + # Single recognized arch across the whole cluster, and nothing unrecognized: + # build just that platform and skip emulation. + if [[ "$unknown" -eq 0 && -n "$distinct" && "$distinct" != *,* ]]; then + printf '%s' "$distinct" + return 0 + fi + + # Mixed-arch, empty, or anything we could not classify confidently. + _resolve_fallback "$fallback" +} + +# Run directly when invoked as a script (not when sourced by tests). +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + compute_build_platforms "${1:-}" "${2:-}" "${3:-}" +fi diff --git a/.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh b/.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh new file mode 100755 index 0000000000..add62d0bf3 --- /dev/null +++ b/.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +# Tests for compute-build-platforms.sh (the effective container build platform +# resolver) and for the workflow/action wiring that feeds it. Run directly or via +# `make test-build-platforms`. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR +readonly SCRIPT="${SCRIPT_DIR}/compute-build-platforms.sh" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../../.." && pwd)" +readonly REPO_ROOT +readonly AZURE_WF="${REPO_ROOT}/.github/extension/run-rad-commands-azure.yml" +readonly AWS_WF="${REPO_ROOT}/.github/extension/run-rad-commands-aws.yml" +readonly ACTION="${SCRIPT_DIR}/action.yml" + +fail() { + echo "FAIL: $*" >&2 + exit 1 +} + +# assert_platforms MODE FALLBACK ARCHES EXPECTED +assert_platforms() { + local mode="$1" fallback="$2" arches="$3" expected="$4" + local actual + actual="$(bash "${SCRIPT}" "${mode}" "${fallback}" "${arches}")" + if [[ "${actual}" != "${expected}" ]]; then + fail "compute('${mode}','${fallback}','${arches}') = '${actual}', expected '${expected}'" + fi +} + +DEFAULT="linux/amd64,linux/arm64" + +# --- Feature disabled ------------------------------------------------------- +# Empty mode and an unsubstituted placeholder both emit nothing so the recipe's +# default platforms apply (existing behavior preserved). +assert_platforms "" "" "" "" +assert_platforms "" "${DEFAULT}" "amd64" "" +assert_platforms "{{TARGET_CLUSTER_ARCH_MODE}}" "${DEFAULT}" "amd64 arm64" "" + +# --- detect: single-arch cluster -> that single platform, no emulation ------ +assert_platforms "detect" "${DEFAULT}" "amd64" "linux/amd64" +assert_platforms "detect" "${DEFAULT}" "arm64" "linux/arm64" +assert_platforms "detect" "${DEFAULT}" "x86_64" "linux/amd64" +assert_platforms "detect" "${DEFAULT}" "aarch64" "linux/arm64" +# Multiple nodes, all the same arch. +assert_platforms "detect" "${DEFAULT}" "amd64 +amd64 +amd64" "linux/amd64" + +# --- detect: mixed / undetermined -> fallback ------------------------------- +assert_platforms "detect" "${DEFAULT}" "amd64 arm64" "${DEFAULT}" +assert_platforms "detect" "${DEFAULT}" "" "${DEFAULT}" +# An unknown architecture is not classified confidently -> fallback. +assert_platforms "detect" "${DEFAULT}" "ppc64le" "${DEFAULT}" +# One known plus one unknown is still not confidently single-arch -> fallback. +assert_platforms "detect" "${DEFAULT}" "amd64 ppc64le" "${DEFAULT}" + +# --- detect: custom / placeholder fallback ---------------------------------- +assert_platforms "detect" "linux/arm64" "amd64 arm64" "linux/arm64" +assert_platforms "detect" "{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}" "amd64 arm64" "${DEFAULT}" + +# --- Explicit override: honored verbatim, no detection ---------------------- +assert_platforms "linux/amd64" "${DEFAULT}" "arm64" "linux/amd64" +assert_platforms "linux/amd64,linux/arm64" "${DEFAULT}" "" "${DEFAULT}" +# Whitespace and duplicates are normalized (trimmed, de-duplicated, sorted). +assert_platforms "linux/amd64, linux/amd64 , linux/arm64" "${DEFAULT}" "" "${DEFAULT}" + +# --- Unrecognized mode -> fail safe to fallback, with a warning ------------- +unrecognized_err="$(bash "${SCRIPT}" "bogus" "${DEFAULT}" "amd64" 2>&1 >/dev/null)" +assert_platforms "bogus" "${DEFAULT}" "amd64" "${DEFAULT}" +echo "${unrecognized_err}" | grep -q "unrecognized mode" || + fail "expected a warning on stderr for an unrecognized mode" + +# --- Workflow / action wiring ------------------------------------------------ +for wf in "${AZURE_WF}" "${AWS_WF}"; do + grep -q "{{TARGET_CLUSTER_ARCH_MODE}}" "${wf}" || + fail "expected TARGET_CLUSTER_ARCH_MODE placeholder in $(basename "${wf}")" + grep -q "{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}" "${wf}" || + fail "expected TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS placeholder in $(basename "${wf}")" + grep -q "build-arch-mode:" "${wf}" || + fail "expected build-arch-mode wired to run-rad-commands in $(basename "${wf}")" + grep -q "build-fallback-platforms:" "${wf}" || + fail "expected build-fallback-platforms wired to run-rad-commands in $(basename "${wf}")" +done + +grep -q "build-arch-mode:" "${ACTION}" || + fail "expected build-arch-mode input declared in run-rad-commands action.yml" +grep -q "build-fallback-platforms:" "${ACTION}" || + fail "expected build-fallback-platforms input declared in run-rad-commands action.yml" +grep -q "compute-build-platforms.sh" "${ACTION}" || + fail "expected the detection step to invoke compute-build-platforms.sh" +grep -q "RADIUS_EFFECTIVE_BUILD_PLATFORMS" "${ACTION}" || + fail "expected the detection step to export RADIUS_EFFECTIVE_BUILD_PLATFORMS" + +echo "compute-build-platforms tests passed" diff --git a/.github/extension/actions/run-rad-commands/deploy-parameters.sh b/.github/extension/actions/run-rad-commands/deploy-parameters.sh index f5e3e7be8f..bcdb4f0ce7 100644 --- a/.github/extension/actions/run-rad-commands/deploy-parameters.sh +++ b/.github/extension/actions/run-rad-commands/deploy-parameters.sh @@ -113,4 +113,18 @@ append_generated_app_params() { --parameters "registryPassword=${REGISTRY_PASSWORD}" ) fi + # Target-cluster architecture-aware container builds. When the deploy computed + # an effective platform list (see compute-build-platforms.sh) and the app + # declares a `platforms` parameter, pass it so Radius.Compute/containerImages + # builds only the needed platform(s) instead of the recipe's multi-arch + # default (which builds arm64 under QEMU emulation on an amd64 runner). Apps + # that do not declare `platforms` are unaffected, and a value already supplied + # via RADIUS_DEPLOY_PARAMS is not overridden. + if [[ -n "${RADIUS_EFFECTIVE_BUILD_PLATFORMS:-}" ]] && + app_declares_parameter "platforms" && + ! deploy_params_has_key "platforms"; then + GENERATED_APP_PARAMS+=( + --parameters "platforms=${RADIUS_EFFECTIVE_BUILD_PLATFORMS}" + ) + fi } diff --git a/.github/extension/actions/run-rad-commands/deploy-parameters_test.sh b/.github/extension/actions/run-rad-commands/deploy-parameters_test.sh index 358ea1057a..fb8d28daa3 100644 --- a/.github/extension/actions/run-rad-commands/deploy-parameters_test.sh +++ b/.github/extension/actions/run-rad-commands/deploy-parameters_test.sh @@ -132,6 +132,30 @@ assert_param_absent "registryUsername=${REGISTRY_USERNAME}" assert_param_present "registryPassword=${REGISTRY_PASSWORD}" DEPLOY_PARAMS_JSON="" +# Architecture-aware build platforms: injected only when the app declares a +# `platforms` parameter and the deploy computed an effective platform list. +RADIUS_EFFECTIVE_BUILD_PLATFORMS="linux/amd64" + +run_generated_params_case '{"platforms":{}}' +assert_param_present "platforms=linux/amd64" + +# App does not declare `platforms`: nothing injected. +run_generated_params_case '{"environment":{}}' +assert_param_absent "platforms=linux/amd64" + +# No effective platforms computed (feature off/undetermined): nothing injected. +RADIUS_EFFECTIVE_BUILD_PLATFORMS="" +run_generated_params_case '{"platforms":{}}' +assert_param_absent "platforms=linux/amd64" + +# A value supplied via RADIUS_DEPLOY_PARAMS is not overridden by the computed one. +RADIUS_EFFECTIVE_BUILD_PLATFORMS="linux/amd64" +DEPLOY_PARAMS_JSON='{"platforms":"linux/arm64"}' +run_generated_params_case '{"platforms":{}}' +assert_param_absent "platforms=linux/amd64" +DEPLOY_PARAMS_JSON="" +RADIUS_EFFECTIVE_BUILD_PLATFORMS="" + reset_discovery write_template '{"image":{}}' load_declared_app_params "${APP_FILE}" diff --git a/.github/extension/run-rad-commands-aws.yml b/.github/extension/run-rad-commands-aws.yml index 484772360f..3ce2378fec 100644 --- a/.github/extension/run-rad-commands-aws.yml +++ b/.github/extension/run-rad-commands-aws.yml @@ -36,6 +36,14 @@ env: ENVIRONMENT: ${{ inputs.environment }} APP_FILE: '{{APP_FILE}}' APP_IMAGE: ${{ inputs.image || github.sha || 'latest' }} + # Target-cluster architecture-aware container image builds. Rendered by the + # Radius extension: TARGET_CLUSTER_ARCH_MODE -> ${{ vars.RADIUS_BUILD_ARCH_MODE + # || 'detect' }} and TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS -> + # ${{ vars.RADIUS_BUILD_PLATFORMS || 'linux/amd64,linux/arm64' }}. When a + # template does not opt in, the unsubstituted placeholder disables the feature + # and the containerImages recipe's default platforms apply (existing behavior). + TARGET_CLUSTER_ARCH_MODE: '{{TARGET_CLUSTER_ARCH_MODE}}' + TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS: '{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}' jobs: deploy: @@ -387,6 +395,8 @@ jobs: deploy-params: ${{ secrets.RADIUS_DEPLOY_PARAMS }} registry-username: ${{ github.actor }} registry-password: ${{ secrets.GITHUB_TOKEN }} + build-arch-mode: ${{ env.TARGET_CLUSTER_ARCH_MODE }} + build-fallback-platforms: ${{ env.TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS }} # Publish status on failure too: an empty Deployed tab is least helpful # exactly when a deploy failed and the user is trying to see which diff --git a/.github/extension/run-rad-commands-azure.yml b/.github/extension/run-rad-commands-azure.yml index cc9d5ce9d7..79f7350220 100644 --- a/.github/extension/run-rad-commands-azure.yml +++ b/.github/extension/run-rad-commands-azure.yml @@ -36,6 +36,14 @@ env: ENVIRONMENT: ${{ inputs.environment }} APP_FILE: '{{APP_FILE}}' APP_IMAGE: ${{ inputs.image || github.sha || 'latest' }} + # Target-cluster architecture-aware container image builds. Rendered by the + # Radius extension: TARGET_CLUSTER_ARCH_MODE -> ${{ vars.RADIUS_BUILD_ARCH_MODE + # || 'detect' }} and TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS -> + # ${{ vars.RADIUS_BUILD_PLATFORMS || 'linux/amd64,linux/arm64' }}. When a + # template does not opt in, the unsubstituted placeholder disables the feature + # and the containerImages recipe's default platforms apply (existing behavior). + TARGET_CLUSTER_ARCH_MODE: '{{TARGET_CLUSTER_ARCH_MODE}}' + TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS: '{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}' jobs: deploy: @@ -342,6 +350,8 @@ jobs: deploy-params: ${{ secrets.RADIUS_DEPLOY_PARAMS }} registry-username: ${{ github.actor }} registry-password: ${{ secrets.GITHUB_TOKEN }} + build-arch-mode: ${{ env.TARGET_CLUSTER_ARCH_MODE }} + build-fallback-platforms: ${{ env.TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS }} # Publish status on failure too: an empty Deployed tab is least helpful # exactly when a deploy failed and the user is trying to see which diff --git a/build/test.mk b/build/test.mk index b4ab6487ab..894e653f6b 100644 --- a/build/test.mk +++ b/build/test.mk @@ -53,7 +53,7 @@ GOTEST_OPTS ?= GOTEST_TOOL ?= go tool gotestsum $(GOTESTSUM_OPTS) -- .PHONY: test -test: test-get-envtools test-helm test-manage-radius-installation test-update-tools-pr test-run-rad-commands-action test-publish-deploy-status ## Runs unit tests, excluding kubernetes controller tests +test: test-get-envtools test-helm test-manage-radius-installation test-update-tools-pr test-run-rad-commands-action test-build-platforms test-publish-deploy-status ## Runs unit tests, excluding kubernetes controller tests KUBEBUILDER_ASSETS="$(shell $(ENV_SETUP) use -p path ${K8S_VERSION} --arch amd64)" CGO_ENABLED=1 $(GOTEST_TOOL) ./pkg/... $(GOTEST_OPTS) .PHONY: test-manage-radius-installation @@ -68,6 +68,10 @@ test-update-tools-pr: ## Tests the automated tool-update pull request workflow test-run-rad-commands-action: ## Tests application deploy parameter filtering in the run-rad-commands action @bash ./.github/extension/actions/run-rad-commands/deploy-parameters_test.sh +.PHONY: test-build-platforms +test-build-platforms: ## Tests container build platform resolution and workflow wiring in the run-rad-commands action + @bash ./.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh + .PHONY: test-publish-deploy-status test-publish-deploy-status: ## Tests deploy status publishing in the publish-deploy-status action @bash ./.github/extension/actions/publish-deploy-status/publish-deploy-status_test.sh From 17807655b68335b5fb94d7256c17aa1d9d28897b Mon Sep 17 00:00:00 2001 From: Sylvain Niles Date: Mon, 10 Aug 2026 17:03:07 -0700 Subject: [PATCH 2/2] Address review: guard kubeconfig under set -u; clarify mode contract comment - action.yml: default-expand RADIUS_TARGET_KUBECONFIG so an unset value does not trip set -u before the empty case is handled (degrades to fallback). - compute-build-platforms.sh: rewrite the malformed MODE contract header comment (unfinished parenthesis / broken bullet) for readability. Signed-off-by: Sylvain Niles --- .../actions/run-rad-commands/action.yml | 6 ++++-- .../run-rad-commands/compute-build-platforms.sh | 17 +++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/extension/actions/run-rad-commands/action.yml b/.github/extension/actions/run-rad-commands/action.yml index 077a2b1b88..ab170a0a4f 100644 --- a/.github/extension/actions/run-rad-commands/action.yml +++ b/.github/extension/actions/run-rad-commands/action.yml @@ -103,8 +103,10 @@ runs: # verbatim by the helper without any kubectl call. ARCHES="" if [ "$BUILD_ARCH_MODE" = "detect" ]; then - TARGET_KUBECONFIG="$RADIUS_TARGET_KUBECONFIG" - if [ -n "${TARGET_KUBECONFIG:-}" ] && [ -f "$TARGET_KUBECONFIG" ]; then + # Default-expand so an unset RADIUS_TARGET_KUBECONFIG does not trip + # `set -u`; the empty case is handled below and degrades to fallback. + TARGET_KUBECONFIG="${RADIUS_TARGET_KUBECONFIG:-}" + if [ -n "$TARGET_KUBECONFIG" ] && [ -f "$TARGET_KUBECONFIG" ]; then # One architecture token per node; failures degrade to empty, which the # helper treats as "undetermined" and resolves to the fallback list. ARCHES=$(kubectl --kubeconfig "$TARGET_KUBECONFIG" get nodes \ diff --git a/.github/extension/actions/run-rad-commands/compute-build-platforms.sh b/.github/extension/actions/run-rad-commands/compute-build-platforms.sh index b61190647d..1df25387c2 100755 --- a/.github/extension/actions/run-rad-commands/compute-build-platforms.sh +++ b/.github/extension/actions/run-rad-commands/compute-build-platforms.sh @@ -15,14 +15,15 @@ # # Contract (kept intentionally small and explicit): # MODE -# - '' feature disabled -> emit nothing (callers -# - '{{TARGET_CLUSTER_ARCH_MODE}}' keep existing behavior: the recipe's own -# default platforms apply). An unsubstituted -# template placeholder is treated as disabled. -# - 'detect' probe the target cluster node architectures. -# - an explicit platform list any value containing '/', e.g. -# 'linux/amd64' or 'linux/amd64,linux/arm64', -# is honored verbatim with no detection. +# - '' Feature disabled: emit nothing so the recipe +# default platforms apply (existing behavior). +# - '{{TARGET_CLUSTER_ARCH_MODE}}' +# An unsubstituted template placeholder is +# treated the same as empty (disabled). +# - 'detect' Probe the target cluster node architectures. +# - an explicit platform list Any value containing '/', e.g. 'linux/amd64' +# or 'linux/amd64,linux/arm64', is honored +# verbatim with no detection. # FALLBACK # - comma-separated platform list used when detection is inconclusive # (mixed-arch or undetermined). Empty or an unsubstituted placeholder