diff --git a/src/Runner.Common/RunnerDotcomServer.cs b/src/Runner.Common/RunnerDotcomServer.cs index b607024d4..c021e8bcc 100644 --- a/src/Runner.Common/RunnerDotcomServer.cs +++ b/src/Runner.Common/RunnerDotcomServer.cs @@ -19,6 +19,7 @@ namespace GitHub.Runner.Common Task AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey); Task ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey); + Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId); Task> GetRunnerGroupsAsync(string githubUrl, string githubToken); } @@ -43,117 +44,15 @@ namespace GitHub.Runner.Common public async Task> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName) { - var githubApiUrl = ""; - var gitHubUrlBuilder = new UriBuilder(githubUrl); - var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries); - var isOrgRunner = path.Length == 1; - var isRepoOrEnterpriseRunner = path.Length == 2; - var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase); - - if (isOrgRunner) - { - // org runner - if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}"; - } - else - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runners?name={Uri.EscapeDataString(agentName)}"; - } - } - else if (isRepoOrEnterpriseRunner) - { - // Repository runner - if (isRepoRunner) - { - if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}"; - } - else - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}"; - } - } - else - { - // Enterprise runner - if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}"; - } - else - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runners?name={Uri.EscapeDataString(agentName)}"; - } - } - } - else - { - throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise."); - } - + var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners?name={Uri.EscapeDataString(agentName)}"; var runnersList = await RetryRequest(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools"); - return runnersList.ToTaskAgents(); } public async Task> GetRunnerGroupsAsync(string githubUrl, string githubToken) { - var githubApiUrl = ""; - var gitHubUrlBuilder = new UriBuilder(githubUrl); - var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries); - var isOrgRunner = path.Length == 1; - var isRepoOrEnterpriseRunner = path.Length == 2; - var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase); - - if (isOrgRunner) - { - // org runner - if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions/runner-groups"; - } - else - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions/runner-groups"; - } - } - else if (isRepoOrEnterpriseRunner) - { - // Repository Runner - if (isRepoRunner) - { - if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions/runner-groups"; - } - else - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions/runner-groups"; - } - } - else - { - // Enterprise Runner - if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions/runner-groups"; - } - else - { - githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions/runner-groups"; - } - } - } - else - { - throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise."); - } - + var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runner-groups"; var agentPools = await RetryRequest(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools"); - return agentPools?.ToAgentPoolList(); } @@ -204,6 +103,12 @@ namespace GitHub.Runner.Common return await RetryRequest(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body); } + public async Task DeleteRunnerAsync(string githubUrl, string githubToken, ulong runnerId) + { + var githubApiUrl = $"{GetEntityUrl(githubUrl)}/runners/{runnerId}"; + await RetryRequest(githubApiUrl, githubToken, RequestType.Delete, 3, "Failed to delete agent"); + } + private async Task RetryRequest(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null) { int retry = 0; @@ -220,13 +125,22 @@ namespace GitHub.Runner.Common try { HttpResponseMessage response = null; - if (requestType == RequestType.Get) + switch (requestType) { - response = await httpClient.GetAsync(githubApiUrl); - } - else - { - response = await httpClient.PostAsync(githubApiUrl, body); + case RequestType.Get: + response = await httpClient.GetAsync(githubApiUrl); + break; + case RequestType.Post: + response = await httpClient.PostAsync(githubApiUrl, body); + break; + case RequestType.Patch: + response = await httpClient.PatchAsync(githubApiUrl, body); + break; + case RequestType.Delete: + response = await httpClient.DeleteAsync(githubApiUrl); + break; + default: + throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null); } if (response != null) @@ -261,5 +175,61 @@ namespace GitHub.Runner.Common await Task.Delay(backOff); } } + + private string GetEntityUrl(string githubUrl) + { + var githubApiUrl = ""; + var gitHubUrlBuilder = new UriBuilder(githubUrl); + var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries); + var isOrgRunner = path.Length == 1; + var isRepoOrEnterpriseRunner = path.Length == 2; + var isRepoRunner = isRepoOrEnterpriseRunner && !string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase); + + if (isOrgRunner) + { + // org runner + if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/orgs/{path[0]}/actions"; + } + else + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/orgs/{path[0]}/actions"; + } + } + else if (isRepoOrEnterpriseRunner) + { + // Repository Runner + if (isRepoRunner) + { + if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/repos/{path[0]}/{path[1]}/actions"; + } + else + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/repos/{path[0]}/{path[1]}/actions"; + } + } + else + { + // Enterprise Runner + if (UrlUtil.IsHostedServer(gitHubUrlBuilder)) + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/{path[0]}/{path[1]}/actions"; + } + else + { + githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/{path[0]}/{path[1]}/actions"; + } + } + } + else + { + throw new ArgumentException($"'{githubUrl}' should point to an org or enterprise."); + } + + return githubApiUrl; + } } } diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index 474e6a360..c3da7f8e5 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -537,41 +537,50 @@ namespace GitHub.Runner.Listener.Configuration if (isConfigured && hasCredentials) { RunnerSettings settings = _store.GetSettings(); - var credentialManager = HostContext.GetService(); - // Get the credentials - VssCredentials creds = null; - if (string.IsNullOrEmpty(settings.GitHubUrl)) - { - var credProvider = GetCredentialProvider(command, settings.ServerUrl); - creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false); - Trace.Info("legacy vss cred retrieved"); - } - else + if (settings.UseV2Flow) { var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove"); - GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove); - creds = authResult.ToVssCredentials(); - Trace.Info("cred retrieved via GitHub auth"); - } - - // Determine the service deployment type based on connection data. (Hosted/OnPremises) - await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds); - - var agents = await _runnerServer.GetAgentsAsync(settings.AgentName); - Trace.Verbose("Returns {0} agents", agents.Count); - TaskAgent agent = agents.FirstOrDefault(); - if (agent == null) - { - _term.WriteLine("Does not exist. Skipping " + currentAction); + await _dotcomServer.DeleteRunnerAsync(settings.GitHubUrl, deletionToken, settings.AgentId); } else { - await _runnerServer.DeleteAgentAsync(settings.AgentId); + var credentialManager = HostContext.GetService(); - _term.WriteLine(); - _term.WriteSuccessMessage("Runner removed successfully"); + // Get the credentials + VssCredentials creds = null; + if (string.IsNullOrEmpty(settings.GitHubUrl)) + { + var credProvider = GetCredentialProvider(command, settings.ServerUrl); + creds = credProvider.GetVssCredentials(HostContext, allowAuthUrlV2: false); + Trace.Info("legacy vss cred retrieved"); + } + else + { + var deletionToken = await GetRunnerTokenAsync(command, settings.GitHubUrl, "remove"); + GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, deletionToken, Constants.RunnerEvent.Remove); + creds = authResult.ToVssCredentials(); + Trace.Info("cred retrieved via GitHub auth"); + } + + // Determine the service deployment type based on connection data. (Hosted/OnPremises) + await _runnerServer.ConnectAsync(new Uri(settings.ServerUrl), creds); + + var agents = await _runnerServer.GetAgentsAsync(settings.AgentName); + Trace.Verbose("Returns {0} agents", agents.Count); + TaskAgent agent = agents.FirstOrDefault(); + if (agent == null) + { + _term.WriteLine("Does not exist. Skipping " + currentAction); + } + else + { + await _runnerServer.DeleteAgentAsync(settings.AgentId); + } } + + _term.WriteLine(); + _term.WriteSuccessMessage("Runner removed successfully"); } else { diff --git a/src/Test/L0/Listener/RunnerL0.cs b/src/Test/L0/Listener/RunnerL0.cs index 6a4dce372..456f51cc9 100644 --- a/src/Test/L0/Listener/RunnerL0.cs +++ b/src/Test/L0/Listener/RunnerL0.cs @@ -978,7 +978,7 @@ namespace GitHub.Runner.Common.Tests.Listener _messageListener.Verify(x => x.GetNextMessageAsync(It.IsAny()), Times.AtLeast(2)); _messageListener.Verify(x => x.DeleteMessageAsync(It.IsAny()), Times.AtLeast(2)); _messageListener.Verify(x => x.DeleteSessionAsync(), Times.Once()); - _credentialManager.Verify(x => x.LoadCredentials(true), Times.Exactly(2)); + _credentialManager.Verify(x => x.LoadCredentials(true), Times.AtLeast(2)); Assert.False(hc.AllowAuthMigration); }