mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:21:58 +00:00
238 lines
10 KiB
C#
238 lines
10 KiB
C#
using GitHub.DistributedTask.WebApi;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using GitHub.Services.WebApi;
|
|
using GitHub.Services.Common;
|
|
using GitHub.Runner.Sdk;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Linq;
|
|
|
|
namespace GitHub.Runner.Common
|
|
{
|
|
[ServiceLocator(Default = typeof(RunnerDotcomServer))]
|
|
public interface IRunnerDotcomServer : IRunnerService
|
|
{
|
|
Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName);
|
|
|
|
Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
|
Task<DistributedTask.WebApi.Runner> ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
|
|
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);
|
|
}
|
|
|
|
public enum RequestType
|
|
{
|
|
Get,
|
|
Post,
|
|
Patch,
|
|
Delete
|
|
}
|
|
|
|
public class RunnerDotcomServer : RunnerService, IRunnerDotcomServer
|
|
{
|
|
private ITerminal _term;
|
|
|
|
public override void Initialize(IHostContext hostContext)
|
|
{
|
|
base.Initialize(hostContext);
|
|
_term = hostContext.GetService<ITerminal>();
|
|
}
|
|
|
|
|
|
public async Task<List<TaskAgent>> GetRunnerByNameAsync(string githubUrl, string githubToken, string agentName)
|
|
{
|
|
var githubApiUrl = "";
|
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
if (path.Length == 1)
|
|
{
|
|
// 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 (path.Length == 2)
|
|
{
|
|
// repo or enterprise runner.
|
|
if (!string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
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 runnersList = await RetryRequest<ListRunnersResponse>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
|
|
|
return runnersList.ToTaskAgents();
|
|
}
|
|
|
|
public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken)
|
|
{
|
|
var githubApiUrl = "";
|
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
if (path.Length == 1)
|
|
{
|
|
// 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 (path.Length == 2)
|
|
{
|
|
// repo or enterprise runner.
|
|
if (!string.Equals(path[0], "enterprises", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
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 agentPools = await RetryRequest<RunnerGroupList>(githubApiUrl, githubToken, RequestType.Get, 3, "Failed to get agents pools");
|
|
|
|
return agentPools?.ToAgentPoolList();
|
|
}
|
|
|
|
public async Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey)
|
|
{
|
|
return await AddOrReplaceRunner(runnerGroupId, agent, githubUrl, githubToken, publicKey, false);
|
|
}
|
|
|
|
public async Task<DistributedTask.WebApi.Runner> ReplaceRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey)
|
|
{
|
|
return await AddOrReplaceRunner(runnerGroupId, agent, githubUrl, githubToken, publicKey, true);
|
|
}
|
|
|
|
private async Task<DistributedTask.WebApi.Runner> AddOrReplaceRunner(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey, bool replace)
|
|
{
|
|
var gitHubUrlBuilder = new UriBuilder(githubUrl);
|
|
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
|
|
string githubApiUrl;
|
|
if (UrlUtil.IsHostedServer(gitHubUrlBuilder))
|
|
{
|
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://api.{gitHubUrlBuilder.Host}/actions/runners/register";
|
|
}
|
|
else
|
|
{
|
|
githubApiUrl = $"{gitHubUrlBuilder.Scheme}://{gitHubUrlBuilder.Host}/api/v3/actions/runners/register";
|
|
}
|
|
|
|
var bodyObject = new Dictionary<string, Object>()
|
|
{
|
|
{"url", githubUrl},
|
|
{"group_id", runnerGroupId},
|
|
{"name", agent.Name},
|
|
{"version", agent.Version},
|
|
{"updates_disabled", agent.DisableUpdate},
|
|
{"ephemeral", agent.Ephemeral},
|
|
{"labels", agent.Labels},
|
|
{"public_key", publicKey},
|
|
};
|
|
|
|
if (replace)
|
|
{
|
|
bodyObject.Add("runner_id", agent.Id);
|
|
bodyObject.Add("replace", replace);
|
|
}
|
|
|
|
var body = new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json");
|
|
|
|
return await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
|
|
}
|
|
|
|
private async Task<T> RetryRequest<T>(string githubApiUrl, string githubToken, RequestType requestType, int maxRetryAttemptsCount = 5, string errorMessage = null, StringContent body = null)
|
|
{
|
|
int retry = 0;
|
|
while (true)
|
|
{
|
|
retry++;
|
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
|
using (var httpClient = new HttpClient(httpClientHandler))
|
|
{
|
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("RemoteAuth", githubToken);
|
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
|
|
|
var responseStatus = System.Net.HttpStatusCode.OK;
|
|
try
|
|
{
|
|
HttpResponseMessage response = null;
|
|
if (requestType == RequestType.Get)
|
|
{
|
|
response = await httpClient.GetAsync(githubApiUrl);
|
|
}
|
|
else
|
|
{
|
|
response = await httpClient.PostAsync(githubApiUrl, body);
|
|
}
|
|
|
|
if (response != null)
|
|
{
|
|
responseStatus = response.StatusCode;
|
|
var githubRequestId = UrlUtil.GetGitHubRequestId(response.Headers);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
Trace.Info($"Http response code: {response.StatusCode} from '{requestType.ToString()} {githubApiUrl}' ({githubRequestId})");
|
|
var jsonResponse = await response.Content.ReadAsStringAsync();
|
|
return StringUtil.ConvertFromJson<T>(jsonResponse);
|
|
}
|
|
else
|
|
{
|
|
_term.WriteError($"Http response code: {response.StatusCode} from '{requestType.ToString()} {githubApiUrl}' (Request Id: {githubRequestId})");
|
|
var errorResponse = await response.Content.ReadAsStringAsync();
|
|
_term.WriteError(errorResponse);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
}
|
|
|
|
}
|
|
catch (Exception ex) when (retry < maxRetryAttemptsCount && responseStatus != System.Net.HttpStatusCode.NotFound)
|
|
{
|
|
Trace.Error($"{errorMessage} -- Attempt: {retry}");
|
|
Trace.Error(ex);
|
|
}
|
|
}
|
|
var backOff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(5));
|
|
Trace.Info($"Retrying in {backOff.Seconds} seconds");
|
|
await Task.Delay(backOff);
|
|
}
|
|
}
|
|
}
|
|
}
|