-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Resolve Actions Directly From Launch for Run Service Jobs #2529
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
Changes from all commits
5d35e06
3fa88c6
180f2b2
ce7d5f1
c139011
eccf619
ffe4899
ba4368d
f67e4eb
523a4f2
25eedad
1ce356d
a29ff09
d67a5f9
98d2c3d
62c2ad6
8dd3295
e968b5a
4889151
c015ed6
0c71d05
9d26a6b
4c6287a
080c58b
069aefe
306a4ec
43bca84
2bde421
ea42fb2
b6d96fe
98374e4
3768a5d
1a78a1d
064967f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
| using GitHub.DistributedTask.WebApi; | ||
| using GitHub.Services.Launch.Client; | ||
| using GitHub.Services.WebApi; | ||
|
|
||
| namespace GitHub.Runner.Common | ||
| { | ||
| [ServiceLocator(Default = typeof(LaunchServer))] | ||
| public interface ILaunchServer : IRunnerService | ||
| { | ||
| void InitializeLaunchClient(Uri uri, string token); | ||
|
|
||
| Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken); | ||
| } | ||
|
|
||
| public sealed class LaunchServer : RunnerService, ILaunchServer | ||
| { | ||
| private LaunchHttpClient _launchClient; | ||
|
|
||
| public void InitializeLaunchClient(Uri uri, string token) | ||
| { | ||
| var httpMessageHandler = HostContext.CreateHttpClientHandler(); | ||
| this._launchClient = new LaunchHttpClient(uri, httpMessageHandler, token, disposeHandler: true); | ||
| } | ||
|
|
||
| public Task<ActionDownloadInfoCollection> ResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| if (_launchClient != null) | ||
| { | ||
| return _launchClient.GetResolveActionsDownloadInfoAsync(planId, jobId, actionReferenceList, | ||
| cancellationToken: cancellationToken); | ||
| } | ||
|
|
||
| throw new InvalidOperationException("Launch client is not initialized."); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Runtime.Serialization; | ||
|
|
||
| namespace GitHub.Services.Launch.Contracts | ||
| { | ||
| [DataContract] | ||
| public class ActionReferenceRequest | ||
| { | ||
| [DataMember(EmitDefaultValue = false, Name = "action")] | ||
| public string Action { get; set; } | ||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "version")] | ||
| public string Version { get; set; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is currently called
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is copied pretty much directly from https://github.com/github/actions-dotnet/blob/179ed33568dd1ad80c457fb1c328c964e372accf/Launch/Sdk/Server/Models/GitHub.cs#L90-L152. Since the runner is now the one receiving the payload from launch, it needs to parse the JSON payload the same way. The same goes for sending the payload to Launch. The runner needs to serialize the data into JSON that Launch expects. I don't believe this is a rename |
||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "path")] | ||
| public string Path { get; set; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the server care about the |
||
| } | ||
|
|
||
| [DataContract] | ||
| public class ActionReferenceRequestList | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need an object to wrap around a list?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The endpoint for resolving actions expects an object wrapped around a list |
||
| { | ||
| [DataMember(EmitDefaultValue = false, Name = "actions")] | ||
| public IList<ActionReferenceRequest> Actions { get; set; } | ||
| } | ||
|
|
||
| [DataContract] | ||
| public class ActionDownloadInfoResponse | ||
| { | ||
| [DataMember(EmitDefaultValue = false, Name = "authentication")] | ||
| public ActionDownloadAuthenticationResponse Authentication { get; set; } | ||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "name")] | ||
| public string Name { get; set; } | ||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "resolved_name")] | ||
| public string ResolvedName { get; set; } | ||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "resolved_sha")] | ||
| public string ResolvedSha { get; set; } | ||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "tar_url")] | ||
| public string TarUrl { get; set; } | ||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "version")] | ||
| public string Version { get; set; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question about |
||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "zip_url")] | ||
| public string ZipUrl { get; set; } | ||
| } | ||
|
|
||
| [DataContract] | ||
| public class ActionDownloadAuthenticationResponse | ||
| { | ||
| [DataMember(EmitDefaultValue = false, Name = "expires_at")] | ||
| public DateTime ExpiresAt { get; set; } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we really care about the token's expiresAt?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's necessary for building the |
||
|
|
||
| [DataMember(EmitDefaultValue = false, Name = "token")] | ||
| public string Token { get; set; } | ||
| } | ||
|
|
||
| [DataContract] | ||
| public class ActionDownloadInfoResponseCollection | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question, why do we need this wrapper around a dictionary? |
||
| { | ||
| /// <summary>A mapping of action specifications to their download information.</summary> | ||
| /// <remarks>The key is the full name of the action plus version, e.g. "actions/checkout@v2".</remarks> | ||
| [DataMember(EmitDefaultValue = false, Name = "actions")] | ||
| public IDictionary<string, ActionDownloadInfoResponse> Actions { get; set; } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| #nullable enable | ||
|
|
||
| using System; | ||
| using System.Linq; | ||
| using System.Net.Http; | ||
| using System.Net.Http.Formatting; | ||
| using System.Net.Http.Headers; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| using GitHub.DistributedTask.WebApi; | ||
| using GitHub.Services.Launch.Contracts; | ||
|
|
||
| using Sdk.WebApi.WebApi; | ||
|
|
||
| namespace GitHub.Services.Launch.Client | ||
| { | ||
| public class LaunchHttpClient : RawHttpClientBase | ||
| { | ||
| public LaunchHttpClient( | ||
| Uri baseUrl, | ||
| HttpMessageHandler pipeline, | ||
| string token, | ||
| bool disposeHandler) | ||
| : base(baseUrl, pipeline, disposeHandler) | ||
| { | ||
| m_token = token; | ||
| m_launchServiceUrl = baseUrl; | ||
| m_formatter = new JsonMediaTypeFormatter(); | ||
| } | ||
|
|
||
| public async Task<ActionDownloadInfoCollection> GetResolveActionsDownloadInfoAsync(Guid planId, Guid jobId, ActionReferenceList actionReferenceList, CancellationToken cancellationToken) | ||
| { | ||
| var GetResolveActionsDownloadInfoURLEndpoint = new Uri(m_launchServiceUrl, $"/actions/build/{planId.ToString()}/jobs/{jobId.ToString()}/runnerresolve/actions"); | ||
| return ToServerData(await GetLaunchSignedURLResponse<ActionReferenceRequestList, ActionDownloadInfoResponseCollection>(GetResolveActionsDownloadInfoURLEndpoint, ToGitHubData(actionReferenceList), cancellationToken)); | ||
| } | ||
|
|
||
| // Resolve Actions | ||
| private async Task<T> GetLaunchSignedURLResponse<R, T>(Uri uri, R request, CancellationToken cancellationToken) | ||
|
jherns marked this conversation as resolved.
|
||
| { | ||
| using (HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, uri)) | ||
| { | ||
| requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", m_token); | ||
| requestMessage.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json")); | ||
|
|
||
| using (HttpContent content = new ObjectContent<R>(request, m_formatter)) | ||
| { | ||
| requestMessage.Content = content; | ||
| using (var response = await SendAsync(requestMessage, HttpCompletionOption.ResponseContentRead, cancellationToken: cancellationToken)) | ||
| { | ||
| return await ReadJsonContentAsync<T>(response, cancellationToken); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private static ActionReferenceRequestList ToGitHubData(ActionReferenceList actionReferenceList) | ||
| { | ||
| return new ActionReferenceRequestList | ||
| { | ||
| Actions = actionReferenceList.Actions?.Select(ToGitHubData).ToList() | ||
| }; | ||
| } | ||
|
|
||
| private static ActionReferenceRequest ToGitHubData(ActionReference actionReference) | ||
| { | ||
| return new ActionReferenceRequest | ||
| { | ||
| Action = actionReference.NameWithOwner, | ||
| Version = actionReference.Ref, | ||
| Path = actionReference.Path | ||
| }; | ||
| } | ||
|
|
||
| private static ActionDownloadInfoCollection ToServerData(ActionDownloadInfoResponseCollection actionDownloadInfoResponseCollection) | ||
| { | ||
| return new ActionDownloadInfoCollection | ||
| { | ||
| Actions = actionDownloadInfoResponseCollection.Actions?.ToDictionary(kvp => kvp.Key, kvp => ToServerData(kvp.Value)) | ||
| }; | ||
| } | ||
|
|
||
| private static ActionDownloadInfo ToServerData(ActionDownloadInfoResponse actionDownloadInfoResponse) | ||
| { | ||
| return new ActionDownloadInfo | ||
| { | ||
| Authentication = ToServerData(actionDownloadInfoResponse.Authentication), | ||
| NameWithOwner = actionDownloadInfoResponse.Name, | ||
| ResolvedNameWithOwner = actionDownloadInfoResponse.ResolvedName, | ||
| ResolvedSha = actionDownloadInfoResponse.ResolvedSha, | ||
| TarballUrl = actionDownloadInfoResponse.TarUrl, | ||
| Ref = actionDownloadInfoResponse.Version, | ||
| ZipballUrl = actionDownloadInfoResponse.ZipUrl, | ||
| }; | ||
| } | ||
|
|
||
| private static ActionDownloadAuthentication? ToServerData(ActionDownloadAuthenticationResponse? actionDownloadAuthenticationResponse) | ||
| { | ||
| if (actionDownloadAuthenticationResponse == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| return new ActionDownloadAuthentication | ||
| { | ||
| ExpiresAt = actionDownloadAuthenticationResponse.ExpiresAt, | ||
| Token = actionDownloadAuthenticationResponse.Token | ||
| }; | ||
| } | ||
|
|
||
| private MediaTypeFormatter m_formatter; | ||
| private Uri m_launchServiceUrl; | ||
| private string m_token; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.