diff --git a/tools/FlatFlowMigrationCli/Constants.cs b/tools/FlatFlowMigrationCli/Constants.cs deleted file mode 100644 index 0ae82321e3..0000000000 --- a/tools/FlatFlowMigrationCli/Constants.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace FlatFlowMigrationCli; - -internal static class Constants -{ - public const string VmrUri = "https://github.com/dotnet/dotnet"; - public const string ArcadeRepoUri = "https://github.com/dotnet/arcade"; - public const string SdkRepoUri = "https://github.com/dotnet/sdk"; - public const string LatestArcadeChannel = ".NET Eng - Latest"; - public const string VmrChannelName = ".NET 10 UB"; - public const string SdkPatchLocation = "src/SourceBuild/patches"; -} diff --git a/tools/ProductConstructionService.ReproTool/Operations/ForwardFlowTestOperation.cs b/tools/ProductConstructionService.ReproTool/Operations/ForwardFlowTestOperation.cs new file mode 100644 index 0000000000..988982ba32 --- /dev/null +++ b/tools/ProductConstructionService.ReproTool/Operations/ForwardFlowTestOperation.cs @@ -0,0 +1,67 @@ +// 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.DarcLib; +using Microsoft.DotNet.ProductConstructionService.Client; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Tools.Common; +using GitHubClient = Octokit.GitHubClient; + +namespace ProductConstructionService.ReproTool.Operations; + +internal class ForwardFlowTestOperation( + VmrDependencyResolver vmrDependencyResolver, + ILogger logger, + GitHubClient ghClient, + DarcProcessManager darcProcessManager, + IBarApiClient prodBarClient, + [FromKeyedServices("local")] IProductConstructionServiceApi localPcsApi) : Operation(logger, ghClient, localPcsApi) +{ + internal override async Task RunAsync() + { + await darcProcessManager.InitializeAsync(); + + var vmrRepos = await vmrDependencyResolver.GetVmrRepositoriesAsync( + "https://github.com/dotnet/dotnet", + "https://github.com/dotnet/sdk", + "main"); + + var vmrTestBranch = await PrepareVmrForkAsync("main", skipCleanup: true); + + var channelName = $"repro-{Guid.NewGuid()}"; + await using var channel = await darcProcessManager.CreateTestChannelAsync(channelName, true); + + foreach (var vmrRepo in vmrRepos) + { + var productRepoForkUri = $"{ProductRepoFormat}{vmrRepo.Mapping.DefaultRemote.Split('/', StringSplitOptions.RemoveEmptyEntries).Last()}"; + var latestBuild = await prodBarClient.GetLatestBuildAsync(vmrRepo.Mapping.DefaultRemote, vmrRepo.Channel.Channel.Id); + + var productRepoTmpBranch = await PrepareProductRepoForkAsync(vmrRepo.Mapping.DefaultRemote, productRepoForkUri, latestBuild.GetBranch(), false); + + var testBuild = await CreateBuildAsync( + productRepoForkUri, + productRepoTmpBranch.Value, + latestBuild.Commit, + []); + + await UpdateVmrSourceFiles( + vmrTestBranch.Value, + vmrRepo.Mapping.DefaultRemote, + productRepoForkUri); + + await using var testSubscription = await darcProcessManager.CreateSubscriptionAsync( + channel: channelName, + sourceRepo: productRepoForkUri, + targetRepo: VmrForkUri, + targetBranch: vmrTestBranch.Value, + sourceDirectory: null, + targetDirectory: vmrRepo.Mapping.Name, + skipCleanup: true); + + await darcProcessManager.AddBuildToChannelAsync(testBuild.Id, channelName, skipCleanup: true); + + await TriggerSubscriptionAsync(testSubscription.Value); + } + } +} diff --git a/tools/ProductConstructionService.ReproTool/Operations/FullBackflowTestOperation.cs b/tools/ProductConstructionService.ReproTool/Operations/FullBackflowTestOperation.cs new file mode 100644 index 0000000000..581d25626c --- /dev/null +++ b/tools/ProductConstructionService.ReproTool/Operations/FullBackflowTestOperation.cs @@ -0,0 +1,76 @@ +// 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.DarcLib; +using Microsoft.DotNet.ProductConstructionService.Client; +using Microsoft.DotNet.ProductConstructionService.Client.Models; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using ProductConstructionService.ReproTool.Options; +using Tools.Common; +using GitHubClient = Octokit.GitHubClient; + +namespace ProductConstructionService.ReproTool.Operations; +internal class FullBackflowTestOperation : Operation +{ + private readonly IBarApiClient _prodBarClient; + private readonly FullBackflowTestOptions _options; + private readonly DarcProcessManager _darcProcessManager; + private readonly VmrDependencyResolver _vmrDependencyResolver; + + public FullBackflowTestOperation( + ILogger logger, + GitHubClient ghClient, + [FromKeyedServices("local")] IProductConstructionServiceApi localPcsApi, + IBarApiClient prodBarClient, + FullBackflowTestOptions options, + DarcProcessManager darcProcessManager, + VmrDependencyResolver vmrDependencyResolver) + : base(logger, ghClient, localPcsApi) + { + _prodBarClient = prodBarClient; + _options = options; + _darcProcessManager = darcProcessManager; + _vmrDependencyResolver = vmrDependencyResolver; + } + + internal override async Task RunAsync() + { + await _darcProcessManager.InitializeAsync(); + Build vmrBuild = await _prodBarClient.GetBuildAsync(_options.BuildId); + + Build testBuild = await CreateBuildAsync( + VmrForkUri, + _options.VmrBranch, + _options.Commit, + [ ..CreateAssetDataFromBuild(vmrBuild).Take(1000)]); + + var channelName = $"repro-{Guid.NewGuid()}"; + await using var channel = await _darcProcessManager.CreateTestChannelAsync(channelName, skipCleanup: true); + await _darcProcessManager.AddBuildToChannelAsync(testBuild.Id, channelName, skipCleanup: true); + + var vmrRepos = (await _vmrDependencyResolver.GetVmrRepositoriesAsync( + "https://github.com/dotnet/dotnet", + "https://github.com/dotnet/sdk", + "main")); + + foreach (var vmrRepo in vmrRepos) + { + var productRepoForkUri = $"{ProductRepoFormat}{vmrRepo.Mapping.DefaultRemote.Split('/', StringSplitOptions.RemoveEmptyEntries).Last()}"; + string targetBranch = _options.TargetBranch is null ? + (await PrepareProductRepoForkAsync(vmrRepo.Mapping.DefaultRemote, productRepoForkUri, vmrRepo.Mapping.DefaultRef, skipCleanup: true)).Value : + _options.TargetBranch; + + var subscription = await _darcProcessManager.CreateSubscriptionAsync( + channel: channelName, + sourceRepo: VmrForkUri, + targetRepo: productRepoForkUri, + targetBranch: targetBranch, + sourceDirectory: vmrRepo.Mapping.Name, + targetDirectory: null, + skipCleanup: true); + + await _darcProcessManager.TriggerSubscriptionAsync(subscription.Value); + } + } +} diff --git a/tools/ProductConstructionService.ReproTool/Options/ForwardFlowTestOptions.cs b/tools/ProductConstructionService.ReproTool/Options/ForwardFlowTestOptions.cs new file mode 100644 index 0000000000..f8ae0ea90f --- /dev/null +++ b/tools/ProductConstructionService.ReproTool/Options/ForwardFlowTestOptions.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using CommandLine; +using Microsoft.Extensions.DependencyInjection; +using ProductConstructionService.ReproTool.Operations; + +namespace ProductConstructionService.ReproTool.Options; +[Verb("forward-flow-test", HelpText = "Test full flat flow in the maestro-auth-test org")] +internal class ForwardFlowTestOptions : Options +{ + internal override Operation GetOperation(IServiceProvider sp) + => ActivatorUtilities.CreateInstance(sp); +} diff --git a/tools/ProductConstructionService.ReproTool/Options/FullBackflowTestOptions.cs b/tools/ProductConstructionService.ReproTool/Options/FullBackflowTestOptions.cs new file mode 100644 index 0000000000..db822cacd9 --- /dev/null +++ b/tools/ProductConstructionService.ReproTool/Options/FullBackflowTestOptions.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 CommandLine; +using Microsoft.Extensions.DependencyInjection; +using ProductConstructionService.ReproTool.Operations; + +namespace ProductConstructionService.ReproTool.Options; + +[Verb("backflow-test", HelpText = "Flows an existing VMR build to all repos in maestro-auth-test")] +internal class FullBackflowTestOptions : Options +{ + [Option("build", HelpText = "Real VMR build from which we'll take assets from", Required = true)] + public required int BuildId { get; init; } + + [Option("target-branch", HelpText = "Branch to target in all repos, if missing, will create a new branch", Required = true)] + public string? TargetBranch { get; set; } + + [Option("vmr-branch", HelpText = "Vmr branch from which to backflow", Required = true)] + public required string VmrBranch { get; init; } + + [Option("commit", HelpText = "maestro-auth-test/dotnet commit to flow", Required = true)] + public required string Commit { get; init; } + + internal override Operation GetOperation(IServiceProvider sp) + => ActivatorUtilities.CreateInstance(sp, this); +} diff --git a/tools/ProductConstructionService.ReproTool/Program.cs b/tools/ProductConstructionService.ReproTool/Program.cs index 9e313d1f04..93607add6e 100644 --- a/tools/ProductConstructionService.ReproTool/Program.cs +++ b/tools/ProductConstructionService.ReproTool/Program.cs @@ -12,6 +12,8 @@ Type[] options = [ typeof(ReproOptions), + typeof(ForwardFlowTestOptions), + typeof(FullBackflowTestOptions), typeof(FlowCommitOptions), ]; diff --git a/tools/ProductConstructionService.ReproTool/ReproTool.cs b/tools/ProductConstructionService.ReproTool/ReproTool.cs deleted file mode 100644 index 9faf87dd98..0000000000 --- a/tools/ProductConstructionService.ReproTool/ReproTool.cs +++ /dev/null @@ -1,354 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Maestro.Data; -using Microsoft.DotNet.DarcLib; -using Microsoft.DotNet.DarcLib.Helpers; -using Microsoft.DotNet.DarcLib.VirtualMonoRepo; -using Microsoft.DotNet.ProductConstructionService.Client; -using Microsoft.DotNet.ProductConstructionService.Client.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Octokit; -using Build = Microsoft.DotNet.ProductConstructionService.Client.Models.Build; -using BuildData = Microsoft.DotNet.ProductConstructionService.Client.Models.BuildData; -using GitHubClient = Octokit.GitHubClient; - -namespace ProductConstructionService.ReproTool; - -internal class ReproTool( - IBarApiClient prodBarClient, - ReproToolOptions options, - BuildAssetRegistryContext context, - DarcProcessManager darcProcessManager, - IProductConstructionServiceApi localPcsApi, - GitHubClient ghClient, - ILogger logger) -{ - private const string MaestroAuthTestOrgName = "maestro-auth-test"; - private const string VmrForkRepoName = "dotnet"; - private const string VmrForkUri = $"https://github.com/{MaestroAuthTestOrgName}/{VmrForkRepoName}"; - private const string ProductRepoFormat = $"https://github.com/{MaestroAuthTestOrgName}/"; - private const long InstallationId = 289474; - private const string SourceMappingsPath = $"{VmrInfo.SourceDirName}/{VmrInfo.SourceMappingsFileName}"; - private const string SourceManifestPath = $"{VmrInfo.SourceDirName}/{VmrInfo.SourceManifestFileName}"; - private const string DarcPRBranchPrefix = "darc"; - - internal async Task ReproduceCodeFlow() - { - logger.LogInformation("Fetching {subscriptionId} subscription from BAR", - options.Subscription); - var subscription = await prodBarClient.GetSubscriptionAsync(options.Subscription); - - if (subscription == null) - { - throw new ArgumentException($"Couldn't find subscription with subscription id {options.Subscription}"); - } - - if (!subscription.SourceEnabled) - { - throw new ArgumentException($"Subscription {options.Subscription} is not a code flow subscription"); - } - - if (!string.IsNullOrEmpty(subscription.SourceDirectory) && !string.IsNullOrEmpty(subscription.TargetDirectory)) - { - throw new ArgumentException("Code flow subscription incorrectly configured: is missing SourceDirectory or TargetDirectory"); - } - - if (!string.IsNullOrEmpty(options.Commit) && options.BuildId != null) - { - throw new ArgumentException($"Only one of {nameof(ReproToolOptions.Commit)} and {nameof(ReproToolOptions.BuildId)} can be provided"); - } - - Build? build = null; - if (options.BuildId != null) - { - build = await prodBarClient.GetBuildAsync(options.BuildId.Value); - if (build.GitHubRepository != subscription.SourceRepository) - { - throw new ArgumentException($"Build {build.Id} repository {build.GitHubRepository} doesn't match the subscription source repository {subscription.SourceRepository}"); - } - } - await darcProcessManager.InitializeAsync(); - - var defaultChannel = (await prodBarClient.GetDefaultChannelsAsync(repository: subscription.SourceRepository, channel: subscription.Channel.Name)).First(); - - string vmrBranch, productRepoUri, productRepoBranch; - bool isForwardFlow = !string.IsNullOrEmpty(subscription.TargetDirectory); - if (isForwardFlow) - { - vmrBranch = subscription.TargetBranch; - productRepoUri = subscription.SourceRepository; - productRepoBranch = defaultChannel.Branch; - } - else - { - vmrBranch = defaultChannel.Branch; - productRepoUri = subscription.TargetRepository; - productRepoBranch = subscription.TargetBranch; - } - var productRepoForkUri = ProductRepoFormat + productRepoUri.Split('/', StringSplitOptions.RemoveEmptyEntries).Last(); - logger.LogInformation("Reproducing subscription from {sourceRepo} to {targetRepo}", - isForwardFlow ? productRepoForkUri : VmrForkUri, - isForwardFlow ? VmrForkUri : productRepoForkUri); - - await using var vmrTmpBranch = await PrepareVmrForkAsync(vmrBranch, productRepoUri, productRepoForkUri, options.SkipCleanup); - - logger.LogInformation("Preparing product repo fork {productRepoFork}, branch {branch}", productRepoForkUri, productRepoBranch); - await using var productRepoTmpBranch = await PrepareProductRepoForkAsync(productRepoUri, productRepoForkUri, productRepoBranch, options.SkipCleanup); - - // Find the latest commit in the source repo to create a build from - string sourceRepoSha; - (string sourceRepoName, string sourceRepoOwner) = GitRepoUrlParser.GetRepoNameAndOwner(subscription.SourceRepository); - if (build != null) - { - sourceRepoSha = build.Commit; - } - else if (string.IsNullOrEmpty(options.Commit)) - { - var res = await ghClient.Git.Reference.Get(sourceRepoOwner, sourceRepoName, $"heads/{defaultChannel.Branch}"); - sourceRepoSha = res.Object.Sha; - } - else - { - // Validate that the commit actually exists - try - { - await ghClient.Repository.Commit.Get(sourceRepoOwner, sourceRepoName, options.Commit); - } - catch (NotFoundException) - { - throw new ArgumentException($"Commit {options.Commit} doesn't exist in repo {subscription.SourceRepository}"); - } - sourceRepoSha = options.Commit; - } - - var channelName = $"repro-{Guid.NewGuid()}"; - await using var channel = await darcProcessManager.CreateTestChannelAsync(channelName, options.SkipCleanup); - - var testBuild = await CreateBuildAsync( - isForwardFlow ? productRepoForkUri : VmrForkUri, - isForwardFlow ? productRepoTmpBranch.Value : vmrTmpBranch.Value, - sourceRepoSha, - build != null ? CreateAssetDataFromBuild(build) : []); - - await using var testSubscription = await darcProcessManager.CreateSubscriptionAsync( - channel: channelName, - sourceRepo: isForwardFlow ? productRepoForkUri : VmrForkUri, - targetRepo: isForwardFlow ? VmrForkUri : productRepoForkUri, - targetBranch: isForwardFlow ? vmrTmpBranch.Value : productRepoTmpBranch.Value, - sourceDirectory: subscription.SourceDirectory, - targetDirectory: subscription.TargetDirectory, - skipCleanup: options.SkipCleanup); - - await darcProcessManager.AddBuildToChannelAsync(testBuild.Id, channelName, options.SkipCleanup); - - await TriggerSubscriptionAsync(testSubscription.Value); - - if (options.SkipCleanup) - { - logger.LogInformation("Skipping cleanup. If you want to re-trigger the reproduced subscription run \"darc trigger-subscriptions --ids {subscriptionId} --bar-uri {barUri}\"", - testSubscription.Value, - ProductConstructionServiceApiOptions.PcsLocalUri); - return; - } - - logger.LogInformation("Code flow successfully recreated. Press enter to finish and cleanup"); - Console.ReadLine(); - - // Cleanup - if (isForwardFlow) - { - await DeleteDarcPRBranchAsync(VmrForkRepoName, vmrTmpBranch.Value); - } - else - { - await DeleteDarcPRBranchAsync(productRepoUri.Split('/').Last(), productRepoTmpBranch.Value); - } - } - - private async Task DeleteDarcPRBranchAsync(string repo, string targetBranch) - { - var branch = (await ghClient.Repository.Branch.GetAll(MaestroAuthTestOrgName, repo)) - .FirstOrDefault(branch => branch.Name.StartsWith($"{DarcPRBranchPrefix}-{targetBranch}")); - - if (branch == null) - { - logger.LogWarning("Couldn't find darc PR branch targeting branch {targetBranch}", targetBranch); - } - else - { - await DeleteGitHubBranchAsync(repo, branch.Name); - } - } - - private async Task AddRepositoryToBarIfMissingAsync(string repositoryName) - { - if ((await context.Repositories.FirstOrDefaultAsync(repo => repo.RepositoryName == repositoryName)) == null) - { - logger.LogInformation("Repo {repo} missing in local BAR. Adding an entry for it", repositoryName); - context.Repositories.Add(new Maestro.Data.Models.Repository - { - RepositoryName = repositoryName, - InstallationId = InstallationId - }); - await context.SaveChangesAsync(); - } - } - - private async Task CreateBuildAsync(string repositoryUrl, string branch, string commit, List assets) - { - logger.LogInformation("Creating a test build"); - - Build build = await localPcsApi.Builds.CreateAsync(new BuildData( - commit: commit, - azureDevOpsAccount: "test", - azureDevOpsProject: "test", - azureDevOpsBuildNumber: $"{DateTime.UtcNow:yyyyMMdd}.{new Random().Next(1, 75)}", - azureDevOpsRepository: repositoryUrl, - azureDevOpsBranch: branch, - released: false, - stable: false) - { - GitHubRepository = repositoryUrl, - GitHubBranch = branch, - Assets = assets - }); - - return build; - } - - private static List CreateAssetDataFromBuild(Build build) - { - return build.Assets - .Select(asset => new AssetData(false) - { - Name = asset.Name, - Version = asset.Version, - Locations = asset.Locations?.Select(location => new AssetLocationData(location.Type) { Location = location.Location}).ToList() - }) - .ToList(); - } - - private async Task TriggerSubscriptionAsync(string subscriptionId) - { - logger.LogInformation("Triggering subscription {subscriptionId}", subscriptionId); - await localPcsApi.Subscriptions.TriggerSubscriptionAsync(default, Guid.Parse(subscriptionId)); - } - - private async Task> PrepareVmrForkAsync( - string branch, - string productRepoUri, - string productRepoForkUri, - bool skipCleanup) - { - logger.LogInformation("Preparing VMR fork"); - // Sync the VMR fork branch - await SyncForkAsync("dotnet", "dotnet", branch); - // Check if the user has the forked VMR in local DB - await AddRepositoryToBarIfMissingAsync(VmrForkUri); - - var newBranch = await CreateTmpBranchAsync(VmrForkRepoName, branch, skipCleanup); - - // Fetch source mappings and source manifest files and replace the mapping for the repo we're testing on - logger.LogInformation("Updating source mappings and source manifest files in VMR fork to replace original product repo mapping with fork mapping"); - await UpdateRemoteVmrForkFileAsync(newBranch.Value, productRepoUri, productRepoForkUri, SourceMappingsPath); - await UpdateRemoteVmrForkFileAsync(newBranch.Value, productRepoUri, productRepoForkUri, SourceManifestPath); - - return newBranch; - } - - private async Task DeleteGitHubBranchAsync(string repo, string branch) => await ghClient.Git.Reference.Delete(MaestroAuthTestOrgName, repo, $"heads/{branch}"); - - private async Task UpdateRemoteVmrForkFileAsync(string branch, string productRepoUri, string productRepoForkUri, string filePath) - { - logger.LogInformation("Updating file {file} on branch {branch} in the VMR fork", filePath, branch); - // Fetch remote file and replace the product repo URI with the repo we're testing on - var sourceMappingsFile = (await ghClient.Repository.Content.GetAllContentsByRef( - MaestroAuthTestOrgName, - VmrForkRepoName, - filePath, - branch)) - .FirstOrDefault() - ?? throw new Exception($"Failed to find file {SourceMappingsPath} in {MaestroAuthTestOrgName}" + - $"/{VmrForkRepoName} on branch {SourceMappingsPath}"); - - // Replace the product repo uri with the forked one - var updatedSourceMappings = sourceMappingsFile.Content.Replace(productRepoUri, productRepoForkUri); - UpdateFileRequest update = new( - $"Update {productRepoUri} source mapping", - updatedSourceMappings, - sourceMappingsFile.Sha, - branch); - - await ghClient.Repository.Content.UpdateFile( - MaestroAuthTestOrgName, - VmrForkRepoName, - filePath, - update); - } - - private async Task> PrepareProductRepoForkAsync( - string productRepoUri, - string productRepoForkUri, - string productRepoBranch, - bool skipCleanup) - { - logger.LogInformation("Preparing product repo {repo} fork", productRepoUri); - (var name, var org) = GitRepoUrlParser.GetRepoNameAndOwner(productRepoUri); - // Check if the product repo fork already exists - var allRepos = await ghClient.Repository.GetAllForOrg(MaestroAuthTestOrgName); - - // If we already have a fork in maestro-auth-test, sync the branch we need with the source - if (allRepos.FirstOrDefault(repo => repo.HtmlUrl == productRepoForkUri) != null) - { - logger.LogInformation("Product repo fork {fork} already exists, syncing branch {branch} with source", productRepoForkUri, productRepoBranch); - await SyncForkAsync(org, name, productRepoBranch); - } - // If we don't, create a fork - else - { - logger.LogInformation("Forking product repo {source} to fork {fork}", productRepoUri, productRepoForkUri); - await ghClient.Repository.Forks.Create(org, name, new NewRepositoryFork { Organization = MaestroAuthTestOrgName }); - } - await AddRepositoryToBarIfMissingAsync(productRepoForkUri); - - return await CreateTmpBranchAsync(name, productRepoBranch, skipCleanup); - } - - private async Task SyncForkAsync(string originOrg, string repoName, string branch) - { - logger.LogInformation("Syncing fork {fork} branch {branch} with upstream repo {upstream}", $"{MaestroAuthTestOrgName}/{repoName}", branch, $"{originOrg}/{repoName}"); - var reference = $"heads/{branch}"; - var upstream = await ghClient.Git.Reference.Get(originOrg, repoName, reference); - await ghClient.Git.Reference.Update(MaestroAuthTestOrgName, repoName, reference, new ReferenceUpdate(upstream.Object.Sha, true)); - } - - private async Task> CreateTmpBranchAsync(string repoName, string originalBranch, bool skipCleanup) - { - var newBranchName = $"repro/{Guid.NewGuid()}"; - logger.LogInformation("Creating temporary branch {branch} in {repo}", newBranchName, $"{MaestroAuthTestOrgName}/{repoName}"); - - var baseBranch = await ghClient.Git.Reference.Get(MaestroAuthTestOrgName, repoName, $"heads/{originalBranch}"); - var newBranch = new NewReference($"refs/heads/{newBranchName}", baseBranch.Object.Sha); - await ghClient.Git.Reference.Create(MaestroAuthTestOrgName, repoName, newBranch); - - return AsyncDisposableValue.Create(newBranchName, async () => - { - if (skipCleanup) - { - return; - } - - logger.LogInformation("Cleaning up temporary branch {branchName}", newBranchName); - try - { - await DeleteGitHubBranchAsync(repoName, newBranchName); - } - catch - { - // If this throws an exception the most likely cause is that the branch was already deleted - } - }); - } -} diff --git a/tools/ProductConstructionService.ReproTool/ReproToolConfiguration.cs b/tools/ProductConstructionService.ReproTool/ReproToolConfiguration.cs deleted file mode 100644 index d373de62aa..0000000000 --- a/tools/ProductConstructionService.ReproTool/ReproToolConfiguration.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Maestro.Data; -using Microsoft.DotNet.DarcLib.Helpers; -using Microsoft.DotNet.DarcLib; -using Microsoft.DotNet.GitHub.Authentication; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Maestro.DataProviders; -using Microsoft.DotNet.Kusto; -using Microsoft.DotNet.ProductConstructionService.Client; -using Octokit; -using Microsoft.Extensions.Logging.Console; -using ProductConstructionService.Common; -using GitHubClient = Octokit.GitHubClient; - -namespace ProductConstructionService.ReproTool; -internal static class ReproToolConfiguration -{ - private const string LocalDbConnectionString = "Data Source=localhost\\SQLEXPRESS;Initial Catalog=BuildAssetRegistry;Integrated Security=true"; - private const string MaestroProdUri = "https://maestro.dot.net"; - internal const string PcsLocalUri = "https://localhost:53180"; - - internal static ServiceCollection RegisterServices( - this ServiceCollection services, - ReproToolOptions options) - { - services.AddSingleton(options); - services.AddLogging(b => b - .AddConsole(o => o.FormatterName = CompactConsoleLoggerFormatter.FormatterName) - .AddConsoleFormatter() - .SetMinimumLevel(LogLevel.Information)); - services.AddSingleton(sp => sp.GetRequiredService>()); - - services.AddSingleton(sp => new BarApiClient( - null, - managedIdentityId: null, - disableInteractiveAuth: false, - MaestroProdUri)); - services.AddSingleton(sp => ActivatorUtilities.CreateInstance(sp, "git")); - services.AddSingleton(); - services.AddSingleton(PcsApiFactory.GetAnonymous(PcsLocalUri)); - services.AddSingleton(_ => new GitHubClient(new ProductHeaderValue("repro-tool")) - { - Credentials = new Credentials(options.GitHubToken) - }); - - services.TryAddTransient(); - - services.AddDbContext(options => - { - // Do not log DB context initialization and command executed events - options.ConfigureWarnings(w => - { - w.Ignore(CoreEventId.ContextInitialized); - w.Ignore(RelationalEventId.CommandExecuted); - }); - - options.UseSqlServer(LocalDbConnectionString, sqlOptions => - { - sqlOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery); - }); - }); - - services.AddKustoClientProvider("Kusto"); - services.AddSingleton(); - - return services; - } -} diff --git a/tools/ProductConstructionService.ReproTool/ReproToolOptions.cs b/tools/ProductConstructionService.ReproTool/ReproToolOptions.cs deleted file mode 100644 index f520e96a71..0000000000 --- a/tools/ProductConstructionService.ReproTool/ReproToolOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using CommandLine; - -namespace ProductConstructionService.ReproTool; - -internal class ReproToolOptions -{ - [Option('s', "subscription", HelpText = "Subscription that's getting reproduced", Required = true)] - public required string Subscription { get; init; } - - [Option("github-token", HelpText = "GitHub token", Required = false)] - public string? GitHubToken { get; set; } - - [Option("commit", HelpText = "Commit to flow. Use when not flowing a build. If neither commit or build is specified, the latest commit in the subscription's source repository is flown", Required = false)] - public string? Commit { get; init; } - - [Option("buildId", HelpText = "BAR build ID to flow", Required = false)] - public int? BuildId { get; init; } - - [Option("skip-cleanup", HelpText = "Don't delete the created resources if they're needed for further testing. This includes the channel, subscription and PR branches. False by default", Required = false)] - public bool SkipCleanup { get; init; } = false; -}