diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 6bd248fa0..d9e85a03d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,2 +1,2 @@ -- Add `--target-api-url` to `gh ado2gh migrate-repo`, `gh bbs2gh migrate-repo`, and `gh gei migrate-org` to support newer GitHub migration paths. +- Add `--target-api-url` to commonly used commands to support newer GitHub migration paths. - Fixed `gh ado2gh rewire-pipeline` command for ADO Team Projects with more than 10,000 Build Definitions. diff --git a/src/Octoshift/Commands/CreateTeam/CreateTeamCommandBase.cs b/src/Octoshift/Commands/CreateTeam/CreateTeamCommandBase.cs index 3050b5200..1bcfe5416 100644 --- a/src/Octoshift/Commands/CreateTeam/CreateTeamCommandBase.cs +++ b/src/Octoshift/Commands/CreateTeam/CreateTeamCommandBase.cs @@ -22,7 +22,7 @@ public CreateTeamCommandBase() : base(name: "create-team", description: "Creates { Description = "Personal access token of the GitHub target. Overrides GH_PAT environment variable." }; - public Option TargetApiUrl { get; } = new("--target-api-url") + public virtual Option TargetApiUrl { get; } = new("--target-api-url") { Description = "The URL of the target API, if not migrating to github.com. Defaults to https://api.github.com" }; diff --git a/src/Octoshift/Commands/GenerateMannequinCsv/GenerateMannequinCsvCommandBase.cs b/src/Octoshift/Commands/GenerateMannequinCsv/GenerateMannequinCsvCommandBase.cs index b6971166b..6c532b504 100644 --- a/src/Octoshift/Commands/GenerateMannequinCsv/GenerateMannequinCsvCommandBase.cs +++ b/src/Octoshift/Commands/GenerateMannequinCsv/GenerateMannequinCsvCommandBase.cs @@ -37,7 +37,7 @@ public GenerateMannequinCsvCommandBase() : base( { Description = "Personal access token of the GitHub target. Overrides GH_PAT environment variable." }; - public Option TargetApiUrl { get; } = new("--target-api-url") + public virtual Option TargetApiUrl { get; } = new("--target-api-url") { Description = "The URL of the target API, if not migrating to github.com. Defaults to https://api.github.com" }; diff --git a/src/Octoshift/Commands/GrantMigratorRole/GrantMigratorRoleCommandBase.cs b/src/Octoshift/Commands/GrantMigratorRole/GrantMigratorRoleCommandBase.cs index c33e85349..c7f6bf42f 100644 --- a/src/Octoshift/Commands/GrantMigratorRole/GrantMigratorRoleCommandBase.cs +++ b/src/Octoshift/Commands/GrantMigratorRole/GrantMigratorRoleCommandBase.cs @@ -30,7 +30,7 @@ public GrantMigratorRoleCommandBase() : base( IsRequired = false, Description = "The URL of the GitHub Enterprise Server instance, if migrating from GHES. Supports granting access for exports. Can only configure one of --ghes-api-url or --target-api-url at a time." }; - public Option TargetApiUrl { get; } = new("--target-api-url") + public virtual Option TargetApiUrl { get; } = new("--target-api-url") { IsRequired = false, Description = "The URL of the target API, if not migrating to github.com. Defaults to https://api.github.com. Can only configure one of --ghes-api-url or --target-api-url at a time." diff --git a/src/Octoshift/Commands/ReclaimMannequin/ReclaimMannequinCommandBase.cs b/src/Octoshift/Commands/ReclaimMannequin/ReclaimMannequinCommandBase.cs index 145b66e2c..6e8542ef6 100644 --- a/src/Octoshift/Commands/ReclaimMannequin/ReclaimMannequinCommandBase.cs +++ b/src/Octoshift/Commands/ReclaimMannequin/ReclaimMannequinCommandBase.cs @@ -67,7 +67,7 @@ public ReclaimMannequinCommandBase() : base( Description = "Reclaim mannequins immediately without sending an invitation to the user. Only available for Enterprise Managed Users (EMU) organizations. Warning: this is irreversible!" }; - public Option TargetApiUrl { get; } = new("--target-api-url") + public virtual Option TargetApiUrl { get; } = new("--target-api-url") { Description = "The URL of the target API, if not migrating to github.com. Defaults to https://api.github.com" }; diff --git a/src/OctoshiftCLI.Tests/ado2gh/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs b/src/OctoshiftCLI.Tests/ado2gh/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs index 47327898b..223a0eba0 100644 --- a/src/OctoshiftCLI.Tests/ado2gh/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs +++ b/src/OctoshiftCLI.Tests/ado2gh/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs @@ -102,6 +102,34 @@ public async Task SequentialScript_Single_Repo_No_Options() _scriptOutput.Should().Be(expected); } + [Fact] + public async Task SequentialScript_Single_Repo_With_TargetApiUrl() + { + // Arrange + _mockAdoInspector.Setup(m => m.GetRepoCount()).ReturnsAsync(1); + _mockAdoInspector.Setup(m => m.GetOrgs()).ReturnsAsync(ADO_ORGS); + _mockAdoInspector.Setup(m => m.GetTeamProjects(ADO_ORG)).ReturnsAsync(ADO_TEAM_PROJECTS); + _mockAdoInspector.Setup(m => m.GetRepos(ADO_ORG, ADO_TEAM_PROJECT)).ReturnsAsync(ADO_REPOS); + var targetApiUrl = "https://foo.com/api/v3"; + + // Act + var args = new GenerateScriptCommandArgs + { + GithubOrg = GITHUB_ORG, + AdoOrg = ADO_ORG, + Sequential = true, + Output = new FileInfo("unit-test-output"), + TargetApiUrl = targetApiUrl + }; + await _handler.Handle(args); + + _scriptOutput = TrimNonExecutableLines(_scriptOutput); + var expected = $"Exec {{ gh ado2gh migrate-repo --target-api-url \"{targetApiUrl}\" --ado-org \"{ADO_ORG}\" --ado-team-project \"{ADO_TEAM_PROJECT}\" --ado-repo \"{FOO_REPO}\" --github-org \"{GITHUB_ORG}\" --github-repo \"{ADO_TEAM_PROJECT}-{FOO_REPO}\" --target-repo-visibility private }}"; + + // Assert + _scriptOutput.Should().Be(expected); + } + [Fact] public async Task SequentialScript_Single_Repo_AdoServer() { diff --git a/src/OctoshiftCLI.Tests/ado2gh/Commands/GenerateScript/GenerateScriptCommandTests.cs b/src/OctoshiftCLI.Tests/ado2gh/Commands/GenerateScript/GenerateScriptCommandTests.cs index 52555eb54..b06d0d323 100644 --- a/src/OctoshiftCLI.Tests/ado2gh/Commands/GenerateScript/GenerateScriptCommandTests.cs +++ b/src/OctoshiftCLI.Tests/ado2gh/Commands/GenerateScript/GenerateScriptCommandTests.cs @@ -38,7 +38,7 @@ public void Should_Have_Options() var command = new GenerateScriptCommand(); command.Should().NotBeNull(); command.Name.Should().Be("generate-script"); - command.Options.Count.Should().Be(17); + command.Options.Count.Should().Be(18); TestHelpers.VerifyCommandOption(command.Options, "github-org", true); TestHelpers.VerifyCommandOption(command.Options, "ado-org", false); @@ -57,6 +57,7 @@ public void Should_Have_Options() TestHelpers.VerifyCommandOption(command.Options, "rewire-pipelines", false); TestHelpers.VerifyCommandOption(command.Options, "all", false); TestHelpers.VerifyCommandOption(command.Options, "repo-list", false); + TestHelpers.VerifyCommandOption(command.Options, "target-api-url", false); } [Fact] diff --git a/src/OctoshiftCLI.Tests/bbs2gh/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs b/src/OctoshiftCLI.Tests/bbs2gh/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs index b8da1b556..211306a5b 100644 --- a/src/OctoshiftCLI.Tests/bbs2gh/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs +++ b/src/OctoshiftCLI.Tests/bbs2gh/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs @@ -517,6 +517,41 @@ public async Task One_Repo_With_Smb() _mockFileSystemProvider.Verify(m => m.WriteAllTextAsync(It.IsAny(), It.Is(script => script.Contains(migrateRepoCommand)))); } + [Fact] + public async Task One_Repo_With_Smb_And_TargetApiUrl() + { + // Arrange + _mockBbsApi.Setup(m => m.GetProjects()).ReturnsAsync(new[] + { + (Id: 1, Key: BBS_FOO_PROJECT_KEY, Name: BBS_FOO_PROJECT_NAME), + }); + _mockBbsApi.Setup(m => m.GetRepos(BBS_FOO_PROJECT_KEY)).ReturnsAsync(new[] + { + (Id: 1, Slug: BBS_FOO_REPO_1_SLUG, Name: BBS_FOO_REPO_1_NAME), + }); + var targetApiUrl = "https://foo.com/api/v3"; + var migrateRepoCommand = $"Exec {{ gh bbs2gh migrate-repo --target-api-url \"{targetApiUrl}\" --bbs-server-url \"{BBS_SERVER_URL}\" --bbs-username \"{BBS_USERNAME}\" --bbs-shared-home \"{BBS_SHARED_HOME}\" --bbs-project \"{BBS_FOO_PROJECT_KEY}\" --bbs-repo \"{BBS_FOO_REPO_1_SLUG}\" --smb-user \"{SMB_USER}\" --smb-domain {SMB_DOMAIN} --github-org \"{GITHUB_ORG}\" --github-repo \"{BBS_FOO_PROJECT_KEY}-{BBS_FOO_REPO_1_SLUG}\" --verbose --target-repo-visibility private }}"; + + // Act + var args = new GenerateScriptCommandArgs + { + BbsServerUrl = BBS_SERVER_URL, + GithubOrg = GITHUB_ORG, + BbsUsername = BBS_USERNAME, + BbsPassword = BBS_PASSWORD, + BbsSharedHome = BBS_SHARED_HOME, + SmbUser = SMB_USER, + SmbDomain = SMB_DOMAIN, + Output = new FileInfo(OUTPUT), + Verbose = true, + TargetApiUrl = targetApiUrl + }; + await _handler.Handle(args); + + // Assert + _mockFileSystemProvider.Verify(m => m.WriteAllTextAsync(It.IsAny(), It.Is(script => script.Contains(migrateRepoCommand)))); + } + [Fact] public async Task One_Repo_With_Smb_And_Archive_Download_Host() { diff --git a/src/OctoshiftCLI.Tests/bbs2gh/Commands/GenerateScript/GenerateScriptCommandTests.cs b/src/OctoshiftCLI.Tests/bbs2gh/Commands/GenerateScript/GenerateScriptCommandTests.cs index a268930e9..db247883e 100644 --- a/src/OctoshiftCLI.Tests/bbs2gh/Commands/GenerateScript/GenerateScriptCommandTests.cs +++ b/src/OctoshiftCLI.Tests/bbs2gh/Commands/GenerateScript/GenerateScriptCommandTests.cs @@ -36,7 +36,7 @@ public void Should_Have_Options() { _command.Should().NotBeNull(); _command.Name.Should().Be("generate-script"); - _command.Options.Count.Should().Be(19); + _command.Options.Count.Should().Be(20); TestHelpers.VerifyCommandOption(_command.Options, "bbs-server-url", true); TestHelpers.VerifyCommandOption(_command.Options, "github-org", true); @@ -57,6 +57,7 @@ public void Should_Have_Options() TestHelpers.VerifyCommandOption(_command.Options, "aws-region", false); TestHelpers.VerifyCommandOption(_command.Options, "keep-archive", false); TestHelpers.VerifyCommandOption(_command.Options, "no-ssl-verify", false); + TestHelpers.VerifyCommandOption(_command.Options, "target-api-url", false); } [Fact] diff --git a/src/OctoshiftCLI.Tests/gei/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs b/src/OctoshiftCLI.Tests/gei/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs index a08c6453e..31ce841eb 100644 --- a/src/OctoshiftCLI.Tests/gei/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs +++ b/src/OctoshiftCLI.Tests/gei/Commands/GenerateScript/GenerateScriptCommandHandlerTests.cs @@ -153,6 +153,33 @@ public async Task Sequential_Github_Single_Repo() _script.Should().Be(expected); } + [Fact] + public async Task Sequential_Github_Single_Repo_With_TargetApiUrl() + { + // Arrange + _mockGithubApi + .Setup(m => m.GetRepos(SOURCE_ORG)) + .ReturnsAsync(new[] { (REPO, "private") }); + var targetApiUrl = "https://foo.com/api/v3"; + var expected = $"Exec {{ gh gei migrate-repo --target-api-url \"{targetApiUrl}\" --github-source-org \"{SOURCE_ORG}\" --source-repo \"{REPO}\" --github-target-org \"{TARGET_ORG}\" --target-repo \"{REPO}\" --target-repo-visibility private }}"; + + // Act + var args = new GenerateScriptCommandArgs + { + GithubSourceOrg = SOURCE_ORG, + GithubTargetOrg = TARGET_ORG, + Output = new FileInfo("unit-test-output"), + Sequential = true, + TargetApiUrl = targetApiUrl + }; + await _handler.Handle(args); + + _script = TrimNonExecutableLines(_script); + + // Assert + _script.Should().Be(expected); + } + [Fact] public async Task Sequential_Github_Multiple_Repos() { diff --git a/src/OctoshiftCLI.Tests/gei/Commands/GenerateScript/GenerateScriptCommandTests.cs b/src/OctoshiftCLI.Tests/gei/Commands/GenerateScript/GenerateScriptCommandTests.cs index b7b1f9173..a24ff8de7 100644 --- a/src/OctoshiftCLI.Tests/gei/Commands/GenerateScript/GenerateScriptCommandTests.cs +++ b/src/OctoshiftCLI.Tests/gei/Commands/GenerateScript/GenerateScriptCommandTests.cs @@ -39,7 +39,7 @@ public void Should_Have_Options() var command = new GenerateScriptCommand(); command.Should().NotBeNull(); command.Name.Should().Be("generate-script"); - command.Options.Count.Should().Be(14); + command.Options.Count.Should().Be(15); TestHelpers.VerifyCommandOption(command.Options, "github-source-org", true); TestHelpers.VerifyCommandOption(command.Options, "github-target-org", true); @@ -55,6 +55,7 @@ public void Should_Have_Options() TestHelpers.VerifyCommandOption(command.Options, "aws-bucket-name", false); TestHelpers.VerifyCommandOption(command.Options, "aws-region", false); TestHelpers.VerifyCommandOption(command.Options, "keep-archive", false); + TestHelpers.VerifyCommandOption(command.Options, "target-api-url", false); } [Fact] diff --git a/src/ado2gh/Commands/GenerateScript/GenerateScriptCommand.cs b/src/ado2gh/Commands/GenerateScript/GenerateScriptCommand.cs index d2ebc7dca..9047ffd2f 100644 --- a/src/ado2gh/Commands/GenerateScript/GenerateScriptCommand.cs +++ b/src/ado2gh/Commands/GenerateScript/GenerateScriptCommand.cs @@ -18,6 +18,7 @@ public GenerateScriptCommand() : base( "Note: Expects ADO_PAT env variable or --ado-pat option to be set.") { AddOption(GithubOrg); + AddOption(TargetApiUrl); AddOption(AdoOrg); AddOption(AdoTeamProject); AddOption(AdoServerUrl); @@ -91,6 +92,10 @@ public GenerateScriptCommand() : base( { Description = "Path to a csv file that contains a list of repos to generate a script for. The CSV file should be generated using the inventory-report command." }; + public Option TargetApiUrl { get; } = new("--target-api-url") + { + Description = "The URL of the target API, if not migrating to github.com. Defaults to https://api.github.com" + }; public override GenerateScriptCommandHandler BuildHandler(GenerateScriptCommandArgs args, IServiceProvider sp) { diff --git a/src/ado2gh/Commands/GenerateScript/GenerateScriptCommandArgs.cs b/src/ado2gh/Commands/GenerateScript/GenerateScriptCommandArgs.cs index 94e615d08..67581356a 100644 --- a/src/ado2gh/Commands/GenerateScript/GenerateScriptCommandArgs.cs +++ b/src/ado2gh/Commands/GenerateScript/GenerateScriptCommandArgs.cs @@ -22,5 +22,6 @@ public class GenerateScriptCommandArgs : CommandArgs public bool RewirePipelines { get; set; } public bool All { get; set; } public FileInfo RepoList { get; set; } + public string TargetApiUrl { get; set; } } } diff --git a/src/ado2gh/Commands/GenerateScript/GenerateScriptCommandHandler.cs b/src/ado2gh/Commands/GenerateScript/GenerateScriptCommandHandler.cs index dee9ebee4..87ba7f063 100644 --- a/src/ado2gh/Commands/GenerateScript/GenerateScriptCommandHandler.cs +++ b/src/ado2gh/Commands/GenerateScript/GenerateScriptCommandHandler.cs @@ -67,8 +67,8 @@ public async Task Handle(GenerateScriptCommandArgs args) var appIds = _generateScriptOptions.RewirePipelines ? await GetAppIds(_adoApi, args.GithubOrg) : new Dictionary(); var script = args.Sequential - ? await GenerateSequentialScript(appIds, args.GithubOrg, args.AdoServerUrl) - : await GenerateParallelScript(appIds, args.GithubOrg, args.AdoServerUrl); + ? await GenerateSequentialScript(appIds, args.GithubOrg, args.AdoServerUrl, args.TargetApiUrl) + : await GenerateParallelScript(appIds, args.GithubOrg, args.AdoServerUrl, args.TargetApiUrl); _adoInspectorService.OutputRepoListToLog(); @@ -125,7 +125,7 @@ private async Task CheckForDuplicateRepoNames() private string GetRepoMigrationKey(string adoOrg, string githubRepoName) => $"{adoOrg}/{githubRepoName}"; - private async Task GenerateSequentialScript(IDictionary appIds, string githubOrg, string adoServerUrl) + private async Task GenerateSequentialScript(IDictionary appIds, string githubOrg, string adoServerUrl, string targetApiUrl) { var content = new StringBuilder(); @@ -157,8 +157,8 @@ private async Task GenerateSequentialScript(IDictionary continue; } - AppendLine(content, Exec(CreateGithubMaintainersTeamScript(adoTeamProject, githubOrg, _generateScriptOptions.LinkIdpGroups))); - AppendLine(content, Exec(CreateGithubAdminsTeamScript(adoTeamProject, githubOrg, _generateScriptOptions.LinkIdpGroups))); + AppendLine(content, Exec(CreateGithubMaintainersTeamScript(adoTeamProject, githubOrg, _generateScriptOptions.LinkIdpGroups, targetApiUrl))); + AppendLine(content, Exec(CreateGithubAdminsTeamScript(adoTeamProject, githubOrg, _generateScriptOptions.LinkIdpGroups, targetApiUrl))); AppendLine(content, Exec(ShareServiceConnectionScript(adoOrg, adoTeamProject, appId))); foreach (var adoRepo in await _adoInspectorService.GetRepos(adoOrg, adoTeamProject)) @@ -167,13 +167,13 @@ private async Task GenerateSequentialScript(IDictionary AppendLine(content); AppendLine(content, Exec(LockAdoRepoScript(adoOrg, adoTeamProject, adoRepo.Name))); - AppendLine(content, Exec(MigrateRepoScript(adoOrg, adoTeamProject, adoRepo.Name, githubOrg, githubRepo, true, adoServerUrl))); + AppendLine(content, Exec(MigrateRepoScript(adoOrg, adoTeamProject, adoRepo.Name, githubOrg, githubRepo, true, adoServerUrl, targetApiUrl))); AppendLine(content, Exec(DisableAdoRepoScript(adoOrg, adoTeamProject, adoRepo.Name))); AppendLine(content, Exec(ConfigureAutolinkScript(githubOrg, githubRepo, adoOrg, adoTeamProject))); AppendLine(content, Exec(AddMaintainersToGithubRepoScript(adoTeamProject, githubOrg, githubRepo))); AppendLine(content, Exec(AddAdminsToGithubRepoScript(adoTeamProject, githubOrg, githubRepo))); AppendLine(content, Exec(BoardsIntegrationScript(adoOrg, adoTeamProject, githubOrg, githubRepo))); - AppendLine(content, Exec(DownloadMigrationLogScript(githubOrg, githubRepo))); + AppendLine(content, Exec(DownloadMigrationLogScript(githubOrg, githubRepo, targetApiUrl))); foreach (var adoPipeline in await _adoInspectorService.GetPipelines(adoOrg, adoTeamProject, adoRepo.Name)) { @@ -189,7 +189,7 @@ private async Task GenerateSequentialScript(IDictionary return content.ToString(); } - private async Task GenerateParallelScript(IDictionary appIds, string githubOrg, string adoServerUrl) + private async Task GenerateParallelScript(IDictionary appIds, string githubOrg, string adoServerUrl, string targetApiUrl) { var content = new StringBuilder(); AppendLine(content, PWSH_SHEBANG); @@ -230,8 +230,8 @@ private async Task GenerateParallelScript(IDictionary ap continue; } - AppendLine(content, Exec(CreateGithubMaintainersTeamScript(adoTeamProject, githubOrg, _generateScriptOptions.LinkIdpGroups))); - AppendLine(content, Exec(CreateGithubAdminsTeamScript(adoTeamProject, githubOrg, _generateScriptOptions.LinkIdpGroups))); + AppendLine(content, Exec(CreateGithubMaintainersTeamScript(adoTeamProject, githubOrg, _generateScriptOptions.LinkIdpGroups, targetApiUrl))); + AppendLine(content, Exec(CreateGithubAdminsTeamScript(adoTeamProject, githubOrg, _generateScriptOptions.LinkIdpGroups, targetApiUrl))); AppendLine(content, Exec(ShareServiceConnectionScript(adoOrg, adoTeamProject, appId))); // queue up repo migration for each ADO repo @@ -242,7 +242,7 @@ private async Task GenerateParallelScript(IDictionary ap AppendLine(content); AppendLine(content, Exec(LockAdoRepoScript(adoOrg, adoTeamProject, adoRepo.Name))); - AppendLine(content, QueueMigrateRepoScript(adoOrg, adoTeamProject, adoRepo.Name, githubOrg, githubRepo, adoServerUrl)); + AppendLine(content, QueueMigrateRepoScript(adoOrg, adoTeamProject, adoRepo.Name, githubOrg, githubRepo, adoServerUrl, targetApiUrl)); AppendLine(content, $"$RepoMigrations[\"{GetRepoMigrationKey(adoOrg, githubRepo)}\"] = $MigrationID"); } } @@ -266,7 +266,7 @@ private async Task GenerateParallelScript(IDictionary ap AppendLine(content, "$CanExecuteBatch = $false"); AppendLine(content, $"if ($null -ne $RepoMigrations[\"{repoMigrationKey}\"]) {{"); - AppendLine(content, " " + WaitForMigrationScript(repoMigrationKey)); + AppendLine(content, " " + WaitForMigrationScript(repoMigrationKey, targetApiUrl)); AppendLine(content, " $CanExecuteBatch = ($lastexitcode -eq 0)"); AppendLine(content, "}"); @@ -285,7 +285,7 @@ private async Task GenerateParallelScript(IDictionary ap AppendLine(content, " " + Wrap(AddMaintainersToGithubRepoScript(adoTeamProject, githubOrg, githubRepo))); AppendLine(content, " " + Wrap(AddAdminsToGithubRepoScript(adoTeamProject, githubOrg, githubRepo))); AppendLine(content, " " + Wrap(BoardsIntegrationScript(adoOrg, adoTeamProject, githubOrg, githubRepo))); - AppendLine(content, " " + Wrap(DownloadMigrationLogScript(githubOrg, githubRepo))); + AppendLine(content, " " + Wrap(DownloadMigrationLogScript(githubOrg, githubRepo, targetApiUrl))); appIds.TryGetValue(adoOrg, out var appId); foreach (var adoPipeline in await _adoInspectorService.GetPipelines(adoOrg, adoTeamProject, adoRepo.Name)) @@ -357,20 +357,20 @@ private string ConfigureAutolinkScript(string githubOrg, string githubRepo, stri ? $"gh ado2gh configure-autolink --github-org \"{githubOrg}\" --github-repo \"{githubRepo}\" --ado-org \"{adoOrg}\" --ado-team-project \"{adoTeamProject}\"{(_log.Verbose ? " --verbose" : string.Empty)}" : null; - private string MigrateRepoScript(string adoOrg, string adoTeamProject, string adoRepo, string githubOrg, string githubRepo, bool wait, string adoServerUrl) => - $"gh ado2gh migrate-repo --ado-org \"{adoOrg}\" --ado-team-project \"{adoTeamProject}\" --ado-repo \"{adoRepo}\" --github-org \"{githubOrg}\" --github-repo \"{githubRepo}\"{(_log.Verbose ? " --verbose" : string.Empty)}{(wait ? string.Empty : " --queue-only")} --target-repo-visibility private{(adoServerUrl.IsNullOrWhiteSpace() ? string.Empty : $" --ado-server-url \"{adoServerUrl}\"")}"; + private string MigrateRepoScript(string adoOrg, string adoTeamProject, string adoRepo, string githubOrg, string githubRepo, bool wait, string adoServerUrl, string targetApiUrl) => + $"gh ado2gh migrate-repo{(targetApiUrl.HasValue() ? $" --target-api-url \"{targetApiUrl}\"" : string.Empty)} --ado-org \"{adoOrg}\" --ado-team-project \"{adoTeamProject}\" --ado-repo \"{adoRepo}\" --github-org \"{githubOrg}\" --github-repo \"{githubRepo}\"{(_log.Verbose ? " --verbose" : string.Empty)}{(wait ? string.Empty : " --queue-only")} --target-repo-visibility private{(adoServerUrl.HasValue() ? $" --ado-server-url \"{adoServerUrl}\"" : string.Empty)}"; - private string QueueMigrateRepoScript(string adoOrg, string adoTeamProject, string adoRepo, string githubOrg, string githubRepo, string adoServerUrl) => - $"$MigrationID = {ExecAndGetMigrationId(MigrateRepoScript(adoOrg, adoTeamProject, adoRepo, githubOrg, githubRepo, false, adoServerUrl))}"; + private string QueueMigrateRepoScript(string adoOrg, string adoTeamProject, string adoRepo, string githubOrg, string githubRepo, string adoServerUrl, string targetApiUrl) => + $"$MigrationID = {ExecAndGetMigrationId(MigrateRepoScript(adoOrg, adoTeamProject, adoRepo, githubOrg, githubRepo, false, adoServerUrl, targetApiUrl))}"; - private string CreateGithubMaintainersTeamScript(string adoTeamProject, string githubOrg, bool linkIdpGroups) => + private string CreateGithubMaintainersTeamScript(string adoTeamProject, string githubOrg, bool linkIdpGroups, string targetApiUrl) => _generateScriptOptions.CreateTeams - ? $"gh ado2gh create-team --github-org \"{githubOrg}\" --team-name \"{adoTeamProject.ReplaceInvalidCharactersWithDash()}-Maintainers\"{(_log.Verbose ? " --verbose" : string.Empty)}{(linkIdpGroups ? $" --idp-group \"{adoTeamProject.ReplaceInvalidCharactersWithDash()}-Maintainers\"" : string.Empty)}" + ? $"gh ado2gh create-team{(targetApiUrl.HasValue() ? $" --target-api-url \"{targetApiUrl}\"" : string.Empty)} --github-org \"{githubOrg}\" --team-name \"{adoTeamProject.ReplaceInvalidCharactersWithDash()}-Maintainers\"{(_log.Verbose ? " --verbose" : string.Empty)}{(linkIdpGroups ? $" --idp-group \"{adoTeamProject.ReplaceInvalidCharactersWithDash()}-Maintainers\"" : string.Empty)}" : null; - private string CreateGithubAdminsTeamScript(string adoTeamProject, string githubOrg, bool linkIdpGroups) => + private string CreateGithubAdminsTeamScript(string adoTeamProject, string githubOrg, bool linkIdpGroups, string targetApiUrl) => _generateScriptOptions.CreateTeams - ? $"gh ado2gh create-team --github-org \"{githubOrg}\" --team-name \"{adoTeamProject.ReplaceInvalidCharactersWithDash()}-Admins\"{(_log.Verbose ? " --verbose" : string.Empty)}{(linkIdpGroups ? $" --idp-group \"{adoTeamProject.ReplaceInvalidCharactersWithDash()}-Admins\"" : string.Empty)}" + ? $"gh ado2gh create-team{(targetApiUrl.HasValue() ? $" --target-api-url \"{targetApiUrl}\"" : string.Empty)} --github-org \"{githubOrg}\" --team-name \"{adoTeamProject.ReplaceInvalidCharactersWithDash()}-Admins\"{(_log.Verbose ? " --verbose" : string.Empty)}{(linkIdpGroups ? $" --idp-group \"{adoTeamProject.ReplaceInvalidCharactersWithDash()}-Admins\"" : string.Empty)}" : null; private string AddMaintainersToGithubRepoScript(string adoTeamProject, string githubOrg, string githubRepo) => @@ -393,11 +393,11 @@ private string BoardsIntegrationScript(string adoOrg, string adoTeamProject, str ? $"gh ado2gh integrate-boards --ado-org \"{adoOrg}\" --ado-team-project \"{adoTeamProject}\" --github-org \"{githubOrg}\" --github-repo \"{githubRepo}\"{(_log.Verbose ? " --verbose" : string.Empty)}" : null; - private string WaitForMigrationScript(string repoMigrationKey) => $"gh ado2gh wait-for-migration --migration-id $RepoMigrations[\"{repoMigrationKey}\"]"; + private string WaitForMigrationScript(string repoMigrationKey, string targetApiUrl) => $"gh ado2gh wait-for-migration{(targetApiUrl.HasValue() ? $" --target-api-url \"{targetApiUrl}\"" : string.Empty)} --migration-id $RepoMigrations[\"{repoMigrationKey}\"]"; - private string DownloadMigrationLogScript(string githubOrg, string githubRepo) => + private string DownloadMigrationLogScript(string githubOrg, string githubRepo, string targetApiUrl) => _generateScriptOptions.DownloadMigrationLogs - ? $"gh ado2gh download-logs --github-org \"{githubOrg}\" --github-repo \"{githubRepo}\"" + ? $"gh ado2gh download-logs{(targetApiUrl.HasValue() ? $" --target-api-url \"{targetApiUrl}\"" : string.Empty)} --github-org \"{githubOrg}\" --github-repo \"{githubRepo}\"" : null; private string Exec(string script) => Wrap(script, "Exec"); diff --git a/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommand.cs b/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommand.cs index 00861b1c6..b5016f6af 100644 --- a/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommand.cs +++ b/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommand.cs @@ -17,6 +17,7 @@ public GenerateScriptCommand() : base( { AddOption(BbsServerUrl); AddOption(GithubOrg); + AddOption(TargetApiUrl); AddOption(BbsUsername); AddOption(BbsPassword); AddOption(BbsProject); @@ -118,6 +119,10 @@ public GenerateScriptCommand() : base( name: "--no-ssl-verify", description: "Disables SSL verification when communicating with your Bitbucket Server/Data Center instance. All other migration steps will continue to verify SSL. " + "If your Bitbucket instance has a self-signed SSL certificate then setting this flag will allow the migration archive to be exported."); + public Option TargetApiUrl { get; } = new("--target-api-url") + { + Description = "The URL of the target API, if not migrating to github.com. Defaults to https://api.github.com" + }; public override GenerateScriptCommandHandler BuildHandler(GenerateScriptCommandArgs args, IServiceProvider sp) { diff --git a/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommandArgs.cs b/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommandArgs.cs index caefad809..2e2c6c93f 100644 --- a/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommandArgs.cs +++ b/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommandArgs.cs @@ -26,6 +26,7 @@ public class GenerateScriptCommandArgs : CommandArgs public string AwsRegion { get; set; } public bool KeepArchive { get; set; } public bool NoSslVerify { get; set; } + public string TargetApiUrl { get; set; } public override void Validate(OctoLogger log) { diff --git a/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommandHandler.cs b/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommandHandler.cs index d0c5232dc..68be3956b 100644 --- a/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommandHandler.cs +++ b/src/bbs2gh/Commands/GenerateScript/GenerateScriptCommandHandler.cs @@ -134,8 +134,9 @@ private string MigrateGithubRepoScript(GenerateScriptCommandArgs args, string bb var keepArchive = args.KeepArchive ? " --keep-archive" : ""; var noSslVerify = args.NoSslVerify ? " --no-ssl-verify" : ""; var targetRepoVisibility = " --target-repo-visibility private"; + var targetApiUrlOption = args.TargetApiUrl.HasValue() ? $" --target-api-url \"{args.TargetApiUrl}\"" : ""; - return $"gh bbs2gh migrate-repo{bbsServerUrlOption}{bbsUsernameOption}{bbsSharedHomeOption}{bbsProjectOption}{bbsRepoOption}{sshArchiveDownloadOptions}" + + return $"gh bbs2gh migrate-repo{targetApiUrlOption}{bbsServerUrlOption}{bbsUsernameOption}{bbsSharedHomeOption}{bbsProjectOption}{bbsRepoOption}{sshArchiveDownloadOptions}" + $"{smbArchiveDownloadOptions}{githubOrgOption}{githubRepoOption}{verboseOption}{waitOption}{kerberosOption}{awsBucketNameOption}{awsRegionOption}{keepArchive}{noSslVerify}{targetRepoVisibility}"; } diff --git a/src/gei/Commands/GenerateScript/GenerateScriptCommand.cs b/src/gei/Commands/GenerateScript/GenerateScriptCommand.cs index 265bf622a..2e20eae87 100644 --- a/src/gei/Commands/GenerateScript/GenerateScriptCommand.cs +++ b/src/gei/Commands/GenerateScript/GenerateScriptCommand.cs @@ -21,6 +21,7 @@ public GenerateScriptCommand() : base( AddOption(GithubSourceOrg); AddOption(GithubTargetOrg); + AddOption(TargetApiUrl); AddOption(GhesApiUrl); AddOption(AwsBucketName); AddOption(AwsRegion); @@ -94,6 +95,10 @@ public GenerateScriptCommand() : base( { Description = "Keeps the archive on this machine after uploading to the blob storage account. Only applicable for migrations from GitHub Enterprise Server versions before 3.8.0." }; + public Option TargetApiUrl { get; } = new("--target-api-url") + { + Description = "The URL of the target API, if not migrating to github.com. Defaults to https://api.github.com" + }; public override GenerateScriptCommandHandler BuildHandler(GenerateScriptCommandArgs args, IServiceProvider sp) { diff --git a/src/gei/Commands/GenerateScript/GenerateScriptCommandArgs.cs b/src/gei/Commands/GenerateScript/GenerateScriptCommandArgs.cs index 378b4d265..d1f64956a 100644 --- a/src/gei/Commands/GenerateScript/GenerateScriptCommandArgs.cs +++ b/src/gei/Commands/GenerateScript/GenerateScriptCommandArgs.cs @@ -22,6 +22,7 @@ public class GenerateScriptCommandArgs : CommandArgs [Secret] public string GithubSourcePat { get; set; } public bool KeepArchive { get; set; } + public string TargetApiUrl { get; set; } public override void Validate(OctoLogger log) { diff --git a/src/gei/Commands/GenerateScript/GenerateScriptCommandHandler.cs b/src/gei/Commands/GenerateScript/GenerateScriptCommandHandler.cs index 5e7020253..39f8b7aee 100644 --- a/src/gei/Commands/GenerateScript/GenerateScriptCommandHandler.cs +++ b/src/gei/Commands/GenerateScript/GenerateScriptCommandHandler.cs @@ -44,7 +44,7 @@ public async Task Handle(GenerateScriptCommandArgs args) _log.LogInformation("Generating Script..."); - var script = await GenerateScript(args.GithubSourceOrg, args.GithubTargetOrg, args.GhesApiUrl, args.AwsBucketName, args.AwsRegion, args.NoSslVerify, args.Sequential, args.SkipReleases, args.LockSourceRepo, args.DownloadMigrationLogs, args.KeepArchive); + var script = await GenerateScript(args.GithubSourceOrg, args.GithubTargetOrg, args.GhesApiUrl, args.AwsBucketName, args.AwsRegion, args.NoSslVerify, args.Sequential, args.SkipReleases, args.LockSourceRepo, args.DownloadMigrationLogs, args.KeepArchive, args.TargetApiUrl); if (script.HasValue() && args.Output.HasValue()) { @@ -52,7 +52,7 @@ public async Task Handle(GenerateScriptCommandArgs args) } } - private async Task GenerateScript(string githubSourceOrg, string githubTargetOrg, string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool sequential, bool skipReleases, bool lockSourceRepo, bool downloadMigrationLogs, bool keepArchive) + private async Task GenerateScript(string githubSourceOrg, string githubTargetOrg, string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool sequential, bool skipReleases, bool lockSourceRepo, bool downloadMigrationLogs, bool keepArchive, string targetApiUrl) { var repos = await GetGithubRepos(_sourceGithubApi, githubSourceOrg); if (!repos.Any()) @@ -62,8 +62,8 @@ private async Task GenerateScript(string githubSourceOrg, string githubT } return sequential - ? await GenerateSequentialGithubScript(repos, githubSourceOrg, githubTargetOrg, ghesApiUrl, awsBucketName, awsRegion, noSslVerify, skipReleases, lockSourceRepo, downloadMigrationLogs, keepArchive) - : await GenerateParallelGithubScript(repos, githubSourceOrg, githubTargetOrg, ghesApiUrl, awsBucketName, awsRegion, noSslVerify, skipReleases, lockSourceRepo, downloadMigrationLogs, keepArchive); + ? await GenerateSequentialGithubScript(repos, githubSourceOrg, githubTargetOrg, ghesApiUrl, awsBucketName, awsRegion, noSslVerify, skipReleases, lockSourceRepo, downloadMigrationLogs, keepArchive, targetApiUrl) + : await GenerateParallelGithubScript(repos, githubSourceOrg, githubTargetOrg, ghesApiUrl, awsBucketName, awsRegion, noSslVerify, skipReleases, lockSourceRepo, downloadMigrationLogs, keepArchive, targetApiUrl); } private async Task> GetGithubRepos(GithubApi github, string githubOrg) @@ -83,7 +83,7 @@ private async Task GenerateScript(string githubSourceOrg, string githubT return repos; } - private async Task GenerateSequentialGithubScript(IEnumerable<(string Name, string Visibility)> repos, string githubSourceOrg, string githubTargetOrg, string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool skipReleases, bool lockSourceRepo, bool downloadMigrationLogs, bool keepArchive) + private async Task GenerateSequentialGithubScript(IEnumerable<(string Name, string Visibility)> repos, string githubSourceOrg, string githubTargetOrg, string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool skipReleases, bool lockSourceRepo, bool downloadMigrationLogs, bool keepArchive, string targetApiUrl) { var content = new StringBuilder(); @@ -110,18 +110,18 @@ private async Task GenerateSequentialGithubScript(IEnumerable<(string Na foreach (var (name, visibility) in repos) { - content.AppendLine(Exec(MigrateGithubRepoScript(githubSourceOrg, githubTargetOrg, name, ghesApiUrl, awsBucketName, awsRegion, noSslVerify, true, skipReleases, lockSourceRepo, keepArchive, visibility))); + content.AppendLine(Exec(MigrateGithubRepoScript(githubSourceOrg, githubTargetOrg, name, ghesApiUrl, awsBucketName, awsRegion, noSslVerify, true, skipReleases, lockSourceRepo, keepArchive, visibility, targetApiUrl))); if (downloadMigrationLogs) { - content.AppendLine(Exec(DownloadMigrationLogScript(githubTargetOrg, name))); + content.AppendLine(Exec(DownloadMigrationLogScript(githubTargetOrg, name, targetApiUrl))); } } return content.ToString(); } - private async Task GenerateParallelGithubScript(IEnumerable<(string Name, string Visibility)> repos, string githubSourceOrg, string githubTargetOrg, string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool skipReleases, bool lockSourceRepo, bool downloadMigrationLogs, bool keepArchive) + private async Task GenerateParallelGithubScript(IEnumerable<(string Name, string Visibility)> repos, string githubSourceOrg, string githubTargetOrg, string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool skipReleases, bool lockSourceRepo, bool downloadMigrationLogs, bool keepArchive, string targetApiUrl) { var content = new StringBuilder(); @@ -158,7 +158,7 @@ private async Task GenerateParallelGithubScript(IEnumerable<(string Name // Queuing migrations foreach (var (name, visibility) in repos) { - content.AppendLine($"$MigrationID = {ExecAndGetMigrationId(MigrateGithubRepoScript(githubSourceOrg, githubTargetOrg, name, ghesApiUrl, awsBucketName, awsRegion, noSslVerify, false, skipReleases, lockSourceRepo, keepArchive, visibility))}"); + content.AppendLine($"$MigrationID = {ExecAndGetMigrationId(MigrateGithubRepoScript(githubSourceOrg, githubTargetOrg, name, ghesApiUrl, awsBucketName, awsRegion, noSslVerify, false, skipReleases, lockSourceRepo, keepArchive, visibility, targetApiUrl))}"); content.AppendLine($"$RepoMigrations[\"{name}\"] = $MigrationID"); content.AppendLine(); } @@ -171,12 +171,12 @@ private async Task GenerateParallelGithubScript(IEnumerable<(string Name // Query each migration's status foreach (var (name, _) in repos) { - content.AppendLine(Wrap(WaitForMigrationScript(name), $"if ($RepoMigrations[\"{name}\"])")); + content.AppendLine(Wrap(WaitForMigrationScript(targetApiUrl, name), $"if ($RepoMigrations[\"{name}\"])")); content.AppendLine($"if ($RepoMigrations[\"{name}\"] -and $lastexitcode -eq 0) {{ $Succeeded++ }} else {{ $Failed++ }}"); if (downloadMigrationLogs) { - content.AppendLine(DownloadMigrationLogScript(githubTargetOrg, name)); + content.AppendLine(DownloadMigrationLogScript(githubTargetOrg, name, targetApiUrl)); } content.AppendLine(); @@ -199,11 +199,11 @@ exit 1 return content.ToString(); } - private string MigrateGithubRepoScript(string githubSourceOrg, string githubTargetOrg, string repo, string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool wait, bool skipReleases, bool lockSourceRepo, bool keepArchive, string repoVisibility) + private string MigrateGithubRepoScript(string githubSourceOrg, string githubTargetOrg, string repo, string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool wait, bool skipReleases, bool lockSourceRepo, bool keepArchive, string repoVisibility, string targetApiUrl) { var ghesRepoOptions = ghesApiUrl.HasValue() ? GetGhesRepoOptions(ghesApiUrl, awsBucketName, awsRegion, noSslVerify, keepArchive) : null; - return $"gh gei migrate-repo --github-source-org \"{githubSourceOrg}\" --source-repo \"{repo}\" --github-target-org \"{githubTargetOrg}\" --target-repo \"{repo}\"{(!string.IsNullOrEmpty(ghesRepoOptions) ? $" {ghesRepoOptions}" : string.Empty)}{(_log.Verbose ? " --verbose" : string.Empty)}{(wait ? string.Empty : " --queue-only")}{(skipReleases ? " --skip-releases" : string.Empty)}{(lockSourceRepo ? " --lock-source-repo" : string.Empty)} --target-repo-visibility {repoVisibility}"; + return $"gh gei migrate-repo{(targetApiUrl.HasValue() ? $" --target-api-url \"{targetApiUrl}\"" : string.Empty)} --github-source-org \"{githubSourceOrg}\" --source-repo \"{repo}\" --github-target-org \"{githubTargetOrg}\" --target-repo \"{repo}\"{(!string.IsNullOrEmpty(ghesRepoOptions) ? $" {ghesRepoOptions}" : string.Empty)}{(_log.Verbose ? " --verbose" : string.Empty)}{(wait ? string.Empty : " --queue-only")}{(skipReleases ? " --skip-releases" : string.Empty)}{(lockSourceRepo ? " --lock-source-repo" : string.Empty)} --target-repo-visibility {repoVisibility}"; } private string GetGhesRepoOptions(string ghesApiUrl, string awsBucketName, string awsRegion, bool noSslVerify, bool keepArchive) @@ -211,11 +211,11 @@ private string GetGhesRepoOptions(string ghesApiUrl, string awsBucketName, strin return $"--ghes-api-url \"{ghesApiUrl}\"{(awsBucketName.HasValue() ? $" --aws-bucket-name \"{awsBucketName}\"" : "")}{(awsRegion.HasValue() ? $" --aws-region \"{awsRegion}\"" : "")}{(noSslVerify ? " --no-ssl-verify" : string.Empty)}{(keepArchive ? " --keep-archive" : string.Empty)}"; } - private string WaitForMigrationScript(string repoMigrationKey = null) => $"gh gei wait-for-migration --migration-id $RepoMigrations[\"{repoMigrationKey}\"]"; + private string WaitForMigrationScript(string targetApiUrl, string repoMigrationKey = null) => $"gh gei wait-for-migration{(targetApiUrl.HasValue() ? $" --target-api-url \"{targetApiUrl}\"" : string.Empty)} --migration-id $RepoMigrations[\"{repoMigrationKey}\"]"; - private string DownloadMigrationLogScript(string githubTargetOrg, string targetRepo) + private string DownloadMigrationLogScript(string githubTargetOrg, string targetRepo, string targetApiUrl) { - return $"gh gei download-logs --github-target-org \"{githubTargetOrg}\" --target-repo \"{targetRepo}\""; + return $"gh gei download-logs{(targetApiUrl.HasValue() ? $" --target-api-url \"{targetApiUrl}\"" : string.Empty)} --github-target-org \"{githubTargetOrg}\" --target-repo \"{targetRepo}\""; } private string Exec(string script) => Wrap(script, "Exec");