From b2bd42f53433e6f742d417c105112a61099e883b Mon Sep 17 00:00:00 2001 From: dotnet-docker-bot <60522487+dotnet-docker-bot@users.noreply.github.com> Date: Fri, 17 Oct 2025 09:34:10 -0700 Subject: [PATCH 01/75] [main] Update common Docker engineering infrastructure with latest (#6717) --- .../templates/steps/validate-branch.yml | 29 ++++++++++++++----- .../templates/variables/docker-images.yml | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/eng/common/templates/steps/validate-branch.yml b/eng/common/templates/steps/validate-branch.yml index 0bfcf9c9f2..0fb1a841d2 100644 --- a/eng/common/templates/steps/validate-branch.yml +++ b/eng/common/templates/steps/validate-branch.yml @@ -7,26 +7,39 @@ steps: - powershell: | if ("$env:ONEESPT_BUILDTYPE" -eq "Unofficial") { - echo "Build is from an unofficial pipeline, continuing..." + echo "Build is from an unofficial pipeline, continuing." exit 0 } - if ("$(officialBranches)".Split(',').Contains("$(sourceBranch)") ` - -and "$(officialRepoPrefixes)".Split(',').Contains("${{ parameters.publishConfig.publishAcr.repoPrefix }}")) + $isOfficialRepoPrefix = "$(officialRepoPrefixes)".Split(',').Contains("${{ parameters.publishConfig.publishAcr.repoPrefix }}") + if (-not $isOfficialRepoPrefix) { - echo "Conditions met for official build, continuing..." + echo "This build will not publish to an official repo prefix, continuing." + echo "Publish repo prefix: ${{ parameters.publishConfig.publishAcr.repoPrefix }}" + echo "Official repo prefixes: $(officialRepoPrefixes)" exit 0 } - if (-not "$(officialRepoPrefixes)".Split(',').Contains("${{ parameters.publishConfig.publishAcr.repoPrefix }}")) + $isOfficialBranch = "$(officialBranches)".Split(',').Contains("$(sourceBranch)") + if ($isOfficialBranch) { - echo "This build is a test build, continuing..." + echo "$(sourceBranch) is an official branch, continuing." + echo "Official branches: $(officialBranches)" exit 0 } - if ("${{ variables['overrideOfficialBranchValidation'] }}" -eq "true") + $hasOfficialBranchPrefix = $false + foreach ($prefix in "$(officialBranchPrefixes)".Split(',')) { + if ("$(sourceBranch)".StartsWith($prefix)) { + $hasOfficialBranchPrefix = $true + break + } + } + + if ($hasOfficialBranchPrefix) { - echo "Variable overrideOfficialBranchValidation is set to true, continuing..." + echo "$(sourceBranch) has an official branch prefix, continuing." + echo "Official branch prefixes: $(officialBranchPrefixes)" exit 0 } diff --git a/eng/common/templates/variables/docker-images.yml b/eng/common/templates/variables/docker-images.yml index cfaf8fa59a..172ed723f5 100644 --- a/eng/common/templates/variables/docker-images.yml +++ b/eng/common/templates/variables/docker-images.yml @@ -1,5 +1,5 @@ variables: - imageNames.imageBuilderName: mcr.microsoft.com/dotnet-buildtools/image-builder:2805555 + imageNames.imageBuilderName: mcr.microsoft.com/dotnet-buildtools/image-builder:2817852 imageNames.imageBuilder: $(imageNames.imageBuilderName) imageNames.imageBuilder.withrepo: imagebuilder-withrepo:$(Build.BuildId)-$(System.JobId) imageNames.testRunner: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux3.0-docker-testrunner From b9808226d752eab0cfa07d3c9e9247b51be29104 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Tue, 21 Oct 2025 11:37:28 -0700 Subject: [PATCH 02/75] [release/2025-11B] Fix sync-internal-release pipeline string replacement (#6739) --- eng/pipelines/pipelines/sync-internal-release.yml | 11 +++++++++-- eng/pipelines/sync-internal-release-official.yml | 8 +++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/eng/pipelines/pipelines/sync-internal-release.yml b/eng/pipelines/pipelines/sync-internal-release.yml index e2fba9a7e5..b4e2b3975f 100644 --- a/eng/pipelines/pipelines/sync-internal-release.yml +++ b/eng/pipelines/pipelines/sync-internal-release.yml @@ -46,14 +46,21 @@ extends: scriptType: pscore scriptLocation: inlineScript addSpnToEnvironment: true + # $(Build.SourceBranch) always has a refs/heads/ prefix when this pipeline is triggered from a branch. + # The update-dependencies tool doesn't expect the prefix, so we need to remove it. inlineScript: >- + $sourceBranch = "${{ parameters.sourceBranch }}"; + $sourceBranch = $sourceBranch -replace "refs/heads/", ""; + $targetBranch = "${{ parameters.targetBranch }}"; + $targetBranch = $targetBranch -replace "refs/heads/", ""; + dotnet run --configuration Release --project ./eng/update-dependencies/update-dependencies.csproj -- sync-internal-release --azdo-organization "$env:SYSTEM_COLLECTIONURI" --azdo-project "$env:SYSTEM_TEAMPROJECT" --azdo-repo "$env:BUILD_REPOSITORY_NAME" - --source-branch "${{ parameters.sourceBranch }}" - --target-branch "${{ parameters.targetBranch }}" + --source-branch "$sourceBranch" + --target-branch "$targetBranch" --pr-branch-prefix "pr" --user "$env:DOTNETDOCKERBOT_USERNAME" --email "$env:DOTNETDOCKERBOT_EMAIL" diff --git a/eng/pipelines/sync-internal-release-official.yml b/eng/pipelines/sync-internal-release-official.yml index 56151c1108..b47586d218 100644 --- a/eng/pipelines/sync-internal-release-official.yml +++ b/eng/pipelines/sync-internal-release-official.yml @@ -12,9 +12,11 @@ variables: extends: template: /eng/pipelines/pipelines/sync-internal-release.yml@self parameters: - # Source branch will always be a release/* branch due to the pipeline trigger above - sourceBranch: "$(Build.SourceBranchName)" + # Source branch will always be a release/* branch due to the pipeline trigger above. + # Don't use Build.SourceBranchName because it strips all branch prefixes, which + # would turn 'release/foo' into just 'foo'. + sourceBranch: "$(Build.SourceBranch)" # Target branch should be the internal version of the release branch - targetBranch: "internal/$(Build.SourceBranchName)" + targetBranch: "internal/$(Build.SourceBranch)" # Service connection used to push new branches and submit pull requests. serviceConnection: "$(updateDepsInt.serviceConnectionName)" From c6daeb6ff37f3fa8f0169046e609e8b872fac459 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Wed, 22 Oct 2025 14:18:21 -0700 Subject: [PATCH 03/75] [release/2025-11B] Automatically run release staging pipeline when internal/release/* branches are updated (#6745) Original PR: https://github.com/dotnet/dotnet-docker/pull/6743 Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> --- eng/pipelines/release-staging-official.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/release-staging-official.yml b/eng/pipelines/release-staging-official.yml index d421dc0687..fc9e50b490 100644 --- a/eng/pipelines/release-staging-official.yml +++ b/eng/pipelines/release-staging-official.yml @@ -3,7 +3,11 @@ # # The images can later be published by running the release-promotion pipeline. -trigger: none +trigger: + batch: true + branches: + include: + - internal/release/* pr: none parameters: @@ -22,7 +26,7 @@ parameters: parameter to true forces all images to be built (except those excluded by path arguments). type: boolean - default: false + default: true variables: - template: /eng/pipelines/variables/core.yml@self From 0d0ab4da4ec3aeac966277dcac5b4b121474dde3 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Wed, 22 Oct 2025 14:25:55 -0700 Subject: [PATCH 04/75] [release/2025-11B] Update common Docker engineering infrastructure with latest (#6744) --- .../templates/steps/validate-branch.yml | 29 ++++++++++++++----- .../templates/variables/docker-images.yml | 2 +- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/eng/common/templates/steps/validate-branch.yml b/eng/common/templates/steps/validate-branch.yml index 0bfcf9c9f2..0fb1a841d2 100644 --- a/eng/common/templates/steps/validate-branch.yml +++ b/eng/common/templates/steps/validate-branch.yml @@ -7,26 +7,39 @@ steps: - powershell: | if ("$env:ONEESPT_BUILDTYPE" -eq "Unofficial") { - echo "Build is from an unofficial pipeline, continuing..." + echo "Build is from an unofficial pipeline, continuing." exit 0 } - if ("$(officialBranches)".Split(',').Contains("$(sourceBranch)") ` - -and "$(officialRepoPrefixes)".Split(',').Contains("${{ parameters.publishConfig.publishAcr.repoPrefix }}")) + $isOfficialRepoPrefix = "$(officialRepoPrefixes)".Split(',').Contains("${{ parameters.publishConfig.publishAcr.repoPrefix }}") + if (-not $isOfficialRepoPrefix) { - echo "Conditions met for official build, continuing..." + echo "This build will not publish to an official repo prefix, continuing." + echo "Publish repo prefix: ${{ parameters.publishConfig.publishAcr.repoPrefix }}" + echo "Official repo prefixes: $(officialRepoPrefixes)" exit 0 } - if (-not "$(officialRepoPrefixes)".Split(',').Contains("${{ parameters.publishConfig.publishAcr.repoPrefix }}")) + $isOfficialBranch = "$(officialBranches)".Split(',').Contains("$(sourceBranch)") + if ($isOfficialBranch) { - echo "This build is a test build, continuing..." + echo "$(sourceBranch) is an official branch, continuing." + echo "Official branches: $(officialBranches)" exit 0 } - if ("${{ variables['overrideOfficialBranchValidation'] }}" -eq "true") + $hasOfficialBranchPrefix = $false + foreach ($prefix in "$(officialBranchPrefixes)".Split(',')) { + if ("$(sourceBranch)".StartsWith($prefix)) { + $hasOfficialBranchPrefix = $true + break + } + } + + if ($hasOfficialBranchPrefix) { - echo "Variable overrideOfficialBranchValidation is set to true, continuing..." + echo "$(sourceBranch) has an official branch prefix, continuing." + echo "Official branch prefixes: $(officialBranchPrefixes)" exit 0 } diff --git a/eng/common/templates/variables/docker-images.yml b/eng/common/templates/variables/docker-images.yml index cfaf8fa59a..172ed723f5 100644 --- a/eng/common/templates/variables/docker-images.yml +++ b/eng/common/templates/variables/docker-images.yml @@ -1,5 +1,5 @@ variables: - imageNames.imageBuilderName: mcr.microsoft.com/dotnet-buildtools/image-builder:2805555 + imageNames.imageBuilderName: mcr.microsoft.com/dotnet-buildtools/image-builder:2817852 imageNames.imageBuilder: $(imageNames.imageBuilderName) imageNames.imageBuilder.withrepo: imagebuilder-withrepo:$(Build.BuildId)-$(System.JobId) imageNames.testRunner: mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux3.0-docker-testrunner From 98a65a0bbc843358b9256e9dabd0628557246f4d Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Thu, 16 Oct 2025 11:45:11 -0700 Subject: [PATCH 05/75] Add Aspire Dashboard support to update-dependencies tooling (#6726) --- Microsoft.DotNet.Docker.slnx | 1 + README.aspire-dashboard.md | 4 +- .../aspire-dashboard/Dockerfile.linux | 10 +- .../aspire-dashboard-tags.yml | 2 +- eng/shared/.editorconfig | 5 + eng/shared/DotNetVersion.cs | 94 +++++++++ .../Microsoft.DotNet.Docker.Shared.csproj | 18 ++ eng/shared/README.md | 5 + .../AspireBuildUpdaterService.cs | 111 +++++++++++ eng/update-dependencies/BuildExtensions.cs | 27 +++ eng/update-dependencies/BuildRepo.cs | 15 ++ .../CreatePullRequestOptions.cs | 3 + eng/update-dependencies/DotNetVersion.cs | 43 ----- eng/update-dependencies/FromBuildCommand.cs | 26 ++- eng/update-dependencies/FromChannelCommand.cs | 22 ++- .../FromComponentCommand.cs | 10 +- .../FromStagingPipelineCommand.cs | 13 +- .../IBuildUpdaterService.cs | 11 ++ eng/update-dependencies/IManifestVariables.cs | 16 ++ eng/update-dependencies/ManifestVariables.cs | 2 +- eng/update-dependencies/NuGetConfigUpdater.cs | 1 + eng/update-dependencies/Program.cs | 9 +- eng/update-dependencies/SpecificCommand.cs | 20 +- .../SpecificCommandOptions.cs | 25 ++- eng/update-dependencies/VariableUpdateInfo.cs | 13 ++ eng/update-dependencies/VariableUpdater.cs | 31 +++ ...erService.cs => VmrBuildUpdaterService.cs} | 43 +---- .../update-dependencies.csproj | 4 + manifest.json | 24 +-- manifest.versions.json | 18 +- .../amd64/Dockerfile | 0 .../arm64v8/Dockerfile | 0 ...e-dashboard-amd64-Dockerfile.approved.txt} | 0 ...dashboard-arm64v8-Dockerfile.approved.txt} | 0 .../DockerfileInfo.cs | 59 ++++-- .../ManifestHelper.cs | 2 +- .../Microsoft.DotNet.Docker.Tests.csproj | 4 + .../StaticTagTests.cs | 179 ++++++++---------- 38 files changed, 608 insertions(+), 262 deletions(-) create mode 100644 eng/shared/.editorconfig create mode 100644 eng/shared/DotNetVersion.cs create mode 100644 eng/shared/Microsoft.DotNet.Docker.Shared.csproj create mode 100644 eng/shared/README.md create mode 100644 eng/update-dependencies/AspireBuildUpdaterService.cs create mode 100644 eng/update-dependencies/BuildExtensions.cs create mode 100644 eng/update-dependencies/BuildRepo.cs delete mode 100644 eng/update-dependencies/DotNetVersion.cs create mode 100644 eng/update-dependencies/IBuildUpdaterService.cs create mode 100644 eng/update-dependencies/IManifestVariables.cs create mode 100644 eng/update-dependencies/VariableUpdateInfo.cs create mode 100644 eng/update-dependencies/VariableUpdater.cs rename eng/update-dependencies/{BuildUpdaterService.cs => VmrBuildUpdaterService.cs} (65%) rename src/aspire-dashboard/{9.5/azurelinux-distroless => }/amd64/Dockerfile (100%) rename src/aspire-dashboard/{9.5/azurelinux-distroless => }/arm64v8/Dockerfile (100%) rename tests/Microsoft.DotNet.Docker.Tests/Baselines/GeneratedArtifactTests/VerifyInternalDockerfilesOutput/{aspire-dashboard-9.5-azurelinux-distroless-amd64-Dockerfile.approved.txt => aspire-dashboard-amd64-Dockerfile.approved.txt} (100%) rename tests/Microsoft.DotNet.Docker.Tests/Baselines/GeneratedArtifactTests/VerifyInternalDockerfilesOutput/{aspire-dashboard-9.5-azurelinux-distroless-arm64v8-Dockerfile.approved.txt => aspire-dashboard-arm64v8-Dockerfile.approved.txt} (100%) diff --git a/Microsoft.DotNet.Docker.slnx b/Microsoft.DotNet.Docker.slnx index 4d5227d519..f1a4b931c9 100644 --- a/Microsoft.DotNet.Docker.slnx +++ b/Microsoft.DotNet.Docker.slnx @@ -5,6 +5,7 @@ + diff --git a/README.aspire-dashboard.md b/README.aspire-dashboard.md index 35b3c66671..7fa5c44858 100644 --- a/README.aspire-dashboard.md +++ b/README.aspire-dashboard.md @@ -113,13 +113,13 @@ Limits are per-resource. For example, a `MaxLogCount` value of 10,000 configures Tags | Dockerfile | OS Version -----------| -------------| ------------- -9.5.0, 9.5, 9, latest | [Dockerfile](src/aspire-dashboard/9.5/azurelinux-distroless/amd64/Dockerfile) | Azure Linux 3.0 +9.5.0, 9.5, 9, latest | [Dockerfile](src/aspire-dashboard/amd64/Dockerfile) | Azure Linux 3.0 ### Linux arm64 Tags Tags | Dockerfile | OS Version -----------| -------------| ------------- -9.5.0, 9.5, 9, latest | [Dockerfile](src/aspire-dashboard/9.5/azurelinux-distroless/arm64v8/Dockerfile) | Azure Linux 3.0 +9.5.0, 9.5, 9, latest | [Dockerfile](src/aspire-dashboard/arm64v8/Dockerfile) | Azure Linux 3.0 *Tags not listed in the table above are not supported. See the [Supported Tags Policy](https://github.com/dotnet/dotnet-docker/blob/main/documentation/supported-tags.md). See the [full list of tags](https://mcr.microsoft.com/v2/dotnet/aspire-dashboard/tags/list) for all supported and unsupported tags.* diff --git a/eng/dockerfile-templates/aspire-dashboard/Dockerfile.linux b/eng/dockerfile-templates/aspire-dashboard/Dockerfile.linux index cece030c11..976173716d 100644 --- a/eng/dockerfile-templates/aspire-dashboard/Dockerfile.linux +++ b/eng/dockerfile-templates/aspire-dashboard/Dockerfile.linux @@ -13,19 +13,19 @@ OS_VERSION_NUMBER), cat(ARCH_VERSIONED, "/buildpack-deps:", osVersionBase, "-curl")) ^ - set baseUrl to VARIABLES[cat("aspire-dashboard|", aspireMajorMinor, "|base-url|", VARIABLES["branch"])] ^ + set baseUrl to VARIABLES[cat("aspire-dashboard|base-url|", VARIABLES["branch"])] ^ set isInternal to find(baseUrl, "artifacts.visualstudio.com") >= 0 ^ - set buildVersion to VARIABLES[cat("aspire-dashboard|", aspireMajorMinor, "|build-version")] ^ + set buildVersion to VARIABLES["aspire-dashboard|build-version"] ^ set aspireVersionVariable to when(find(buildVersion, '-rtm') >= 0 || find(buildVersion, '-servicing') >= 0, "product-version", "build-version") ^ - set aspireVersion to VARIABLES[cat("aspire-dashboard|", aspireMajorMinor, "|", aspireVersionVariable)] ^ + set aspireVersion to VARIABLES[cat("aspire-dashboard|", aspireVersionVariable)] ^ set versionFolder to when(buildVersion != aspireVersion, buildVersion, '$dotnet_aspire_version') ^ - set aspireBaseUrl to cat(VARIABLES[cat("aspire-dashboard|", aspireMajorMinor, "|base-url|", VARIABLES["branch"])], "/aspire/", versionFolder, "/") ^ + set aspireBaseUrl to cat(VARIABLES[cat("aspire-dashboard|base-url|", VARIABLES["branch"])], "/aspire/", versionFolder, "/") ^ set downloadUrl to cat(aspireBaseUrl, "aspire-dashboard-linux-", ARCH_SHORT, ".zip") ^ set outFile to "aspire_dashboard.zip" ^ set appDir to "/app" ^ - set sha to VARIABLES[join(["aspire-dashboard", aspireMajorMinor, "linux", ARCH_SHORT, "sha"], "|")] + set sha to VARIABLES[join(["aspire-dashboard", "linux", ARCH_SHORT, "sha"], "|")] }}ARG REPO=mcr.microsoft.com/dotnet/aspnet diff --git a/eng/mcr-tags-metadata-templates/aspire-dashboard-tags.yml b/eng/mcr-tags-metadata-templates/aspire-dashboard-tags.yml index 53c029126b..24158fced7 100644 --- a/eng/mcr-tags-metadata-templates/aspire-dashboard-tags.yml +++ b/eng/mcr-tags-metadata-templates/aspire-dashboard-tags.yml @@ -1,2 +1,2 @@ $(McrTagsYmlRepo:aspire-dashboard) -$(McrTagsYmlTagGroup:9.5) +$(McrTagsYmlTagGroup:latest) diff --git a/eng/shared/.editorconfig b/eng/shared/.editorconfig new file mode 100644 index 0000000000..3d267e16c2 --- /dev/null +++ b/eng/shared/.editorconfig @@ -0,0 +1,5 @@ +[*.cs] +# CS1591: Missing XML comment for publicly visible type or member +# https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs1591 +# Since this is a shared project, public members should be documented. +dotnet_diagnostic.CS1591.severity = suggestion diff --git a/eng/shared/DotNetVersion.cs b/eng/shared/DotNetVersion.cs new file mode 100644 index 0000000000..5115c6596b --- /dev/null +++ b/eng/shared/DotNetVersion.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text.RegularExpressions; +using NuGet.Versioning; + +namespace Microsoft.DotNet.Docker.Shared; + +/// +/// Represents the version of a .NET build artifact. +/// +/// +/// Additional notes about .NET versions: +/// - : For runtime versions, this is number typically starts at +/// 0 and counts up with new releases. For SDK versions, this number starts at 100 and can be as +/// high as 400+. +/// - : For stable release .NET versions, this is typically +/// empty. For preview .NET versions, it is usually a suffix like "preview.6.12345.678-shipping". +/// For internal builds of stable versions, it can be a suffix like "servicing.12345.6". +/// +public sealed partial class DotNetVersion(SemanticVersion version) : SemanticVersion(version) +{ + /// + /// Parse a string into a . This is not a + /// "strict" parse. Major-only versions are allowed. Unspecified minor and + /// patch versions default to 0. + /// + public static new DotNetVersion Parse(string versionString) + { + // SemanticVersion only handles full, strict semantic versions. + // If we can parse it as a full semantic version, use that. It resolves + // full build versions correctly including prerelease suffixes (e.g. '-preview.7') + if (SemanticVersion.TryParse(versionString, out var fullSemanticVersion)) + { + return new DotNetVersion(fullSemanticVersion); + } + + // We also need to allow major-only and major.minor versions, so fall + // back to simple parsing when that happens. + // A valid major.minor.patch version would have been handled above so + // we don't need to account for that in our parsing. + // Don't use System.Version.Parse here, because it defaults unmatched + // minor/patch versions to -1, whereas we want them to default to 0. + var matchGroups = MajorMinorVersionRegex.Match(versionString).Groups; + int major = int.Parse(matchGroups["major"].Value); + int minor = int.TryParse(matchGroups["minor"].Value.TrimStart('.'), out int minorValue) ? minorValue : 0; + return new DotNetVersion(new SemanticVersion(major, minor, 0)); + } + + /// + /// Implicitly converts a string to a . + /// + public static implicit operator DotNetVersion(string versionString) + { + SemanticVersion version = Parse(versionString); + return new(version); + } + + /// + /// Formats the version to a string with the specified number of parts. + /// + /// + /// Number of parts to include. If greater than 3, the full version is + /// returned, whether or not there are any additional parts. + /// + public string ToString(int parts) => parts switch + { + < 1 => throw new ArgumentOutOfRangeException(nameof(parts), "Cannot format less than 1 version part"), + 1 => $"{Major}", + 2 => $"{Major}.{Minor}", + 3 => $"{Major}.{Minor}.{Patch}", + > 3 => ToString(), + }; + + /// + /// Whether the .NET version is a public preview version. + /// + public bool IsPublicPreview => + // Assume all "preview" versions are public, non-security releases. + // Assume that all "rc" and "servicing" versions are internal security releases. + Release.StartsWith("preview", StringComparison.OrdinalIgnoreCase); + + /// + /// Determines if the version is considered a GA version. + /// + /// + /// RTM versions are also accepted as GA versions. + /// + public bool IsGA => ReleaseLabels.Contains("rtm") || !IsPrerelease; + + [GeneratedRegex(@"^(?\d+)(?\.\d+)?")] + private static partial Regex MajorMinorVersionRegex { get; } +} diff --git a/eng/shared/Microsoft.DotNet.Docker.Shared.csproj b/eng/shared/Microsoft.DotNet.Docker.Shared.csproj new file mode 100644 index 0000000000..c045e9b31a --- /dev/null +++ b/eng/shared/Microsoft.DotNet.Docker.Shared.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + true + Microsoft.DotNet.Docker.Shared + + + true + + + + + + + diff --git a/eng/shared/README.md b/eng/shared/README.md new file mode 100644 index 0000000000..e8597d1960 --- /dev/null +++ b/eng/shared/README.md @@ -0,0 +1,5 @@ +# Microsoft.DotNet.Docker.Shared + +This class library contains code that's shared between the +[`update-dependencies` tool](../update-dependencies/README.md) and the +[.NET Docker test project](../../tests/Microsoft.DotNet.Docker.Tests/). diff --git a/eng/update-dependencies/AspireBuildUpdaterService.cs b/eng/update-dependencies/AspireBuildUpdaterService.cs new file mode 100644 index 0000000000..bd7d178ef1 --- /dev/null +++ b/eng/update-dependencies/AspireBuildUpdaterService.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.ProductConstructionService.Client.Models; +using Microsoft.Extensions.Logging; + +namespace Dotnet.Docker; + +internal class AspireBuildUpdaterService( + ILogger logger, + HttpClient httpClient, + Func manifestVariablesFactory +) : IBuildUpdaterService +{ + private readonly ILogger _logger = logger; + private readonly HttpClient _httpClient = httpClient; + private readonly Func _createManifestVariables = manifestVariablesFactory; + + /// + /// Given a BAR (https://aka.ms/bar) build of Aspire, updates the Aspire Dashboard build version. + /// + public async Task UpdateFrom(Build build, CreatePullRequestOptions pullRequestOptions) + { + if (build.GetBuildRepo() != BuildRepo.Aspire) + { + throw new InvalidOperationException( + $"AspireBuildUpdaterService cannot process builds from repo: {build.GitHubRepository}"); + } + + // Read manifest.versions.json early so that we can fail fast in case we have the wrong file path. + var manifestVariables = _createManifestVariables(pullRequestOptions.GetManifestVersionsFilePath()); + var branch = manifestVariables.GetValue("branch"); + var dashboardBaseUrl = manifestVariables.GetValue($"aspire-dashboard|base-url|{branch}"); + + var dashboardAssets = build.Assets.Where(asset => + asset.Name.Contains("aspire-dashboard-linux-x64") + || asset.Name.Contains("aspire-dashboard-linux-arm64")); + + if (!dashboardAssets.Any()) + { + throw new InvalidOperationException($"Could not find aspire-dashboard-linux-* assets in build {build.Id}"); + } + + _logger.LogInformation("Found Aspire build version: {version}", dashboardAssets.First().Version); + + IEnumerable dashboardChecksums = await dashboardAssets + .Select(asset => GetDashboardUrl(dashboardBaseUrl, asset)) + .ToAsyncEnumerable() + .SelectAwait(async url => await GetChecksumAsync(url)) + .ToListAsync(); + + var dashboardChecksumInfos = dashboardAssets.Zip(dashboardChecksums); + + var manifestVersionsPath = pullRequestOptions.GetManifestVersionsFilePath(); + + var version = dashboardAssets.First().Version; + var majorMinorVersion = VersionHelper.ResolveMajorMinorVersion(version); + + List variableUpdates = + [ + new VariableUpdateInfo("aspire-dashboard|build-version", version), + new VariableUpdateInfo("aspire-dashboard|product-version", VersionHelper.ResolveProductVersion(version)), + new VariableUpdateInfo("aspire-dashboard|fixed-tag", VersionHelper.ResolveProductVersion(version)), + new VariableUpdateInfo("aspire-dashboard|minor-tag", majorMinorVersion.ToString(2)), + new VariableUpdateInfo("aspire-dashboard|major-tag", majorMinorVersion.Major.ToString()), + ]; + + variableUpdates.AddRange(dashboardChecksumInfos + // Filter out null checksums (indicates the checksum was not found above) + .Where(info => info.Second is not null) + .Select(info => + { + var (asset, checksum) = info; + var arch = asset.Name.Contains("arm64") ? "arm64" : "x64"; + // Null-forgiving operator is OK since we filtered out null checksums above. + return new VariableUpdateInfo($"aspire-dashboard|linux|{arch}|sha", checksum!); + })); + + var dependencyUpdaters = variableUpdates + .Select(updateInfo => new VariableUpdater(manifestVersionsPath, updateInfo)); + + var updateDependencies = new SpecificCommand(); + updateDependencies.CustomUpdateInfos.AddRange(variableUpdates); + updateDependencies.CustomUpdaters.AddRange(dependencyUpdaters); + + // Don't pass in any through options, since we calculated all of the variables + // and their new versions to update above. Everything is handled through CustomUpdateInfos. + // Using the SpecificCommand here just allows us to easily create automated pull requests. + var updateDependenciesOptions = SpecificCommandOptions.FromPullRequestOptions(pullRequestOptions); + return await updateDependencies.ExecuteAsync(updateDependenciesOptions); + } + + /// + /// Gets the full download URL to an Aspire Dashboard asset given its base + /// URL and the BAR asset information. + /// + private string GetDashboardUrl(string baseUrl, Asset dashboardAsset) => $"{baseUrl}/{dashboardAsset.Name}"; + + /// + /// Manually compute the checksum of an Aspire Dashboard asset by downloading it from + /// and hashing the contents. + /// + /// + /// Remove once https://github.com/dotnet/dotnet-docker/issues/6568 is completed. + /// + /// + /// Null if the checksum could not be computed for any reason. An error will + /// be logged to the console if this happens. + /// + private Task GetChecksumAsync(string url) => ChecksumHelper.ComputeChecksumShaAsync(_httpClient, url); +} diff --git a/eng/update-dependencies/BuildExtensions.cs b/eng/update-dependencies/BuildExtensions.cs new file mode 100644 index 0000000000..d1a1aa9de0 --- /dev/null +++ b/eng/update-dependencies/BuildExtensions.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.DotNet.ProductConstructionService.Client.Models; + +namespace Dotnet.Docker; + +/// +/// Extensions for .NET build asset registry s. +/// +internal static class BuildExtensions +{ + /// + /// Given a , maps its source repository (either GitHub + /// or Azure Devops) to a supported enum value. + /// + public static BuildRepo GetBuildRepo(this Build build) + { + string repo = build.GitHubRepository ?? build.AzureDevOpsRepository; + return repo switch + { + "https://github.com/dotnet/dotnet" or "https://dev.azure.com/dnceng/internal/_git/dotnet-dotnet" => BuildRepo.Vmr, + "https://github.com/dotnet/aspire" or "https://dev.azure.com/dnceng/internal/_git/dotnet-aspire" => BuildRepo.Aspire, + _ => throw new InvalidOperationException($"Build {build.Id} was from unsupported repository '{repo}'"), + }; + } +} diff --git a/eng/update-dependencies/BuildRepo.cs b/eng/update-dependencies/BuildRepo.cs new file mode 100644 index 0000000000..cd6841c187 --- /dev/null +++ b/eng/update-dependencies/BuildRepo.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Dotnet.Docker; + +/// +/// Represents supported source repositories for builds from the .NET build +/// asset registry. +/// +internal enum BuildRepo +{ + Vmr, + Aspire, + // When adding new repos, also update "BuildExtensions.GetBuildRepo()" +} diff --git a/eng/update-dependencies/CreatePullRequestOptions.cs b/eng/update-dependencies/CreatePullRequestOptions.cs index 36f0bdc737..d343a5536b 100644 --- a/eng/update-dependencies/CreatePullRequestOptions.cs +++ b/eng/update-dependencies/CreatePullRequestOptions.cs @@ -29,6 +29,9 @@ public string AzdoOrganization public string SourceBranch { get; init; } = ""; public string TargetBranch { get; init; } = "nightly"; + // If new properties or options are added, they may need to be added to + // SpecificCommandOptions.FromPullRequestOptions(...) + public static List