Skip to content

Architecture-aware container image builds in deploy workflows - #12640

Merged
DariuszPorowski merged 2 commits into
mainfrom
arch-aware-container-builds
Aug 18, 2026
Merged

DariuszPorowski merged 2 commits into
mainfrom
arch-aware-container-builds

Conversation

@sylvainsf

Copy link
Copy Markdown
Contributor

Description

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, which is roughly an order of magnitude slower and prone to emulation crashes (see the analysis on #12595). This PR lets the Azure and AWS deploy workflows build only the platform(s) the target cluster actually runs, while preserving multi-arch when it is genuinely needed.

This is the upstream half of the contract in radius-project/ai-extensions#300.

Contract

Two template placeholders on the Azure and AWS run-rad-commands workflows, rendered by the extension:

Placeholder Extension default
{{TARGET_CLUSTER_ARCH_MODE}} ${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}
{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}} ${{ vars.RADIUS_BUILD_PLATFORMS || 'linux/amd64,linux/arm64' }}

Behavior

Resolved by compute-build-platforms.sh, invoked by the shared run-rad-commands action after the target kubeconfig is configured and before the app is built/deployed:

Mode Cluster Result
detect single-arch build that one platform (no emulation)
detect mixed / undetermined fallback platform list
explicit list (contains /, e.g. linux/amd64) n/a honored verbatim, no detection
empty / unsubstituted placeholder n/a feature off, recipe default applies (existing behavior)

The computed list is exported as RADIUS_EFFECTIVE_BUILD_PLATFORMS and injected as --parameters platforms=<list> 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 declare platforms, and templates that do not render the placeholders, are unaffected.

Mixed-arch is a first-class outcome, not an error. A cluster with both amd64 and arm64 nodes resolves to the fallback multi-arch list so images stay portable; only single-arch clusters drop to a single platform to skip emulation.

Design notes

  • Kept the contract small and explicit. Recognized modes are detect, an explicit platform list, or empty/placeholder (off). An unrecognized keyword fails safe to the fallback list with a warning, documented in the workflow and script comments.
  • Detection uses kubectl get nodes -o jsonpath=...nodeInfo.architecture; a failed probe degrades to empty, which the resolver treats as "undetermined" and maps to the fallback.
  • No Radius runtime/CLI changes; this is entirely in the deploy workflow templates, the shared composite action, and a helper script.

Testing

  • make test-build-platforms — new compute-build-platforms_test.sh: mode/detection/override/fallback matrix (single-arch, mixed-arch, unknown-arch, x86_64/aarch64 aliases, normalization, unrecognized mode), plus assertions that both workflows carry the placeholders and wire the inputs, and that the action declares the inputs and runs the helper.
  • make test-run-rad-commands-action — extended deploy-parameters_test.sh: platforms is injected only when declared, skipped when no effective list was computed, and never overrides a RADIUS_DEPLOY_PARAMS-supplied value.
  • shellcheck clean on all shell; all three modified YAML files parse.

Files

File Change
.github/extension/actions/run-rad-commands/compute-build-platforms.sh New: resolves effective build platforms from mode/fallback/detected arches
.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh New: unit + wiring tests
.github/extension/actions/run-rad-commands/action.yml New build-arch-mode / build-fallback-platforms inputs; detection step exporting RADIUS_EFFECTIVE_BUILD_PLATFORMS
.github/extension/actions/run-rad-commands/deploy-parameters.sh Inject platforms when the app declares it and a list was computed
.github/extension/actions/run-rad-commands/deploy-parameters_test.sh Coverage for the injection
.github/extension/run-rad-commands-azure.yml / -aws.yml Placeholders + pass the two inputs to run-rad-commands
build/test.mk test-build-platforms target, added to test

Related

@sylvainsf
sylvainsf requested a review from a team as a code owner August 10, 2026 23:29
Copilot AI lite review requested due to automatic review settings August 10, 2026 23:29
@sylvainsf
sylvainsf requested a review from a team as a code owner August 10, 2026 23:29
@sylvainsf sylvainsf added the pr:standard Ongoing maintenance, minor improvements, documentation updates, and routine development work label Aug 10, 2026
@github-actions

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the shared run-rad-commands deploy workflows/actions to compute an effective container image build platform list based on the target cluster’s node architectures, so single-arch clusters avoid slow/flaky QEMU-emulated multi-arch builds while mixed/undetermined clusters keep the multi-arch fallback.

