Skip to content

[release/17.0] Add initial WSLc publish/deploy support with explicit capability limits - #22012

Closed
Mitch Denny (mitchdenny) wants to merge 1 commit into
mitchdenny-backport-17-0-14-pr21845from
mitchdenny-backport-17-0-15-pr21009
Closed

Mitch Denny (mitchdenny) wants to merge 1 commit into
mitchdenny-backport-17-0-14-pr21845from
mitchdenny-backport-17-0-15-pr21009

Conversation

@mitchdenny

Copy link
Copy Markdown
Member

Description

Backport of #21009 to release/17.0 (cherry-picked from 64ba1eb with no modifications). Layer 15 of 55 in the release/17.0 backport stack; stacked on #22011.

Backport recommendation: Owner call — WSLc publish is a feature, but the author requested a backport (#21991).

Backport of #21009

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

…ts (#21009)

## Description

WSLc can run Aspire apps on Windows, but publishing currently has no
WSLc runtime implementation. This adds an initial, deliberately limited
publishing implementation so supported image operations use the selected
runtime and unsupported operations fail clearly instead of falling back
or working around WSLc limitations.

This PR targets `main` and builds on the runtime detection and doctor
support from #21004, now incorporated into `main`.

Supported on Windows: local Dockerfile builds without platform
selection, image tagging/removal/push, registry login, native JSON image
configuration inspection, and single-platform .NET SDK publishing to
WSLc's local image store. Auto-discovery retains Docker → Podman → WSLc
priority; explicit case-insensitive selection and custom keyed runtimes
remain supported.

Native SDK local publishing uses `LocalRegistry=Wslc` and requires an
SDK containing the WSLc support introduced in
[dotnet/sdk#55249](dotnet/sdk#55249). It was
verified with the repository-pinned SDK `11.0.100-rc.1.26425.128`; older
SDKs without this support report an unsupported registry rather than
falling back to another runtime. Aspire does not implement an
archive/load bridge.

Unsupported: Compose deployment/management, platform-selected Dockerfile
builds, Dockerfile archives/OCI format selection, manifest inspection,
multi-platform SDK publishing to WSLc's local image store, and
container-file layering (which requires a platform-selected Dockerfile
build). Multi-platform image loading is tracked by
[microsoft/WSL#41123](microsoft/WSL#41123).
These paths reject explicitly using existing exception/inspection-result
contracts. Daemon-free single- and multi-platform SDK archives remain
supported without WSLc health checks or commands; archive publishing
omits `LocalRegistry=Wslc`, preserving compatibility with SDKs that do
not recognize it. No alternative builder or runtime fallback is
introduced.

Dockerfile builds in Docker, Podman, and WSLc now use
`ProcessSpec.ArgumentList` instead of a manually quoted command line.
Shared eager argument helpers preserve quotes, spaces, backslashes,
empty values, and inherited environment values, retaining Podman's
existing secret behavior. Platform-specific process argument handling
remains owned by .NET, without invoking a shell.

### User-facing usage

On Windows, select WSLc with `ASPIRE_CONTAINER_RUNTIME=wslc`.
Single-platform .NET SDK publishing then uses WSLc's local store through
the SDK's native support.

Dockerfile publishing defaults to `linux/amd64`; explicitly opt into the
WSLc host platform only when appropriate:

```csharp
builder.AddDockerfile("program", "./program")
    .WithContainerBuildOptions(options => options.TargetPlatform = null);
```

For a .NET SDK archive without container-file layering:

```csharp
project.WithContainerBuildOptions(options =>
{
    options.Destination = ContainerImageDestination.Archive;
    options.OutputPath = "./images";
});
```

### Validation

- `.\restore.cmd /p:SkipNativeBuild=true` — passed during initial setup.
- `.\.dotnet\dotnet.exe test --project
tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj
--no-launch-profile /p:SkipNativeBuild=true -- --filter-class
'*.WslcContainerRuntimeTests' --filter-class
'*.ContainerRuntimeResolverTests' --filter-class
'*.ContainerRuntimeDetectorTests' --filter-class
'*.DockerContainerRuntimeTests' --filter-class
'*.ContainerRuntimeBaseTests' --filter-class
'*.DotnetProgramArchiveTests' --filter-class
'*.DotnetProgramPublishingTests' --filter-not-trait 'quarantined=true'
--filter-not-trait 'outerloop=true'` — passed, 151 tests after merging
`main`, before the argument-list correction.
- `.\.dotnet\dotnet.exe test --project
tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj
--no-launch-profile /p:SkipNativeBuild=true -- --filter-class
'*.ContainerRuntimeBaseTests' --filter-class
'*.DockerContainerRuntimeTests' --filter-class
'*.PodmanSaveArgumentsTests' --filter-class
'*.WslcContainerRuntimeTests' --filter-not-trait 'quarantined=true'
--filter-not-trait 'outerloop=true'` — passed, 95 tests after the
argument-list correction and eager helper changes. The runtime-constant
follow-up was rebuilt with `--filter-method
'*.BuildImageAsync_PreservesArgumentBoundaries'` (same project and
exclusions) — passed, 3 cases. The complete 95-test selection was rerun
with `--no-build` immediately before pushing — passed.
- `.\.dotnet\dotnet.exe test --project
tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj --no-build
--no-launch-profile -- --filter-method '*.BuildSecretsFormats*'
--filter-not-trait 'quarantined=true' --filter-not-trait
'outerloop=true'` — passed, 4 tests after the argument-list correction.
- `.\.dotnet\dotnet.exe test --project
tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj --no-build
--no-launch-profile -- --filter-class '*.DotnetProgramArchiveTests'
--filter-class '*.DotnetProgramPublishingTests' --filter-not-trait
'quarantined=true' --filter-not-trait 'outerloop=true'` — passed, 48
tests after the argument-list correction.
- `.\.dotnet\dotnet.exe test --project
tests\Aspire.Hosting.Tests\Aspire.Hosting.Tests.csproj --no-build
--no-launch-profile -- --filter-method '*.BuildImageAsync_Dotnet*'
--filter-method
'*.BuildImageAsync_ProjectBuildPassesResolvedContainerRuntimeAsLocalRegistry'
--filter-not-trait 'quarantined=true' --filter-not-trait
'outerloop=true'` — passed, 20 tests after the SDK correction and before
the `main` merge.
- `.\.dotnet\dotnet.exe publish '<probe-directory>\probe.csproj'
--configuration Release /t:PublishContainer
--property:LocalRegistry=Wslc
--property:ContainerRepository=aspire-wslc-sdk-probe-b35fa7a4
--property:ContainerImageTag=single
--property:RuntimeIdentifier=linux-x64
--property:ContainerRuntimeIdentifier=linux-x64
--property:ContainerBaseImage=mcr.microsoft.com/dotnet/runtime:10.0
--source https://packagefeedproxy.microsoft.io/nuget/v3/index.json` —
passed using a temporary minimal net10.0 console project and SDK
`11.0.100-rc.1.26425.128`. SDK reported successful publication via Wslc;
`wslc image inspect aspire-wslc-sdk-probe-b35fa7a4:single --format json`
confirmed linux/amd64. Probe image and project cleaned up; the path
above is a placeholder for that temporary directory.
- Installed WSLc 3.0.3.0: verified local Dockerfile build and native
`image inspect --format json`; inspected tag/remove/push/login/build
help and DCP's WSLc adapter. Direct `--output type=docker` and `--output
type=oci` probes both failed with the Docker-driver exporter limitation,
confirming the explicit archive boundary. Probe image/files cleaned up.
- Local E2E tests were not run; no E2E tests were added. Linux/macOS
execution and live registry push/login were not validated locally.
- Existing NU1902/NU1903 warnings for
`System.Security.Cryptography.Pkcs` 10.0.12 remain; no dependency
changes are included.

### Security considerations

The selected WSLc executable is trusted in the same way as
Docker/Podman. Image commands use the existing process runner; registry
passwords remain on stdin and environment build secrets follow the
existing runtime pattern. Native image inspection uses source-generated
JSON deserialization and preserves the image reference as a single
process argument. Dockerfile build invocations for all three runtimes
now preserve argument boundaries through `ProcessSpec.ArgumentList`. No
listener, elevated execution, archive extraction, or Aspire-managed
archive/load bridge is added. A separate security review has not been
performed.

Fixes # (issue)

## Checklist

- Is this feature complete?
  - [x] Yes. Ready to ship.
  - [ ] No. Follow-up changes expected.
- Are you including unit tests for the changes and scenario tests if
relevant?
  - [x] Yes
  - [ ] No
- Did you add public API?
  - [ ] Yes
    - If yes, did you have an API Review for it?
      - [ ] Yes
      - [ ] No
- Did you add `<remarks />` and `<code />` elements on your triple slash
comments?
      - [ ] Yes
      - [ ] No
  - [x] No
- Does the change make any security assumptions or guarantees?
  - [ ] Yes
    - If yes, have you done a threat model and had a security review?
      - [ ] Yes
      - [x] No
  - [x] No

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f85e4b06-371e-465a-8fd8-e635d14ec41f
Copilot-Session: b35fa7a4-1433-4b70-9840-5f5866230ff4
(cherry picked from commit 64ba1eb)
@github-actions

This comment has been minimized.

@mitchdenny

Copy link
Copy Markdown
Member Author

Approved for inclusion in release/17.0. Do not merge yet.

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 22012

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 22012"

@github-actions

This comment has been minimized.

@mitchdenny
Mitch Denny (mitchdenny) force-pushed the mitchdenny-backport-17-0-15-pr21009 branch 2 times, most recently from 4a63560 to e40acf1 Compare November 10, 2026 07:19
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@mitchdenny
Mitch Denny (mitchdenny) changed the base branch from mitchdenny-backport-17-0-14-pr21845 to mitchdenny-backport-17-0-12-pr21565 November 10, 2026 08:18
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@mitchdenny
Mitch Denny (mitchdenny) force-pushed the mitchdenny-backport-17-0-15-pr21009 branch from e40acf1 to 4a63560 Compare November 10, 2026 09:10
@mitchdenny
Mitch Denny (mitchdenny) changed the base branch from mitchdenny-backport-17-0-12-pr21565 to mitchdenny-backport-17-0-14-pr21845 November 10, 2026 10:33
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants