-
Notifications
You must be signed in to change notification settings - Fork 136
Architecture-aware container image builds in deploy workflows #12640
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
.github/extension/actions/run-rad-commands/compute-build-platforms.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| #!/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 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 | ||
| # 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 |
97 changes: 97 additions & 0 deletions
97
.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.