From 7af5b5e7c9b8ff6dcbad6a0514446fd4b7299052 Mon Sep 17 00:00:00 2001 From: Steven Liekens Date: Mon, 9 Mar 2026 14:44:10 +0000 Subject: [PATCH 1/4] Fix dotnet latest resolution --- src/dotnet/NOTES.md | 2 +- src/dotnet/README.md | 10 ++--- src/dotnet/install.sh | 2 +- src/dotnet/scripts/dotnet-helpers.sh | 42 +++++++------------ test/dotnet/dotnet_helpers.sh | 41 +++++++----------- test/dotnet/install_dotnet_lts.sh | 2 +- .../dotnet/install_dotnet_specific_release.sh | 4 +- test/dotnet/install_dotnet_workloads.sh | 3 -- test/dotnet/scenarios.json | 2 +- 9 files changed, 42 insertions(+), 66 deletions(-) diff --git a/src/dotnet/NOTES.md b/src/dotnet/NOTES.md index ff52835b0..953372381 100644 --- a/src/dotnet/NOTES.md +++ b/src/dotnet/NOTES.md @@ -67,7 +67,7 @@ Installing .NET workloads. Multiple workloads can be specified as comma-separate ``` json "features": { "ghcr.io/devcontainers/features/dotnet:2": { - "workloads": "aspire, wasm-tools" + "workloads": "wasm-tools" } } ``` diff --git a/src/dotnet/README.md b/src/dotnet/README.md index fecaeb3f3..8244b151d 100644 --- a/src/dotnet/README.md +++ b/src/dotnet/README.md @@ -15,10 +15,10 @@ This Feature installs the latest .NET SDK, which includes the .NET CLI and the s | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| -| version | Select or enter a .NET SDK version. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version. | string | latest | -| additionalVersions | Enter additional .NET SDK versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version. | string | - | -| dotnetRuntimeVersions | Enter additional .NET runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version. | string | - | -| aspNetCoreRuntimeVersions | Enter additional ASP.NET Core runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version. | string | - | +| version | Select or enter a .NET SDK version. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | latest | +| additionalVersions | Enter additional .NET SDK versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | - | +| dotnetRuntimeVersions | Enter additional .NET runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | - | +| aspNetCoreRuntimeVersions | Enter additional ASP.NET Core runtime versions, separated by commas. Use 'latest' for the latest version, 'lts' for the latest LTS version, 'X.Y' or 'X.Y.Z' for a specific version, 'X.Y-preview' or 'X.Y-daily' for prereleases. | string | - | | workloads | Enter additional .NET SDK workloads, separated by commas. Use 'dotnet workload search' to learn what workloads are available to install. | string | - | ## Customizations @@ -95,7 +95,7 @@ Installing .NET workloads. Multiple workloads can be specified as comma-separate ``` json "features": { "ghcr.io/devcontainers/features/dotnet:2": { - "workloads": "aspire, wasm-tools" + "workloads": "wasm-tools" } } ``` diff --git a/src/dotnet/install.sh b/src/dotnet/install.sh index a6bde1ebd..d2b06cd0e 100644 --- a/src/dotnet/install.sh +++ b/src/dotnet/install.sh @@ -105,7 +105,7 @@ done # Install .NET versions and dependencies # icu-devtools includes dependencies for .NET -check_packages wget ca-certificates icu-devtools +check_packages wget ca-certificates icu-devtools jq for version in "${versions[@]}"; do read -r clean_version quality < <(parse_version_and_quality "$version") diff --git a/src/dotnet/scripts/dotnet-helpers.sh b/src/dotnet/scripts/dotnet-helpers.sh index 2ef8796eb..a33412f43 100644 --- a/src/dotnet/scripts/dotnet-helpers.sh +++ b/src/dotnet/scripts/dotnet-helpers.sh @@ -8,40 +8,30 @@ # Maintainer: The Dev Container spec maintainers DOTNET_SCRIPTS=$(dirname "${BASH_SOURCE[0]}") DOTNET_INSTALL_SCRIPT="$DOTNET_SCRIPTS/vendor/dotnet-install.sh" +DOTNET_RELEASES_INDEX_URL="https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json" -# Prints the latest dotnet version in the specified channel -# Usage: fetch_latest_version_in_channel [] -# Example: fetch_latest_version_in_channel "LTS" -# Example: fetch_latest_version_in_channel "6.0" "dotnet" -# Example: fetch_latest_version_in_channel "6.0" "aspnetcore" -fetch_latest_version_in_channel() { - local channel="$1" - local runtime="$2" - if [ "$runtime" = "dotnet" ]; then - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Runtime/$channel/latest.version" - elif [ "$runtime" = "aspnetcore" ]; then - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/$channel/latest.version" - else - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Sdk/$channel/latest.version" - fi -} - -# Prints the latest dotnet version +# Prints the latest active dotnet version from the releases index. # Usage: fetch_latest_version [] # Example: fetch_latest_version # Example: fetch_latest_version "dotnet" # Example: fetch_latest_version "aspnetcore" fetch_latest_version() { local runtime="$1" - local sts_version - local lts_version - sts_version=$(fetch_latest_version_in_channel "STS" "$runtime") - lts_version=$(fetch_latest_version_in_channel "LTS" "$runtime") - if [[ "$sts_version" > "$lts_version" ]]; then - echo "$sts_version" - else - echo "$lts_version" + local version_field="latest-sdk" + + if [ -n "$runtime" ]; then + version_field="latest-runtime" fi + + wget -qO- "$DOTNET_RELEASES_INDEX_URL" \ + | jq -er --arg version_field "$version_field" ' + .["releases-index"] + | map( + select(."support-phase" == "active") + | .[$version_field] + ) + | .[0] + ' } # Installs a version of the .NET SDK diff --git a/test/dotnet/dotnet_helpers.sh b/test/dotnet/dotnet_helpers.sh index 01e554f66..448c3f28b 100644 --- a/test/dotnet/dotnet_helpers.sh +++ b/test/dotnet/dotnet_helpers.sh @@ -1,38 +1,29 @@ #!/bin/bash -# Prints the latest dotnet version in the specified channel -# Usage: fetch_latest_version_in_channel [] -# Example: fetch_latest_version_in_channel "LTS" -# Example: fetch_latest_version_in_channel "6.0" "dotnet" -# Example: fetch_latest_version_in_channel "6.0" "aspnetcore" -fetch_latest_version_in_channel() { - local channel="$1" - local runtime="$2" - if [ "$runtime" = "dotnet" ]; then - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Runtime/$channel/latest.version" - elif [ "$runtime" = "aspnetcore" ]; then - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/aspnetcore/Runtime/$channel/latest.version" - else - wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Sdk/$channel/latest.version" - fi -} +DOTNET_RELEASES_INDEX_URL="https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json" -# Prints the latest dotnet version +# Prints the latest active dotnet version from the releases index. # Usage: fetch_latest_version [] # Example: fetch_latest_version # Example: fetch_latest_version "dotnet" # Example: fetch_latest_version "aspnetcore" fetch_latest_version() { local runtime="$1" - local sts_version - local lts_version - sts_version=$(fetch_latest_version_in_channel "STS" "$runtime") - lts_version=$(fetch_latest_version_in_channel "LTS" "$runtime") - if [[ "$sts_version" > "$lts_version" ]]; then - echo "$sts_version" - else - echo "$lts_version" + local version_field="latest-sdk" + + if [ -n "$runtime" ]; then + version_field="latest-runtime" fi + + wget -qO- "$DOTNET_RELEASES_INDEX_URL" \ + | jq -er --arg version_field "$version_field" ' + .["releases-index"] + | map( + select(."support-phase" == "active") + | .[$version_field] + ) + | .[0] + ' } # Asserts that the specified .NET SDK version is installed diff --git a/test/dotnet/install_dotnet_lts.sh b/test/dotnet/install_dotnet_lts.sh index da9175c15..a62c5937b 100644 --- a/test/dotnet/install_dotnet_lts.sh +++ b/test/dotnet/install_dotnet_lts.sh @@ -13,7 +13,7 @@ source dev-container-features-test-lib source dotnet_env.sh source dotnet_helpers.sh -expected=$(fetch_latest_version_in_channel "LTS") +expected=$(wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Sdk/LTS/latest.version") check "Latest LTS version installed" \ is_dotnet_sdk_version_installed "$expected" diff --git a/test/dotnet/install_dotnet_specific_release.sh b/test/dotnet/install_dotnet_specific_release.sh index 1ef587945..61260c8d1 100644 --- a/test/dotnet/install_dotnet_specific_release.sh +++ b/test/dotnet/install_dotnet_specific_release.sh @@ -13,10 +13,8 @@ source dev-container-features-test-lib source dotnet_env.sh source dotnet_helpers.sh -expected=$(fetch_latest_version_in_channel "10.0") - check ".NET Core SDK 10.0 installed" \ -is_dotnet_sdk_version_installed "$expected" +is_dotnet_sdk_version_installed "10.0" check "Build and run example project" \ dotnet run --project projects/net10.0 diff --git a/test/dotnet/install_dotnet_workloads.sh b/test/dotnet/install_dotnet_workloads.sh index 37c86a2d4..c4885664a 100644 --- a/test/dotnet/install_dotnet_workloads.sh +++ b/test/dotnet/install_dotnet_workloads.sh @@ -13,9 +13,6 @@ source dev-container-features-test-lib source dotnet_env.sh source dotnet_helpers.sh -check "Aspire is installed" \ -is_dotnet_workload_installed "aspire" - check "WASM tools are installed" \ is_dotnet_workload_installed "wasm-tools" diff --git a/test/dotnet/scenarios.json b/test/dotnet/scenarios.json index 62e2f1a46..52c35b2b0 100644 --- a/test/dotnet/scenarios.json +++ b/test/dotnet/scenarios.json @@ -89,7 +89,7 @@ "features": { "dotnet": { "version": "latest", - "workloads": "aspire, wasm-tools" + "workloads": "wasm-tools" } } } From 404af6f80be85320033686fcbef3232301ff69c4 Mon Sep 17 00:00:00 2001 From: Steven Liekens Date: Mon, 9 Mar 2026 14:57:02 +0000 Subject: [PATCH 2/4] Restore latest version check in test --- test/dotnet/install_dotnet_specific_release.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/dotnet/install_dotnet_specific_release.sh b/test/dotnet/install_dotnet_specific_release.sh index 61260c8d1..961cb47ab 100644 --- a/test/dotnet/install_dotnet_specific_release.sh +++ b/test/dotnet/install_dotnet_specific_release.sh @@ -13,8 +13,10 @@ source dev-container-features-test-lib source dotnet_env.sh source dotnet_helpers.sh +expected=$(wget -qO- "https://builds.dotnet.microsoft.com/dotnet/Sdk/10.0/latest.version") + check ".NET Core SDK 10.0 installed" \ -is_dotnet_sdk_version_installed "10.0" +is_dotnet_sdk_version_installed "$expected" check "Build and run example project" \ dotnet run --project projects/net10.0 From b628fe57c1e8fffb7c390c0801b4306fcb4c2c95 Mon Sep 17 00:00:00 2001 From: Steven Liekens Date: Mon, 9 Mar 2026 15:04:05 +0000 Subject: [PATCH 3/4] Avoid silently ignoring CDN errors --- src/dotnet/scripts/dotnet-helpers.sh | 5 ++++- test/dotnet/dotnet_helpers.sh | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dotnet/scripts/dotnet-helpers.sh b/src/dotnet/scripts/dotnet-helpers.sh index a33412f43..75c9f9199 100644 --- a/src/dotnet/scripts/dotnet-helpers.sh +++ b/src/dotnet/scripts/dotnet-helpers.sh @@ -18,12 +18,15 @@ DOTNET_RELEASES_INDEX_URL="https://builds.dotnet.microsoft.com/dotnet/release-me fetch_latest_version() { local runtime="$1" local version_field="latest-sdk" + local releases_index="" if [ -n "$runtime" ]; then version_field="latest-runtime" fi - wget -qO- "$DOTNET_RELEASES_INDEX_URL" \ + releases_index="$(wget -qO- "$DOTNET_RELEASES_INDEX_URL")" || return $? + + printf '%s\n' "$releases_index" \ | jq -er --arg version_field "$version_field" ' .["releases-index"] | map( diff --git a/test/dotnet/dotnet_helpers.sh b/test/dotnet/dotnet_helpers.sh index 448c3f28b..ff8d1ed7e 100644 --- a/test/dotnet/dotnet_helpers.sh +++ b/test/dotnet/dotnet_helpers.sh @@ -10,12 +10,15 @@ DOTNET_RELEASES_INDEX_URL="https://builds.dotnet.microsoft.com/dotnet/release-me fetch_latest_version() { local runtime="$1" local version_field="latest-sdk" + local releases_index="" if [ -n "$runtime" ]; then version_field="latest-runtime" fi - wget -qO- "$DOTNET_RELEASES_INDEX_URL" \ + releases_index="$(wget -qO- "$DOTNET_RELEASES_INDEX_URL")" || return $? + + printf '%s\n' "$releases_index" \ | jq -er --arg version_field "$version_field" ' .["releases-index"] | map( From ee9aa833e31dd280ddecf8900e9ed3b9d9759c68 Mon Sep 17 00:00:00 2001 From: Steven Liekens Date: Mon, 9 Mar 2026 17:26:32 +0000 Subject: [PATCH 4/4] Clarify dotnet latest target selection --- src/dotnet/scripts/dotnet-helpers.sh | 28 ++++++++++++++++++++++------ test/dotnet/dotnet_helpers.sh | 28 ++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/dotnet/scripts/dotnet-helpers.sh b/src/dotnet/scripts/dotnet-helpers.sh index 75c9f9199..d2dbc4534 100644 --- a/src/dotnet/scripts/dotnet-helpers.sh +++ b/src/dotnet/scripts/dotnet-helpers.sh @@ -11,18 +11,34 @@ DOTNET_INSTALL_SCRIPT="$DOTNET_SCRIPTS/vendor/dotnet-install.sh" DOTNET_RELEASES_INDEX_URL="https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json" # Prints the latest active dotnet version from the releases index. -# Usage: fetch_latest_version [] +# Usage: fetch_latest_version [] +# With no target, resolves the latest SDK version. +# With "sdk", resolves the latest SDK version explicitly. +# With "dotnet" or "aspnetcore", resolves the latest runtime version. +# Note: the upstream releases index only distinguishes SDK vs runtime for +# latest resolution, so "dotnet" and "aspnetcore" currently resolve to the +# same version. # Example: fetch_latest_version +# Example: fetch_latest_version "sdk" # Example: fetch_latest_version "dotnet" # Example: fetch_latest_version "aspnetcore" fetch_latest_version() { - local runtime="$1" - local version_field="latest-sdk" + local target="$1" + local version_field="" local releases_index="" - if [ -n "$runtime" ]; then - version_field="latest-runtime" - fi + case "$target" in + ""|sdk) + version_field="latest-sdk" + ;; + dotnet|aspnetcore) + version_field="latest-runtime" + ;; + *) + echo "Unsupported target '$target'. Expected 'sdk', 'dotnet', or 'aspnetcore'." >&2 + return 1 + ;; + esac releases_index="$(wget -qO- "$DOTNET_RELEASES_INDEX_URL")" || return $? diff --git a/test/dotnet/dotnet_helpers.sh b/test/dotnet/dotnet_helpers.sh index ff8d1ed7e..abb0cca26 100644 --- a/test/dotnet/dotnet_helpers.sh +++ b/test/dotnet/dotnet_helpers.sh @@ -3,18 +3,34 @@ DOTNET_RELEASES_INDEX_URL="https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json" # Prints the latest active dotnet version from the releases index. -# Usage: fetch_latest_version [] +# Usage: fetch_latest_version [] +# With no target, resolves the latest SDK version. +# With "sdk", resolves the latest SDK version explicitly. +# With "dotnet" or "aspnetcore", resolves the latest runtime version. +# Note: the upstream releases index only distinguishes SDK vs runtime for +# latest resolution, so "dotnet" and "aspnetcore" currently resolve to the +# same version. # Example: fetch_latest_version +# Example: fetch_latest_version "sdk" # Example: fetch_latest_version "dotnet" # Example: fetch_latest_version "aspnetcore" fetch_latest_version() { - local runtime="$1" - local version_field="latest-sdk" + local target="$1" + local version_field="" local releases_index="" - if [ -n "$runtime" ]; then - version_field="latest-runtime" - fi + case "$target" in + ""|sdk) + version_field="latest-sdk" + ;; + dotnet|aspnetcore) + version_field="latest-runtime" + ;; + *) + echo "Unsupported target '$target'. Expected 'sdk', 'dotnet', or 'aspnetcore'." >&2 + return 1 + ;; + esac releases_index="$(wget -qO- "$DOTNET_RELEASES_INDEX_URL")" || return $?