Changes:

  • Add compute-build-platforms.sh (and tests) to resolve effective build platforms from mode/fallback/detected arches.
  • Wire new build-arch-mode / build-fallback-platforms inputs through the composite action and Azure/AWS workflow templates.
  • Inject --parameters platforms=... only when the app declares platforms and it’s not already provided via RADIUS_DEPLOY_PARAMS; add Make/test coverage.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
build/test.mk Adds test-build-platforms and includes it in make test.
.github/extension/run-rad-commands-azure.yml Adds template placeholders and passes arch inputs to the shared action.
.github/extension/run-rad-commands-aws.yml Adds template placeholders and passes arch inputs to the shared action.
.github/extension/actions/run-rad-commands/action.yml Declares new inputs and adds a step to detect/compute/export RADIUS_EFFECTIVE_BUILD_PLATFORMS.
.github/extension/actions/run-rad-commands/compute-build-platforms.sh New platform resolver script (mode/fallback/detect logic).
.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh New unit + wiring tests for resolver/workflows/action.
.github/extension/actions/run-rad-commands/deploy-parameters.sh Injects platforms parameter when applicable.
.github/extension/actions/run-rad-commands/deploy-parameters_test.sh Adds coverage for platforms injection behavior.
Suppressed comments (2)

.github/extension/actions/run-rad-commands/action.yml:113

  • This currently suppresses kubectl errors and can hide useful diagnostics (RBAC, connectivity, etc.). Prefer letting stderr through while still degrading to empty ARCHES via || true. Also avoid echo "$ARCHES" in command substitution (it can mangle backslashes); use printf instead.
            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' ' ')"

.github/extension/actions/run-rad-commands/compute-build-platforms.sh:66

  • sort is locale-dependent; without pinning the locale, the "sorted (deterministic) order" guarantee can vary across runner images/locales. Set LC_ALL=C for stable collation.
        sort -u |

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/extension/actions/run-rad-commands/action.yml Outdated
Comment thread .github/extension/actions/run-rad-commands/compute-build-platforms.sh Outdated
@github-actions

github-actions Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Unit Tests

    2 files  ±0    457 suites  ±0   7m 26s ⏱️ + 1m 1s
6 263 tests ±0  6 261 ✅ ±0  2 💤 ±0  0 ❌ ±0 
7 495 runs  ±0  7 493 ✅ ±0  2 💤 ±0  0 ❌ ±0 

Results for commit 1780765. ± Comparison against base commit 5ba29da.

♻️ This comment has been updated with latest results.

@codecov

codecov Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 54.32%. Comparing base (5ba29da) to head (1780765).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #12640      +/-   ##
==========================================
+ Coverage   54.31%   54.32%   +0.01%     
==========================================
  Files         770      770              
  Lines       51240    51240              
==========================================
+ Hits        27829    27837       +8     
+ Misses      20795    20791       -4     
+ Partials     2616     2612       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Functional Tests - corerp-cloud

32 tests  ±0   31 ✅ ±0   21m 3s ⏱️ + 1m 24s
 2 suites ±0    1 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 77182df. ± Comparison against base commit 5ba29da.

♻️ This comment has been updated with latest results.

@sylvainsf

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Addressed in bd0005c.

  • set -u on the kubeconfig assignment (action.yml L106/L110): Fixed. TARGET_KUBECONFIG now default-expands ${RADIUS_TARGET_KUBECONFIG:-}, so an unset value no longer trips set -u before the empty case is handled; detect mode degrades cleanly to the fallback when no kubeconfig is present.
  • Malformed MODE contract header comment: Fixed. Rewrote the block so the bullets and parenthesis are well-formed.
  • stderr capture in the test (L71): This one is a false positive, so I left it. The idiom "$(cmd 2>&1 >/dev/null)" is the correct order for "capture stderr, discard stdout": 2>&1 first points fd2 at the command-substitution capture (currently fd1), then >/dev/null redirects fd1 only. Reversing to >/dev/null 2>&1 would discard both. The test asserts the warning is captured and it passes, which confirms stderr is captured.

The corerp-cloud functional failure is unrelated to this change (this PR only touches the extension workflow YAML, the shared composite action, and a helper script; that lane does not exercise run-rad-commands). Rebasing on main will re-trigger it.

