From 9cfc3a7a68f4e8c9fa6d69bd9f297cd76efbb500 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Mon, 5 May 2025 14:45:50 -0700 Subject: [PATCH 1/4] Factor out update from build logic into a new service --- .../BuildUpdaterService.cs | 96 +++++++++++++++++++ eng/update-dependencies/FromChannelCommand.cs | 71 +------------- eng/update-dependencies/Program.cs | 1 + 3 files changed, 100 insertions(+), 68 deletions(-) create mode 100644 eng/update-dependencies/BuildUpdaterService.cs diff --git a/eng/update-dependencies/BuildUpdaterService.cs b/eng/update-dependencies/BuildUpdaterService.cs new file mode 100644 index 0000000000..d90fd9a13c --- /dev/null +++ b/eng/update-dependencies/BuildUpdaterService.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.DotNet.DarcLib; +using Microsoft.DotNet.ProductConstructionService.Client.Models; +using Microsoft.Extensions.Logging; + +namespace Dotnet.Docker; + +internal interface IBuildUpdaterService +{ + Task UpdateFrom(Build build, CreatePullRequestOptions pullRequestOptions); +} + +internal class BuildUpdaterService( + IBuildAssetService buildAssetService, + IBasicBarClient barClient, + ILogger logger) : IBuildUpdaterService +{ + private readonly IBuildAssetService _buildAssetService = buildAssetService; + private readonly IBasicBarClient _barClient = barClient; + private readonly ILogger _logger = logger; + + public async Task UpdateFrom(Build build, CreatePullRequestOptions pullRequestOptions) + { + _logger.LogInformation("Updating to build {build.Id} with commit {options.Repo}@{build.Commit}", + build.Id, build.AzureDevOpsRepository ?? build.GitHubRepository, build.Commit); + + if (!IsVmrBuild(build)) + { + throw new InvalidOperationException( + "Expected a build of the VMR, but got a build of " + + $"{build.AzureDevOpsRepository ?? build.GitHubRepository} instead."); + } + + IEnumerable assets = await _barClient.GetAssetsAsync(buildId: build.Id); + + Asset productCommitsAsset = assets.FirstOrDefault(a => ProductCommits.SdkAssetRegex.IsMatch(a.Name)) + ?? throw new InvalidOperationException($"Could not find product version commit in assets."); + + string productCommitsJson = await _buildAssetService.GetAssetTextContentsAsync(productCommitsAsset); + ProductCommits productCommits = ProductCommits.FromJson(productCommitsJson); + + Version dockerfileVersion = ResolveMajorMinorVersion(productCommits.Sdk.Version); + + // Run old update-dependencies command using the resolved versions + var updateDependencies = new SpecificCommand(); + var updateDependenciesOptions = new SpecificCommandOptions() + { + DockerfileVersion = dockerfileVersion.ToString(), + ProductVersions = new Dictionary() + { + // In the VMR, runtime and aspnetcore versions are coupled + { "runtime", productCommits.Runtime.Version }, + { "aspnet", productCommits.AspNetCore.Version }, + { "aspnet-composite", productCommits.AspNetCore.Version }, + { "sdk", productCommits.Sdk.Version }, + }, + + // Pass through all properties of CreatePullRequestOptions + User = pullRequestOptions.User, + Email = pullRequestOptions.Email, + Password = pullRequestOptions.Password, + AzdoOrganization = pullRequestOptions.AzdoOrganization, + AzdoProject = pullRequestOptions.AzdoProject, + AzdoRepo = pullRequestOptions.AzdoRepo, + VersionSourceName = pullRequestOptions.VersionSourceName, + SourceBranch = pullRequestOptions.SourceBranch, + TargetBranch = pullRequestOptions.TargetBranch, + }; + + return await updateDependencies.ExecuteAsync(updateDependenciesOptions); + } + + private static Version ResolveMajorMinorVersion(string versionString) + { + string[] versionParts = versionString.Split('.'); + if (versionParts.Length < 2) + { + throw new InvalidOperationException($"Could not parse major-minor version from '{versionString}'."); + } + + return new Version(major: int.Parse(versionParts[0]), minor: int.Parse(versionParts[1])); + } + + private static bool IsVmrBuild(Build build) + { + string repo = build.GitHubRepository ?? build.AzureDevOpsRepository; + return repo == "https://github.com/dotnet/dotnet" + || repo == "https://dev.azure.com/dnceng/internal/_git/dotnet-dotnet"; + } +} diff --git a/eng/update-dependencies/FromChannelCommand.cs b/eng/update-dependencies/FromChannelCommand.cs index 9836738664..4d6e909a43 100644 --- a/eng/update-dependencies/FromChannelCommand.cs +++ b/eng/update-dependencies/FromChannelCommand.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.DarcLib; @@ -12,13 +10,13 @@ namespace Dotnet.Docker; internal class FromChannelCommand( - IBuildAssetService buildAssetService, IBasicBarClient barClient, + IBuildUpdaterService buildUpdaterService, ILogger logger) : BaseCommand { - private readonly IBuildAssetService _buildAssetService = buildAssetService; private readonly IBasicBarClient _barClient = barClient; + private readonly IBuildUpdaterService _buildUpdaterService = buildUpdaterService; private readonly ILogger _logger = logger; public override async Task ExecuteAsync(FromChannelOptions options) @@ -31,70 +29,7 @@ public override async Task ExecuteAsync(FromChannelOptions options) _logger.LogInformation("Channel {options.Channel} is '{channel.Name}'", options.Channel, latestBuild.Channels.FirstOrDefault(c => c.Id == options.Channel)?.Name); - _logger.LogInformation("Got latest build {latestBuild.Id} with commit {options.Repo}@{latestBuild.Commit}", - latestBuild.Id, latestBuild.AzureDevOpsRepository ?? latestBuild.GitHubRepository, latestBuild.Commit); - if (!IsVmrBuild(latestBuild)) - { - throw new InvalidOperationException( - "Expected a build of the VMR, but got a build of " + - $"{latestBuild.AzureDevOpsRepository ?? latestBuild.GitHubRepository} instead."); - } - - IEnumerable assets = await _barClient.GetAssetsAsync(buildId: latestBuild.Id); - - Asset productCommitsAsset = assets.FirstOrDefault(a => ProductCommits.SdkAssetRegex.IsMatch(a.Name)) - ?? throw new InvalidOperationException($"Could not find product version commit in assets."); - - string productCommitsJson = await _buildAssetService.GetAssetTextContentsAsync(productCommitsAsset); - ProductCommits productCommits = ProductCommits.FromJson(productCommitsJson); - - Version dockerfileVersion = ResolveMajorMinorVersion(productCommits.Sdk.Version); - - // Run old update-dependencies command using the resolved versions - var updateDependencies = new SpecificCommand(); - var updateDependenciesOptions = new SpecificCommandOptions() - { - DockerfileVersion = dockerfileVersion.ToString(), - ProductVersions = new Dictionary() - { - // In the VMR, runtime and aspnetcore versions are coupled - { "runtime", productCommits.Runtime.Version }, - { "aspnet", productCommits.AspNetCore.Version }, - { "aspnet-composite", productCommits.AspNetCore.Version }, - { "sdk", productCommits.Sdk.Version }, - }, - - // Pass through all properties of CreatePullRequestOptions - User = options.User, - Email = options.Email, - Password = options.Password, - AzdoOrganization = options.AzdoOrganization, - AzdoProject = options.AzdoProject, - AzdoRepo = options.AzdoRepo, - VersionSourceName = options.VersionSourceName, - SourceBranch = options.SourceBranch, - TargetBranch = options.TargetBranch, - }; - - return await updateDependencies.ExecuteAsync(updateDependenciesOptions); - } - - private static Version ResolveMajorMinorVersion(string versionString) - { - string[] versionParts = versionString.Split('.'); - if (versionParts.Length < 2) - { - throw new InvalidOperationException($"Could not parse major-minor version from '{versionString}'."); - } - - return new Version(major: int.Parse(versionParts[0]), minor: int.Parse(versionParts[1])); - } - - private static bool IsVmrBuild(Build build) - { - string repo = build.GitHubRepository ?? build.AzureDevOpsRepository; - return repo == "https://github.com/dotnet/dotnet" - || repo == "https://dev.azure.com/dnceng/internal/_git/dotnet-dotnet"; + return await _buildUpdaterService.UpdateFrom(latestBuild, options); } } diff --git a/eng/update-dependencies/Program.cs b/eng/update-dependencies/Program.cs index 76171ac077..bb35dbd38a 100644 --- a/eng/update-dependencies/Program.cs +++ b/eng/update-dependencies/Program.cs @@ -40,6 +40,7 @@ }, configureHost: host => host.ConfigureServices(services => { + services.AddSingleton(); services.AddSingleton(_ => new BarApiClient(null, null, disableInteractiveAuth: true)); services.AddSingleton(); From 64bc479c86f83cc9424e1b6551abe1a82ea0fb24 Mon Sep 17 00:00:00 2001 From: Logan Bussell Date: Mon, 5 May 2025 14:59:14 -0700 Subject: [PATCH 2/4] Add from-build command --- eng/update-dependencies/FromBuildCommand.cs | 27 +++++++++++++++++++++ eng/update-dependencies/FromBuildOptions.cs | 27 +++++++++++++++++++++ eng/update-dependencies/Program.cs | 4 +++ 3 files changed, 58 insertions(+) create mode 100644 eng/update-dependencies/FromBuildCommand.cs create mode 100644 eng/update-dependencies/FromBuildOptions.cs diff --git a/eng/update-dependencies/FromBuildCommand.cs b/eng/update-dependencies/FromBuildCommand.cs new file mode 100644 index 0000000000..3e9784f5f0 --- /dev/null +++ b/eng/update-dependencies/FromBuildCommand.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 System.Threading.Tasks; +using Microsoft.DotNet.DarcLib; +using Microsoft.DotNet.ProductConstructionService.Client.Models; +using Microsoft.Extensions.Logging; + +namespace Dotnet.Docker; + +internal class FromBuildCommand( + IBasicBarClient barClient, + IBuildUpdaterService buildUpdaterService, + ILogger logger) + : BaseCommand +{ + private readonly IBasicBarClient _barClient = barClient; + private readonly IBuildUpdaterService _buildUpdaterService = buildUpdaterService; + private readonly ILogger _logger = logger; + + public override async Task ExecuteAsync(FromBuildOptions options) + { + _logger.LogInformation("Getting BAR build with ID {options.Id}", options.Id); + Build build = await _barClient.GetBuildAsync(options.Id); + return await _buildUpdaterService.UpdateFrom(build, options); + } +} diff --git a/eng/update-dependencies/FromBuildOptions.cs b/eng/update-dependencies/FromBuildOptions.cs new file mode 100644 index 0000000000..f8fe9139e4 --- /dev/null +++ b/eng/update-dependencies/FromBuildOptions.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 System.Collections.Generic; +using System.CommandLine; + +namespace Dotnet.Docker; + +internal class FromBuildOptions : CreatePullRequestOptions, IOptions +{ + public required int Id { get; init; } + + public static new List Arguments { get; } = + [ + new Argument("id") + { + Arity = ArgumentArity.ExactlyOne, + Description = "The BAR build ID to use as a source for the update (see https://aka.ms/bar)" + }, + ..CreatePullRequestOptions.Arguments, + ]; + + public static new List