diff --git a/eng/update-dependencies/BuildUpdaterService.cs b/eng/update-dependencies/BuildUpdaterService.cs new file mode 100644 index 0000000000..73ff8996dd --- /dev/null +++ b/eng/update-dependencies/BuildUpdaterService.cs @@ -0,0 +1,110 @@ +// 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; + + /// + /// Updates product versions according to a specific BAR build of the VMR. This will update the + /// manifest.versions.json file, generate Dockerfiles and Readmes from the templates, and if + /// credentials are provided, submit a pull request. + /// + /// + /// A build of the VMR repo (dotnet/dotnet) + /// + /// + /// Options for creating a pull request. If credentials are provided, a pull request will be created. + /// + /// Exit code (0 for success) + 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() + { + // "dotnet" version is also required. It sets the "dotnet|*|product-version" + // variable which is used for runtime-deps, runtime, and aspnet tags. + { "dotnet", productCommits.Runtime.Version }, + { "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/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