Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register Runners against V2 servers #2505

Merged
merged 12 commits into from
Mar 28, 2023
Prev Previous commit
Next Next commit
Cleanup
  • Loading branch information
luketomlinson committed Mar 24, 2023
commit ddff604fc7acb306a6a9bd06dc40b11288aa8401
16 changes: 4 additions & 12 deletions src/Runner.Common/RunnerDotcomServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface IRunnerDotcomServer : IRunnerService
{
Task<List<TaskAgent>> GetRunnersAsync(int runnerGroupId, string githubUrl, string githubToken, string agentName);

Task<TaskAgent> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey, string hostId);
Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey);
Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, string githubToken);

string GetGitHubRequestId(HttpResponseHeaders headers);
Expand Down Expand Up @@ -136,7 +136,7 @@ public async Task<List<TaskAgentPool>> GetRunnerGroupsAsync(string githubUrl, st
return agentPools?.ToAgentPoolList();
}

public async Task<TaskAgent> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey, string hostId)
public async Task<DistributedTask.WebApi.Runner> AddRunnerAsync(int runnerGroupId, TaskAgent agent, string githubUrl, string githubToken, string publicKey)
{
var gitHubUrlBuilder = new UriBuilder(githubUrl);
var path = gitHubUrlBuilder.Path.Split('/', '\\', StringSplitOptions.RemoveEmptyEntries);
Expand All @@ -159,20 +159,12 @@ public async Task<TaskAgent> AddRunnerAsync(int runnerGroupId, TaskAgent agent,
{"updates_disabled", agent.DisableUpdate},
{"ephemeral", agent.Ephemeral},
{"labels", agent.Labels},
{"public_key", publicKey},
{"host_id", hostId},
{"public_key", publicKey}
};

var body = new StringContent(StringUtil.ConvertToJson(bodyObject), null, "application/json");

var runner = await RetryRequest<DistributedTask.WebApi.Runner>(githubApiUrl, githubToken, RequestType.Post, 3, "Failed to add agent", body);
agent.Id = runner.Id;
agent.Authorization = new TaskAgentAuthorization()
{
AuthorizationUrl = runner.RunnerAuthorization.AuthorizationUrl,
ClientId = new Guid(runner.RunnerAuthorization.ClientId),
};
return agent;
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)
Expand Down
5 changes: 5 additions & 0 deletions src/Runner.Listener/BrokerMessageListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ private async Task RefreshBrokerConnection()
var configManager = HostContext.GetService<IConfigurationManager>();
_settings = configManager.LoadSettings();

if (_settings.ServerUrlV2 == null)
{
throw new InvalidOperationException("ServerUrlV2 is not set");
}

var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials creds = credMgr.LoadCredentials();
await _brokerServer.ConnectAsync(new Uri(_settings.ServerUrlV2), creds);
Expand Down
51 changes: 24 additions & 27 deletions src/Runner.Listener/Configuration/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ public async Task ConfigureAsync(CommandSettings command)
VssCredentials creds = null;
_term.WriteSection("Authentication");
string registerToken = string.Empty;
string hostId = string.Empty;
while (true)
{
// When testing against a dev deployment of Actions Service, set this environment variable
Expand All @@ -142,7 +141,6 @@ public async Task ConfigureAsync(CommandSettings command)
_term.WriteLine($"Using V2 flow: {runnerSettings.UseV2Flow}");
creds = authResult.ToVssCredentials();
Trace.Info("cred retrieved via GitHub auth");
hostId = GetHostId(authResult.Token);
}

try
Expand Down Expand Up @@ -302,7 +300,9 @@ public async Task ConfigureAsync(CommandSettings command)
{
if (runnerSettings.UseV2Flow)
{
agent = await _dotcomServer.AddRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken, publicKeyXML, hostId);
var runner = await _dotcomServer.AddRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken, publicKeyXML);
runner.ApplyToTaskAgent(agent);
runnerSettings.ServerUrlV2 = runner.RunnerAuthorization.ServerUrl;
}
else
{
Expand Down Expand Up @@ -359,24 +359,28 @@ public async Task ConfigureAsync(CommandSettings command)
}

// Testing agent connection, detect any potential connection issue, like local clock skew that cause OAuth token expired.
var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials credential = credMgr.LoadCredentials();
try
{
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
// ConnectAsync() hits _apis/connectionData which is an anonymous endpoint
// Need to hit an authenticate endpoint to trigger OAuth token exchange.
await _runnerServer.GetAgentPoolsAsync();
_term.WriteSuccessMessage("Runner connection is good");
}
catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is"))

if (!runnerSettings.UseV2Flow)
{
// there are two exception messages server send that indicate clock skew.
// 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}.
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
Trace.Error("Catch exception during test agent connection.");
Trace.Error(ex);
throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
var credMgr = HostContext.GetService<ICredentialManager>();
VssCredentials credential = credMgr.LoadCredentials();
try
{
await _runnerServer.ConnectAsync(new Uri(runnerSettings.ServerUrl), credential);
// ConnectAsync() hits _apis/connectionData which is an anonymous endpoint
// Need to hit an authenticate endpoint to trigger OAuth token exchange.
await _runnerServer.GetAgentPoolsAsync();
_term.WriteSuccessMessage("Runner connection is good");
}
catch (VssOAuthTokenRequestException ex) when (ex.Message.Contains("Current server time is"))
{
// there are two exception messages server send that indicate clock skew.
// 1. The bearer token expired on {jwt.ValidTo}. Current server time is {DateTime.UtcNow}.
// 2. The bearer token is not valid until {jwt.ValidFrom}. Current server time is {DateTime.UtcNow}.
Trace.Error("Catch exception during test agent connection.");
Trace.Error(ex);
throw new Exception("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
}
}

_term.WriteSection("Runner settings");
Expand Down Expand Up @@ -778,12 +782,5 @@ private async Task<GitHubAuthResult> GetTenantCredential(string githubUrl, strin
}
return null;
}

// Temporary hack for sending legacy host id using v2 flow
private string GetHostId(string accessToken)
{
var claims = JsonWebToken.Create(accessToken).ExtractClaims();
return claims.FirstOrDefault(x => x.Type == "aud").Value.Split(':').LastOrDefault();
}
}
}
27 changes: 27 additions & 0 deletions src/Sdk/DTWebApi/WebApi/Runner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,29 @@ public class Runner

public class Authorization
{
/// <summary>
/// The url to refresh tokens
/// </summary>
[JsonProperty("authorization_url")]
public Uri AuthorizationUrl
{
get;
internal set;
}

/// <summary>
/// The url to connect to to poll for messages
/// </summary>
[JsonProperty("server_url")]
public string ServerUrl
{
get;
internal set;
}

/// <summary>
/// The client id to use when connecting to the authorization_url
/// </summary>
[JsonProperty("client_id")]
public string ClientId
{
Expand Down Expand Up @@ -43,5 +59,16 @@ public Authorization RunnerAuthorization
get;
internal set;
}

public TaskAgent ApplyToTaskAgent(TaskAgent agent)
{
agent.Id = this.Id;
agent.Authorization = new TaskAgentAuthorization()
{
AuthorizationUrl = this.RunnerAuthorization.AuthorizationUrl,
ClientId = new Guid(this.RunnerAuthorization.ClientId)
};
return agent;
}
}
}