@sylvainsf
sylvainsf force-pushed the arch-aware-container-builds branch from bd0005c to 84c8ca4 Compare August 11, 2026 00:18
@sylvainsf
sylvainsf enabled auto-merge August 14, 2026 18:20
@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

Functional Tests - dynamicrp-noncloud

67 tests  ±0   67 ✅ ±0   19m 20s ⏱️ +53s
 1 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 8662557. ± Comparison against base commit e208937.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Aug 17, 2026 •

Copy link
Copy Markdown

Functional Tests - samples-noncloud

3 tests  ±0   3 ✅ ±0   1m 24s ⏱️ -1s
1 suites ±0   0 💤 ±0 
1 files   ±0   0 ❌ ±0 

Results for commit 77182df. ± Comparison against base commit 5ba29da.

♻️ This comment has been updated with latest results.

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=<list>` 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 <sylvainniles@microsoft.com>
…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 <sylvainniles@microsoft.com>
@sylvainsf
sylvainsf force-pushed the arch-aware-container-builds branch from 77182df to 1780765 Compare August 18, 2026 03:26
@radius-functional-tests

radius-functional-tests Bot commented Aug 18, 2026 •

Copy link
Copy Markdown

Radius functional test overview

🔍 Go to test action run

Click here to see the test run details
Name Value
Repository radius-project/radius
Commit ref 1780765
Unique ID funca8e5775c90
Image tag pr-funca8e5775c90
  • Dapr: 1.14.4
  • Azure KeyVault CSI driver: 1.4.2
  • Azure Workload identity webhook: 1.3.0
  • Bicep recipe location ghcr.io/radius-project/dev/test/testrecipes/test-bicep-recipes/<name>:pr-funca8e5775c90
  • Terraform recipe location http://tf-module-server.radius-test-tf-module-server.svc.cluster.local/<name>.zip (in cluster)
  • applications-rp test image location: ghcr.io/radius-project/dev/applications-rp:pr-funca8e5775c90
  • dynamic-rp test image location: ghcr.io/radius-project/dev/dynamic-rp:pr-funca8e5775c90
  • controller test image location: ghcr.io/radius-project/dev/controller:pr-funca8e5775c90
  • ucp test image location: ghcr.io/radius-project/dev/ucpd:pr-funca8e5775c90
  • deployment-engine test image location: ghcr.io/radius-project/deployment-engine:latest

Test Status

⌛ Building Radius and pushing container images for functional tests...
✅ Container images build succeeded
⌛ Publishing Bicep Recipes for functional tests...
✅ Recipe publishing succeeded
⌛ Starting ucp-cloud functional tests...
⌛ Starting corerp-cloud functional tests...
✅ ucp-cloud functional tests succeeded
✅ corerp-cloud functional tests succeeded

@sylvainsf
sylvainsf added this pull request to the merge queue Aug 18, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 18, 2026
@DariuszPorowski
DariuszPorowski merged commit 6882b5f into main Aug 18, 2026
76 checks passed
@DariuszPorowski
DariuszPorowski deleted the arch-aware-container-builds branch August 18, 2026 04:29
pull Bot pushed a commit to TheTechOddBug/radius that referenced this pull request Aug 19, 2026
…ng) (radius-project#12721)

## Summary

These deploy workflow templates are rendered by substituting `{{...}}`
placeholders. For the architecture placeholders, the tooling injects a
GitHub Actions expression whose string-literal default is
**single-quoted** (GHA requires single quotes for string literals):

```
${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}
```

But these templates wrapped the placeholder scalar in **single** quotes:

```yaml
TARGET_CLUSTER_ARCH_MODE: '{{TARGET_CLUSTER_ARCH_MODE}}'
```

After substitution the quotes nest and YAML terminates the scalar early,
producing invalid YAML:

```yaml
TARGET_CLUSTER_ARCH_MODE: '${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}'
```

Every Azure and AWS deploy dispatch then failed with `HTTP 422: failed
to parse workflow ... error in your yaml syntax`.

## Fix

In both `.github/extension/run-rad-commands-azure.yml` and
`.github/extension/run-rad-commands-aws.yml`, the two architecture
placeholder scalars are changed from single to **double** quotes:

```yaml
TARGET_CLUSTER_ARCH_MODE: "{{TARGET_CLUSTER_ARCH_MODE}}"
TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS: "{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}"
```

The rendered result then becomes valid YAML — the outer double quotes
safely contain the inner single-quoted GHA string literal:

```yaml
TARGET_CLUSTER_ARCH_MODE: "${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}"
```

The other single-quoted scalars (`APP_FILE: '{{APP_FILE}}'`, `default:
'{{ENV}}'`, `environment: ${{ inputs.environment || '{{ENV}}' }}`)
inject plain values that contain no single quotes, so they remain valid
and are left unchanged.

## Verification

- Both provider files parse as valid YAML.
- Simulated the substitution (`{{TARGET_CLUSTER_ARCH_MODE}}` → `${{
vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}` and
`{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}` → `${{
vars.RADIUS_BUILD_PLATFORMS || 'linux/amd64,linux/arm64' }}`) in temp
copies of each file and confirmed they still parse as valid YAML, with
the GHA expression preserved intact as the string value.

## Related

- Regression introduced by the single-quoting in radius-project#12640

Signed-off-by: sk593 <shruthikumar@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
DariuszPorowski added a commit that referenced this pull request Aug 26, 2026
….1 (#12831)

Patch release `v0.60.1` for channel `0.60`.

## Backported PRs

Listed in cherry-pick (topological) order. **Gap filler** = not
requested for the patch, but included because it touched the same region
of a shared file between `release/0.60` and a requested commit. Without
it the cherry-pick has no common merge base and conflicts.

| # | Commit | PR | Role |
| --- | --- | --- | --- |
| 1 | `6882b5fdf` | #12640 | Requested |
| 2 | `af0443809` | #12721 | Requested |
| 3 | `7c6de043d` | #12733 | **Gap filler** —
`.github/workflows/codeql.yml` |
| 4 | `ca7b5a3e7` | #12728 | Requested |
| 5 | `2eac7c1f1` | #12702 | Requested |
| 6 | `b4ebe2f28` | #12751 | Requested |
| 7 | `e30a2594d` | #12727 | Requested |
| 8 | `3c7dfecd6` | #12769 | **Gap filler** —
`.github/workflows/codeql.yml` |
| 9 | `787b6ca1a` | #12765 | **Gap filler** — `build/test.mk` |
| 10 | `f84184955` | #12775 | Requested |
| 11 | `415d5b9c2` | #12779 | Requested |
| 12 | `a1d976013` | #12782 | Requested |
| 13 | `14a21bd0d` | #12786 | **Gap filler** — `build/test.mk` |
| 14 | `a87146c77` | #12764 | **Gap filler** — `build/test.mk` |
| 15 | `073ca3cfa` | #12758 | Requested |
| 16 | `5e1f94c13` | #12785 | **Gap filler** —
`pkg/cli/cmd/install/kubernetes/kubernetes.go` |
| 17 | `1fd650af5` | #12742 | Requested |
| 18 | `19376c3f3` | #12829 | Requested |

Five of the six gap fillers are CI/test-only. #12785 is not: it adds a
Helm `control-plane-readiness` Job, a `pre-upgrade
wait-for-control-plane` command, UCP readiness probes, and a
NetworkPolicy change. It is required for #12829 to apply.

## Validation

Full chain dry-run cherry-picked onto `release/0.60` with zero
conflicts. `go build ./...` clean; `pkg/cli/cmd/install/kubernetes`,
`cmd/pre-upgrade/cmd`, `pkg/cli/...`, `pkg/graph/...`, and
`test/validation` all pass.

---------

Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
DariuszPorowski pushed a commit that referenced this pull request Aug 26, 2026
## Description

`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, which is roughly an
order of magnitude slower and prone to emulation crashes (see the
analysis on #12595). This PR lets the Azure and AWS
deploy workflows build only the platform(s) the target cluster actually
runs, while preserving multi-arch when it is genuinely needed.

This is the upstream half of the contract in
radius-project/ai-extensions#300.

## Contract

Two template placeholders on the Azure and AWS `run-rad-commands`
workflows, rendered by the extension:

| Placeholder | Extension default |
| --- | --- |
| `{{TARGET_CLUSTER_ARCH_MODE}}` | `${{ vars.RADIUS_BUILD_ARCH_MODE \|\|
'detect' }}` |
| `{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}` | `${{
vars.RADIUS_BUILD_PLATFORMS \|\| 'linux/amd64,linux/arm64' }}` |

## Behavior

Resolved by `compute-build-platforms.sh`, invoked by the shared
`run-rad-commands` action after the target kubeconfig is configured and
before the app is built/deployed:

| Mode | Cluster | Result |
| --- | --- | --- |
| `detect` | single-arch | build that one platform (no emulation) |
| `detect` | mixed / undetermined | fallback platform list |
| explicit list (contains `/`, e.g. `linux/amd64`) | n/a | honored
verbatim, no detection |
| empty / unsubstituted placeholder | n/a | feature off, recipe default
applies (existing behavior) |

The computed list is exported as `RADIUS_EFFECTIVE_BUILD_PLATFORMS` and
injected as `--parameters platforms=<list>` 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 declare `platforms`,
and templates that do not render the placeholders, are unaffected.

**Mixed-arch is a first-class outcome, not an error.** A cluster with
both amd64 and arm64 nodes resolves to the fallback multi-arch list so
images stay portable; only single-arch clusters drop to a single
platform to skip emulation.

## Design notes

- Kept the contract small and explicit. Recognized modes are `detect`,
an explicit platform list, or empty/placeholder (off). An unrecognized
keyword fails safe to the fallback list with a warning, documented in
the workflow and script comments.
- Detection uses `kubectl get nodes -o
jsonpath=...nodeInfo.architecture`; a failed probe degrades to empty,
which the resolver treats as "undetermined" and maps to the fallback.
- No Radius runtime/CLI changes; this is entirely in the deploy workflow
templates, the shared composite action, and a helper script.

## Testing

- `make test-build-platforms` — new `compute-build-platforms_test.sh`:
mode/detection/override/fallback matrix (single-arch, mixed-arch,
unknown-arch, x86_64/aarch64 aliases, normalization, unrecognized mode),
plus assertions that both workflows carry the placeholders and wire the
inputs, and that the action declares the inputs and runs the helper.
- `make test-run-rad-commands-action` — extended
`deploy-parameters_test.sh`: `platforms` is injected only when declared,
skipped when no effective list was computed, and never overrides a
`RADIUS_DEPLOY_PARAMS`-supplied value.
- `shellcheck` clean on all shell; all three modified YAML files parse.

## Files

| File | Change |
| --- | --- |
|
`.github/extension/actions/run-rad-commands/compute-build-platforms.sh`
| New: resolves effective build platforms from mode/fallback/detected
arches |
|
`.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh`
| New: unit + wiring tests |
| `.github/extension/actions/run-rad-commands/action.yml` | New
`build-arch-mode` / `build-fallback-platforms` inputs; detection step
exporting `RADIUS_EFFECTIVE_BUILD_PLATFORMS` |
| `.github/extension/actions/run-rad-commands/deploy-parameters.sh` |
Inject `platforms` when the app declares it and a list was computed |
| `.github/extension/actions/run-rad-commands/deploy-parameters_test.sh`
| Coverage for the injection |
| `.github/extension/run-rad-commands-azure.yml` / `-aws.yml` |
Placeholders + pass the two inputs to `run-rad-commands` |
| `build/test.mk` | `test-build-platforms` target, added to `test` |

## Related

- radius-project/ai-extensions#300
- #12595

---------

Signed-off-by: Sylvain Niles <sylvainniles@microsoft.com>
(cherry picked from commit 6882b5f)
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
DariuszPorowski pushed a commit that referenced this pull request Aug 26, 2026
…ng) (#12721)

## Summary

These deploy workflow templates are rendered by substituting `{{...}}`
placeholders. For the architecture placeholders, the tooling injects a
GitHub Actions expression whose string-literal default is
**single-quoted** (GHA requires single quotes for string literals):

```
${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}
```

But these templates wrapped the placeholder scalar in **single** quotes:

```yaml
TARGET_CLUSTER_ARCH_MODE: '{{TARGET_CLUSTER_ARCH_MODE}}'
```

After substitution the quotes nest and YAML terminates the scalar early,
producing invalid YAML:

```yaml
TARGET_CLUSTER_ARCH_MODE: '${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}'
```

Every Azure and AWS deploy dispatch then failed with `HTTP 422: failed
to parse workflow ... error in your yaml syntax`.

## Fix

In both `.github/extension/run-rad-commands-azure.yml` and
`.github/extension/run-rad-commands-aws.yml`, the two architecture
placeholder scalars are changed from single to **double** quotes:

```yaml
TARGET_CLUSTER_ARCH_MODE: "{{TARGET_CLUSTER_ARCH_MODE}}"
TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS: "{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}"
```

The rendered result then becomes valid YAML — the outer double quotes
safely contain the inner single-quoted GHA string literal:

```yaml
TARGET_CLUSTER_ARCH_MODE: "${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}"
```

The other single-quoted scalars (`APP_FILE: '{{APP_FILE}}'`, `default:
'{{ENV}}'`, `environment: ${{ inputs.environment || '{{ENV}}' }}`)
inject plain values that contain no single quotes, so they remain valid
and are left unchanged.

## Verification

- Both provider files parse as valid YAML.
- Simulated the substitution (`{{TARGET_CLUSTER_ARCH_MODE}}` → `${{
vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}` and
`{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}` → `${{
vars.RADIUS_BUILD_PLATFORMS || 'linux/amd64,linux/arm64' }}`) in temp
copies of each file and confirmed they still parse as valid YAML, with
the GHA expression preserved intact as the string value.

## Related

- Regression introduced by the single-quoting in #12640

Signed-off-by: sk593 <shruthikumar@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit af04438)
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
DariuszPorowski added a commit that referenced this pull request Aug 26, 2026
….1 (#12831)

Patch release `v0.60.1` for channel `0.60`.

## Backported PRs

Listed in cherry-pick (topological) order. **Gap filler** = not
requested for the patch, but included because it touched the same region
of a shared file between `release/0.60` and a requested commit. Without
it the cherry-pick has no common merge base and conflicts.

| # | Commit | PR | Role |
| --- | --- | --- | --- |
| 1 | `6882b5fdf` | #12640 | Requested |
| 2 | `af0443809` | #12721 | Requested |
| 3 | `7c6de043d` | #12733 | **Gap filler** —
`.github/workflows/codeql.yml` |
| 4 | `ca7b5a3e7` | #12728 | Requested |
| 5 | `2eac7c1f1` | #12702 | Requested |
| 6 | `b4ebe2f28` | #12751 | Requested |
| 7 | `e30a2594d` | #12727 | Requested |
| 8 | `3c7dfecd6` | #12769 | **Gap filler** —
`.github/workflows/codeql.yml` |
| 9 | `787b6ca1a` | #12765 | **Gap filler** — `build/test.mk` |
| 10 | `f84184955` | #12775 | Requested |
| 11 | `415d5b9c2` | #12779 | Requested |
| 12 | `a1d976013` | #12782 | Requested |
| 13 | `14a21bd0d` | #12786 | **Gap filler** — `build/test.mk` |
| 14 | `a87146c77` | #12764 | **Gap filler** — `build/test.mk` |
| 15 | `073ca3cfa` | #12758 | Requested |
| 16 | `5e1f94c13` | #12785 | **Gap filler** —
`pkg/cli/cmd/install/kubernetes/kubernetes.go` |
| 17 | `1fd650af5` | #12742 | Requested |
| 18 | `19376c3f3` | #12829 | Requested |

Five of the six gap fillers are CI/test-only. #12785 is not: it adds a
Helm `control-plane-readiness` Job, a `pre-upgrade
wait-for-control-plane` command, UCP readiness probes, and a
NetworkPolicy change. It is required for #12829 to apply.

## Validation

Full chain dry-run cherry-picked onto `release/0.60` with zero
conflicts. `go build ./...` clean; `pkg/cli/cmd/install/kubernetes`,
`cmd/pre-upgrade/cmd`, `pkg/cli/...`, `pkg/graph/...`, and
`test/validation` all pass.

---------

Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
(cherry picked from commit e5938bf)
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
DariuszPorowski pushed a commit that referenced this pull request Aug 26, 2026
## Description

`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, which is roughly an
order of magnitude slower and prone to emulation crashes (see the
analysis on #12595). This PR lets the Azure and AWS
deploy workflows build only the platform(s) the target cluster actually
runs, while preserving multi-arch when it is genuinely needed.

This is the upstream half of the contract in
radius-project/ai-extensions#300.

## Contract

Two template placeholders on the Azure and AWS `run-rad-commands`
workflows, rendered by the extension:

| Placeholder | Extension default |
| --- | --- |
| `{{TARGET_CLUSTER_ARCH_MODE}}` | `${{ vars.RADIUS_BUILD_ARCH_MODE \|\|
'detect' }}` |
| `{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}` | `${{
vars.RADIUS_BUILD_PLATFORMS \|\| 'linux/amd64,linux/arm64' }}` |

## Behavior

Resolved by `compute-build-platforms.sh`, invoked by the shared
`run-rad-commands` action after the target kubeconfig is configured and
before the app is built/deployed:

| Mode | Cluster | Result |
| --- | --- | --- |
| `detect` | single-arch | build that one platform (no emulation) |
| `detect` | mixed / undetermined | fallback platform list |
| explicit list (contains `/`, e.g. `linux/amd64`) | n/a | honored
verbatim, no detection |
| empty / unsubstituted placeholder | n/a | feature off, recipe default
applies (existing behavior) |

The computed list is exported as `RADIUS_EFFECTIVE_BUILD_PLATFORMS` and
injected as `--parameters platforms=<list>` 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 declare `platforms`,
and templates that do not render the placeholders, are unaffected.

**Mixed-arch is a first-class outcome, not an error.** A cluster with
both amd64 and arm64 nodes resolves to the fallback multi-arch list so
images stay portable; only single-arch clusters drop to a single
platform to skip emulation.

## Design notes

- Kept the contract small and explicit. Recognized modes are `detect`,
an explicit platform list, or empty/placeholder (off). An unrecognized
keyword fails safe to the fallback list with a warning, documented in
the workflow and script comments.
- Detection uses `kubectl get nodes -o
jsonpath=...nodeInfo.architecture`; a failed probe degrades to empty,
which the resolver treats as "undetermined" and maps to the fallback.
- No Radius runtime/CLI changes; this is entirely in the deploy workflow
templates, the shared composite action, and a helper script.

## Testing

- `make test-build-platforms` — new `compute-build-platforms_test.sh`:
mode/detection/override/fallback matrix (single-arch, mixed-arch,
unknown-arch, x86_64/aarch64 aliases, normalization, unrecognized mode),
plus assertions that both workflows carry the placeholders and wire the
inputs, and that the action declares the inputs and runs the helper.
- `make test-run-rad-commands-action` — extended
`deploy-parameters_test.sh`: `platforms` is injected only when declared,
skipped when no effective list was computed, and never overrides a
`RADIUS_DEPLOY_PARAMS`-supplied value.
- `shellcheck` clean on all shell; all three modified YAML files parse.

## Files

| File | Change |
| --- | --- |
|
`.github/extension/actions/run-rad-commands/compute-build-platforms.sh`
| New: resolves effective build platforms from mode/fallback/detected
arches |
|
`.github/extension/actions/run-rad-commands/compute-build-platforms_test.sh`
| New: unit + wiring tests |
| `.github/extension/actions/run-rad-commands/action.yml` | New
`build-arch-mode` / `build-fallback-platforms` inputs; detection step
exporting `RADIUS_EFFECTIVE_BUILD_PLATFORMS` |
| `.github/extension/actions/run-rad-commands/deploy-parameters.sh` |
Inject `platforms` when the app declares it and a list was computed |
| `.github/extension/actions/run-rad-commands/deploy-parameters_test.sh`
| Coverage for the injection |
| `.github/extension/run-rad-commands-azure.yml` / `-aws.yml` |
Placeholders + pass the two inputs to `run-rad-commands` |
| `build/test.mk` | `test-build-platforms` target, added to `test` |

## Related

- radius-project/ai-extensions#300
- #12595

---------

Signed-off-by: Sylvain Niles <sylvainniles@microsoft.com>
(cherry picked from commit 6882b5f)
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
DariuszPorowski pushed a commit that referenced this pull request Aug 26, 2026
…ng) (#12721)

## Summary

These deploy workflow templates are rendered by substituting `{{...}}`
placeholders. For the architecture placeholders, the tooling injects a
GitHub Actions expression whose string-literal default is
**single-quoted** (GHA requires single quotes for string literals):

```
${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}
```

But these templates wrapped the placeholder scalar in **single** quotes:

```yaml
TARGET_CLUSTER_ARCH_MODE: '{{TARGET_CLUSTER_ARCH_MODE}}'
```

After substitution the quotes nest and YAML terminates the scalar early,
producing invalid YAML:

```yaml
TARGET_CLUSTER_ARCH_MODE: '${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}'
```

Every Azure and AWS deploy dispatch then failed with `HTTP 422: failed
to parse workflow ... error in your yaml syntax`.

## Fix

In both `.github/extension/run-rad-commands-azure.yml` and
`.github/extension/run-rad-commands-aws.yml`, the two architecture
placeholder scalars are changed from single to **double** quotes:

```yaml
TARGET_CLUSTER_ARCH_MODE: "{{TARGET_CLUSTER_ARCH_MODE}}"
TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS: "{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}"
```

The rendered result then becomes valid YAML — the outer double quotes
safely contain the inner single-quoted GHA string literal:

```yaml
TARGET_CLUSTER_ARCH_MODE: "${{ vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}"
```

The other single-quoted scalars (`APP_FILE: '{{APP_FILE}}'`, `default:
'{{ENV}}'`, `environment: ${{ inputs.environment || '{{ENV}}' }}`)
inject plain values that contain no single quotes, so they remain valid
and are left unchanged.

## Verification

- Both provider files parse as valid YAML.
- Simulated the substitution (`{{TARGET_CLUSTER_ARCH_MODE}}` → `${{
vars.RADIUS_BUILD_ARCH_MODE || 'detect' }}` and
`{{TARGET_CLUSTER_ARCH_FALLBACK_PLATFORMS}}` → `${{
vars.RADIUS_BUILD_PLATFORMS || 'linux/amd64,linux/arm64' }}`) in temp
copies of each file and confirmed they still parse as valid YAML, with
the GHA expression preserved intact as the string value.

## Related

- Regression introduced by the single-quoting in #12640

Signed-off-by: sk593 <shruthikumar@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
(cherry picked from commit af04438)
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
DariuszPorowski added a commit that referenced this pull request Aug 26, 2026
….1 (#12831)

Patch release `v0.60.1` for channel `0.60`.

## Backported PRs

Listed in cherry-pick (topological) order. **Gap filler** = not
requested for the patch, but included because it touched the same region
of a shared file between `release/0.60` and a requested commit. Without
it the cherry-pick has no common merge base and conflicts.

| # | Commit | PR | Role |
| --- | --- | --- | --- |
| 1 | `6882b5fdf` | #12640 | Requested |
| 2 | `af0443809` | #12721 | Requested |
| 3 | `7c6de043d` | #12733 | **Gap filler** —
`.github/workflows/codeql.yml` |
| 4 | `ca7b5a3e7` | #12728 | Requested |
| 5 | `2eac7c1f1` | #12702 | Requested |
| 6 | `b4ebe2f28` | #12751 | Requested |
| 7 | `e30a2594d` | #12727 | Requested |
| 8 | `3c7dfecd6` | #12769 | **Gap filler** —
`.github/workflows/codeql.yml` |
| 9 | `787b6ca1a` | #12765 | **Gap filler** — `build/test.mk` |
| 10 | `f84184955` | #12775 | Requested |
| 11 | `415d5b9c2` | #12779 | Requested |
| 12 | `a1d976013` | #12782 | Requested |
| 13 | `14a21bd0d` | #12786 | **Gap filler** — `build/test.mk` |
| 14 | `a87146c77` | #12764 | **Gap filler** — `build/test.mk` |
| 15 | `073ca3cfa` | #12758 | Requested |
| 16 | `5e1f94c13` | #12785 | **Gap filler** —
`pkg/cli/cmd/install/kubernetes/kubernetes.go` |
| 17 | `1fd650af5` | #12742 | Requested |
| 18 | `19376c3f3` | #12829 | Requested |

Five of the six gap fillers are CI/test-only. #12785 is not: it adds a
Helm `control-plane-readiness` Job, a `pre-upgrade
wait-for-control-plane` command, UCP readiness probes, and a
NetworkPolicy change. It is required for #12829 to apply.

## Validation

Full chain dry-run cherry-picked onto `release/0.60` with zero
conflicts. `go build ./...` clean; `pkg/cli/cmd/install/kubernetes`,
`cmd/pre-upgrade/cmd`, `pkg/cli/...`, `pkg/graph/...`, and
`test/validation` all pass.

---------

Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
(cherry picked from commit e5938bf)
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:standard Ongoing maintenance, minor improvements, documentation updates, and routine development work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants