Resolve action download info (#515)

This commit is contained in:
eric sciple
2020-06-09 08:53:28 -04:00
committed by GitHub
parent 1aea046932
commit 5815819f24
9 changed files with 252 additions and 83 deletions

View File

@@ -22,6 +22,7 @@ namespace GitHub.Runner.Common
Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken); Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken);
Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent; Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent;
Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken); Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken);
Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken);
} }
public sealed class JobServer : RunnerService, IJobServer public sealed class JobServer : RunnerService, IJobServer
@@ -113,5 +114,14 @@ namespace GitHub.Runner.Common
CheckConnection(); CheckConnection();
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken); return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
} }
//-----------------------------------------------------------------
// Action download info
//-----------------------------------------------------------------
public Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken)
{
CheckConnection();
return _taskClient.ResolveActionDownloadInfoAsync(scopeIdentifier, hubName, planId, actions, cancellationToken: cancellationToken);
}
} }
} }

View File

@@ -14,6 +14,7 @@ using GitHub.Runner.Common;
using GitHub.Runner.Sdk; using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container; using GitHub.Runner.Worker.Container;
using GitHub.Services.Common; using GitHub.Services.Common;
using WebApi = GitHub.DistributedTask.WebApi;
using Pipelines = GitHub.DistributedTask.Pipelines; using Pipelines = GitHub.DistributedTask.Pipelines;
using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants; using PipelineTemplateConstants = GitHub.DistributedTask.Pipelines.ObjectTemplating.PipelineTemplateConstants;
@@ -546,10 +547,10 @@ namespace GitHub.Runner.Worker
} }
// This implementation is temporary and will be removed when we switch to a REST API call to the service to resolve the download info // This implementation is temporary and will be removed when we switch to a REST API call to the service to resolve the download info
private async Task<bool> RepoExistsAsync(IExecutionContext executionContext, Pipelines.RepositoryPathReference repositoryReference, string authorization) private async Task<bool> RepoExistsAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo actionDownloadInfo, string token)
{ {
var apiUrl = GetApiUrl(executionContext); var apiUrl = GetApiUrl(executionContext);
var repoUrl = $"{apiUrl}/repos/{repositoryReference.Name}"; var repoUrl = $"{apiUrl}/repos/{actionDownloadInfo.NameWithOwner}";
for (var attempt = 1; attempt <= 3; attempt++) for (var attempt = 1; attempt <= 3; attempt++)
{ {
executionContext.Debug($"Checking whether repo exists: {repoUrl}"); executionContext.Debug($"Checking whether repo exists: {repoUrl}");
@@ -558,7 +559,7 @@ namespace GitHub.Runner.Worker
using (var httpClientHandler = HostContext.CreateHttpClientHandler()) using (var httpClientHandler = HostContext.CreateHttpClientHandler())
using (var httpClient = new HttpClient(httpClientHandler)) using (var httpClient = new HttpClient(httpClientHandler))
{ {
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(authorization); httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(token);
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents); httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
using (var response = await httpClient.GetAsync(repoUrl)) using (var response = await httpClient.GetAsync(repoUrl))
{ {
@@ -582,11 +583,11 @@ namespace GitHub.Runner.Worker
{ {
if (attempt < 3) if (attempt < 3)
{ {
executionContext.Debug($"Failed checking whether repo '{repositoryReference.Name}' exists: {ex.Message}"); executionContext.Debug($"Failed checking whether repo '{actionDownloadInfo.NameWithOwner}' exists: {ex.Message}");
} }
else else
{ {
executionContext.Error($"Failed checking whether repo '{repositoryReference.Name}' exists: {ex.Message}"); executionContext.Error($"Failed checking whether repo '{actionDownloadInfo.NameWithOwner}' exists: {ex.Message}");
throw; throw;
} }
} }
@@ -596,73 +597,89 @@ namespace GitHub.Runner.Worker
} }
// This implementation is temporary and will be replaced with a REST API call to the service to resolve // This implementation is temporary and will be replaced with a REST API call to the service to resolve
private async Task<Dictionary<string, ActionDownloadInfo>> GetDownloadInfoAsync(IExecutionContext executionContext, List<Pipelines.ActionStep> actions) private async Task<IDictionary<string, WebApi.ActionDownloadInfo>> GetDownloadInfoAsync(IExecutionContext executionContext, List<Pipelines.ActionStep> actions)
{ {
var result = new Dictionary<string, ActionDownloadInfo>(StringComparer.OrdinalIgnoreCase); executionContext.Output("Getting action download info");
var configurationStore = HostContext.GetService<IConfigurationStore>(); // Convert to action reference
var runnerSettings = configurationStore.GetSettings(); var actionReferences = actions
var apiUrl = GetApiUrl(executionContext); .GroupBy(x => GetDownloadInfoLookupKey(x))
var accessToken = executionContext.GetGitHubContext("token"); .Where(x => !string.IsNullOrEmpty(x.Key))
var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{accessToken}")); .Select(x =>
var authorization = $"Basic {base64EncodingToken}";
foreach (var action in actions)
{ {
var lookupKey = GetDownloadInfoLookupKey(action); var action = x.First();
if (string.IsNullOrEmpty(lookupKey) || result.ContainsKey(lookupKey))
{
continue;
}
var repositoryReference = action.Reference as Pipelines.RepositoryPathReference; var repositoryReference = action.Reference as Pipelines.RepositoryPathReference;
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference)); ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
return new WebApi.ActionReference
{
NameWithOwner = repositoryReference.Name,
Ref = repositoryReference.Ref,
};
})
.ToList();
var downloadInfo = default(ActionDownloadInfo); // Nothing to resolve?
if (actionReferences.Count == 0)
{
return new Dictionary<string, WebApi.ActionDownloadInfo>();
}
// Resolve download info
var jobServer = HostContext.GetService<IJobServer>();
var actionDownloadInfos = default(WebApi.ActionDownloadInfoCollection);
for (var attempt = 1; attempt <= 3; attempt++)
{
try
{
actionDownloadInfos = await jobServer.ResolveActionDownloadInfoAsync(executionContext.Plan.ScopeIdentifier, executionContext.Plan.PlanType, executionContext.Plan.PlanId, new WebApi.ActionReferenceList { Actions = actionReferences }, executionContext.CancellationToken);
break;
}
catch (Exception ex) when (attempt < 3)
{
executionContext.Output($"Failed to resolve action download info. Error: {ex.Message}");
executionContext.Debug(ex.ToString());
if (String.IsNullOrEmpty(Environment.GetEnvironmentVariable("_GITHUB_ACTION_DOWNLOAD_NO_BACKOFF")))
{
var backoff = BackoffTimerHelper.GetRandomBackoff(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30));
executionContext.Output($"Retrying in {backoff.TotalSeconds} seconds");
await Task.Delay(backoff);
}
}
}
ArgUtil.NotNull(actionDownloadInfos, nameof(actionDownloadInfos));
ArgUtil.NotNull(actionDownloadInfos.Actions, nameof(actionDownloadInfos.Actions));
var apiUrl = GetApiUrl(executionContext);
var defaultAccessToken = executionContext.GetGitHubContext("token");
var configurationStore = HostContext.GetService<IConfigurationStore>();
var runnerSettings = configurationStore.GetSettings();
foreach (var actionDownloadInfo in actionDownloadInfos.Actions.Values)
{
// Add secret
HostContext.SecretMasker.AddValue(actionDownloadInfo.Authentication?.Token);
// Temporary code: Fix token and download URL
if (runnerSettings.IsHostedServer) if (runnerSettings.IsHostedServer)
{ {
downloadInfo = new ActionDownloadInfo actionDownloadInfo.Authentication = new WebApi.ActionDownloadAuthentication { Token = defaultAccessToken };
{ actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", apiUrl);
NameWithOwner = repositoryReference.Name, actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", apiUrl);
Ref = repositoryReference.Ref,
ArchiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
Authorization = authorization,
};
} }
// Test whether the repo exists in the instance else if (await RepoExistsAsync(executionContext, actionDownloadInfo, defaultAccessToken))
else if (await RepoExistsAsync(executionContext, repositoryReference, authorization))
{ {
downloadInfo = new ActionDownloadInfo actionDownloadInfo.Authentication = new WebApi.ActionDownloadAuthentication { Token = defaultAccessToken };
{ actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", apiUrl);
NameWithOwner = repositoryReference.Name, actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", apiUrl);
Ref = repositoryReference.Ref,
ArchiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
Authorization = authorization,
};
} }
// Fallback to dotcom
else else
{ {
downloadInfo = new ActionDownloadInfo actionDownloadInfo.TarballUrl = actionDownloadInfo.TarballUrl.Replace("<GITHUB_API_URL>", "https://api.github.com");
{ actionDownloadInfo.ZipballUrl = actionDownloadInfo.ZipballUrl.Replace("<GITHUB_API_URL>", "https://api.github.com");
NameWithOwner = repositoryReference.Name, }
Ref = repositoryReference.Ref,
ArchiveLink = BuildLinkToActionArchive(_dotcomApiUrl, repositoryReference.Name, repositoryReference.Ref),
Authorization = null,
};
} }
result.Add(lookupKey, downloadInfo); return actionDownloadInfos.Actions;
}
// Register secrets
foreach (var downloadInfo in result.Values)
{
HostContext.SecretMasker.AddValue(downloadInfo.Authorization);
}
return result;
} }
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed // todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
@@ -709,7 +726,6 @@ namespace GitHub.Runner.Worker
{ {
string apiUrl = GetApiUrl(executionContext); string apiUrl = GetApiUrl(executionContext);
string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref); string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref);
Trace.Info($"Download archive '{archiveLink}' to '{destDirectory}'.");
var downloadDetails = new ActionDownloadDetails(archiveLink, ConfigureAuthorizationFromContext); var downloadDetails = new ActionDownloadDetails(archiveLink, ConfigureAuthorizationFromContext);
await DownloadRepositoryActionAsync(executionContext, downloadDetails, null, destDirectory); await DownloadRepositoryActionAsync(executionContext, downloadDetails, null, destDirectory);
return; return;
@@ -735,7 +751,6 @@ namespace GitHub.Runner.Worker
foreach (var downloadAttempt in downloadAttempts) foreach (var downloadAttempt in downloadAttempts)
{ {
Trace.Info($"Download archive '{downloadAttempt.ArchiveLink}' to '{destDirectory}'.");
try try
{ {
await DownloadRepositoryActionAsync(executionContext, downloadAttempt, null, destDirectory); await DownloadRepositoryActionAsync(executionContext, downloadAttempt, null, destDirectory);
@@ -751,7 +766,7 @@ namespace GitHub.Runner.Worker
} }
} }
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadInfo downloadInfo) private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo)
{ {
Trace.Entering(); Trace.Entering();
ArgUtil.NotNull(executionContext, nameof(executionContext)); ArgUtil.NotNull(executionContext, nameof(executionContext));
@@ -774,7 +789,6 @@ namespace GitHub.Runner.Worker
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'"); executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
} }
Trace.Info($"Download archive '{downloadInfo.ArchiveLink}' to '{destDirectory}'.");
await DownloadRepositoryActionAsync(executionContext, null, downloadInfo, destDirectory); await DownloadRepositoryActionAsync(executionContext, null, downloadInfo, destDirectory);
} }
@@ -799,7 +813,7 @@ namespace GitHub.Runner.Worker
} }
// todo: Remove the parameter "actionDownloadDetails" when feature flag DistributedTask.NewActionMetadata is removed // todo: Remove the parameter "actionDownloadDetails" when feature flag DistributedTask.NewActionMetadata is removed
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadDetails actionDownloadDetails, ActionDownloadInfo downloadInfo, string destDirectory) private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, ActionDownloadDetails actionDownloadDetails, WebApi.ActionDownloadInfo downloadInfo, string destDirectory)
{ {
//download and extract action in a temp folder and rename it on success //download and extract action in a temp folder and rename it on success
string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid()); string tempDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), "_temp_" + Guid.NewGuid());
@@ -807,11 +821,12 @@ namespace GitHub.Runner.Worker
#if OS_WINDOWS #if OS_WINDOWS
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip"); string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.zip");
string link = downloadInfo?.ZipballUrl ?? actionDownloadDetails.ArchiveLink;
#else #else
string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz"); string archiveFile = Path.Combine(tempDirectory, $"{Guid.NewGuid()}.tar.gz");
string link = downloadInfo?.TarballUrl ?? actionDownloadDetails.ArchiveLink;
#endif #endif
string link = downloadInfo != null ? downloadInfo.ArchiveLink : actionDownloadDetails.ArchiveLink;
Trace.Info($"Save archive '{link}' into {archiveFile}."); Trace.Info($"Save archive '{link}' into {archiveFile}.");
try try
{ {
@@ -839,7 +854,7 @@ namespace GitHub.Runner.Worker
// FF DistributedTask.NewActionMetadata // FF DistributedTask.NewActionMetadata
else else
{ {
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authorization); httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
} }
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents); httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
@@ -1137,20 +1152,23 @@ namespace GitHub.Runner.Worker
return $"{repositoryReference.Name}@{repositoryReference.Ref}"; return $"{repositoryReference.Name}@{repositoryReference.Ref}";
} }
private static AuthenticationHeaderValue CreateAuthHeader(string authorization) private static string GetDownloadInfoLookupKey(WebApi.ActionDownloadInfo info)
{ {
if (string.IsNullOrEmpty(authorization)) ArgUtil.NotNullOrEmpty(info.NameWithOwner, nameof(info.NameWithOwner));
ArgUtil.NotNullOrEmpty(info.Ref, nameof(info.Ref));
return $"{info.NameWithOwner}@{info.Ref}";
}
private AuthenticationHeaderValue CreateAuthHeader(string token)
{
if (string.IsNullOrEmpty(token))
{ {
return null; return null;
} }
var split = authorization.Split(new char[] { ' ' }, 2); var base64EncodingToken = Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token:{token}"));
if (split.Length != 2 || string.IsNullOrWhiteSpace(split[0]) || string.IsNullOrWhiteSpace(split[1])) HostContext.SecretMasker.AddValue(base64EncodingToken);
{ return new AuthenticationHeaderValue("Basic", base64EncodingToken);
throw new Exception("Unexpected authorization header format");
}
return new AuthenticationHeaderValue(split[0].Trim(), split[1].Trim());
} }
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed // todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
@@ -1166,17 +1184,6 @@ namespace GitHub.Runner.Worker
ConfigureAuthorization = configureAuthorization; ConfigureAuthorization = configureAuthorization;
} }
} }
private class ActionDownloadInfo
{
public string NameWithOwner { get; set; }
public string Ref { get; set; }
public string ArchiveLink { get; set; }
public string Authorization { get; set; }
}
} }
public sealed class Definition public sealed class Definition

View File

@@ -44,6 +44,7 @@ namespace GitHub.Runner.Worker
TaskResult? CommandResult { get; set; } TaskResult? CommandResult { get; set; }
CancellationToken CancellationToken { get; } CancellationToken CancellationToken { get; }
List<ServiceEndpoint> Endpoints { get; } List<ServiceEndpoint> Endpoints { get; }
TaskOrchestrationPlanReference Plan { get; }
PlanFeatures Features { get; } PlanFeatures Features { get; }
Variables Variables { get; } Variables Variables { get; }
@@ -141,6 +142,7 @@ namespace GitHub.Runner.Worker
public Task ForceCompleted => _forceCompleted.Task; public Task ForceCompleted => _forceCompleted.Task;
public CancellationToken CancellationToken => _cancellationTokenSource.Token; public CancellationToken CancellationToken => _cancellationTokenSource.Token;
public List<ServiceEndpoint> Endpoints { get; private set; } public List<ServiceEndpoint> Endpoints { get; private set; }
public TaskOrchestrationPlanReference Plan { get; private set; }
public Variables Variables { get; private set; } public Variables Variables { get; private set; }
public Dictionary<string, string> IntraActionState { get; private set; } public Dictionary<string, string> IntraActionState { get; private set; }
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; } public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
@@ -275,6 +277,7 @@ namespace GitHub.Runner.Worker
child.Features = Features; child.Features = Features;
child.Variables = Variables; child.Variables = Variables;
child.Endpoints = Endpoints; child.Endpoints = Endpoints;
child.Plan = Plan;
if (intraActionState == null) if (intraActionState == null)
{ {
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -576,7 +579,8 @@ namespace GitHub.Runner.Worker
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token); _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token);
// Features // Plan
Plan = message.Plan;
Features = PlanUtil.GetFeatures(message.Plan); Features = PlanUtil.GetFeatures(message.Plan);
// Endpoints // Endpoints

View File

@@ -317,5 +317,37 @@ namespace GitHub.DistributedTask.WebApi
userState: userState, userState: userState,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
/// <summary>
/// [Preview API] Resolves information required to download actions (URL, token) defined in an orchestration.
/// </summary>
/// <param name="scopeIdentifier">The project GUID to scope the request</param>
/// <param name="hubName">The name of the server hub: "build" for the Build server or "rm" for the Release Management server</param>
/// <param name="planId"></param>
/// <param name="actionReferenceList"></param>
/// <param name="userState"></param>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
public virtual Task<ActionDownloadInfoCollection> ResolveActionDownloadInfoAsync(
Guid scopeIdentifier,
string hubName,
Guid planId,
ActionReferenceList actionReferenceList,
object userState = null,
CancellationToken cancellationToken = default)
{
HttpMethod httpMethod = new HttpMethod("POST");
Guid locationId = new Guid("27d7f831-88c1-4719-8ca1-6a061dad90eb");
object routeValues = new { scopeIdentifier = scopeIdentifier, hubName = hubName, planId = planId };
HttpContent content = new ObjectContent<ActionReferenceList>(actionReferenceList, new VssJsonMediaTypeFormatter(true));
return SendAsync<ActionDownloadInfoCollection>(
httpMethod,
locationId,
routeValues: routeValues,
version: new ApiResourceVersion(6.0, 1),
userState: userState,
cancellationToken: cancellationToken,
content: content);
}
} }
} }

View File

@@ -0,0 +1,40 @@
using System;
using System.Runtime.Serialization;
namespace GitHub.DistributedTask.WebApi
{
[DataContract]
public class ActionDownloadInfo
{
[DataMember(EmitDefaultValue = false)]
public ActionDownloadAuthentication Authentication { get; set; }
[DataMember(EmitDefaultValue = false)]
public string NameWithOwner { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ResolvedNameWithOwner { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ResolvedSha { get; set; }
[DataMember(EmitDefaultValue = false)]
public string TarballUrl { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Ref { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ZipballUrl { get; set; }
}
[DataContract]
public class ActionDownloadAuthentication
{
[DataMember(EmitDefaultValue = false)]
public DateTime ExpiresAt { get; set; }
[DataMember(EmitDefaultValue = false)]
public string Token { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace GitHub.DistributedTask.WebApi
{
[DataContract]
public class ActionDownloadInfoCollection
{
[DataMember]
public IDictionary<string, ActionDownloadInfo> Actions
{
get;
set;
}
}
}

View File

@@ -0,0 +1,22 @@
using System.Runtime.Serialization;
namespace GitHub.DistributedTask.WebApi
{
[DataContract]
public class ActionReference
{
[DataMember]
public string NameWithOwner
{
get;
set;
}
[DataMember]
public string Ref
{
get;
set;
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace GitHub.DistributedTask.WebApi
{
[DataContract]
public class ActionReferenceList
{
[DataMember]
public IList<ActionReference> Actions
{
get;
set;
}
}
}

View File

@@ -28,6 +28,7 @@ namespace GitHub.Runner.Common.Tests.Worker
private Mock<IConfigurationStore> _configurationStore; private Mock<IConfigurationStore> _configurationStore;
private Mock<IDockerCommandManager> _dockerManager; private Mock<IDockerCommandManager> _dockerManager;
private Mock<IExecutionContext> _ec; private Mock<IExecutionContext> _ec;
private Mock<IJobServer> _jobServer;
private Mock<IRunnerPluginManager> _pluginManager; private Mock<IRunnerPluginManager> _pluginManager;
private TestHostContext _hc; private TestHostContext _hc;
private ActionManager _actionManager; private ActionManager _actionManager;
@@ -3583,6 +3584,7 @@ runs:
_ec.Setup(x => x.Variables).Returns(new Variables(_hc, variables)); _ec.Setup(x => x.Variables).Returns(new Variables(_hc, variables));
_ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData()); _ec.Setup(x => x.ExpressionValues).Returns(new DictionaryContextData());
_ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>()); _ec.Setup(x => x.ExpressionFunctions).Returns(new List<IFunctionInfo>());
_ec.Setup(x => x.Plan).Returns(new TaskOrchestrationPlanReference());
_ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); }); _ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>())).Callback((string tag, string message) => { _hc.GetTrace().Info($"[{tag}]{message}"); });
_ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); }); _ec.Setup(x => x.AddIssue(It.IsAny<Issue>(), It.IsAny<string>())).Callback((Issue issue, string message) => { _hc.GetTrace().Info($"[{issue.Type}]{issue.Message ?? message}"); });
_ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions")); _ec.Setup(x => x.GetGitHubContext("workspace")).Returns(Path.Combine(_workFolder, "actions", "actions"));
@@ -3593,6 +3595,25 @@ runs:
_dockerManager.Setup(x => x.DockerBuild(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(0)); _dockerManager.Setup(x => x.DockerBuild(_ec.Object, It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>())).Returns(Task.FromResult(0));
_jobServer = new Mock<IJobServer>();
_jobServer.Setup(x => x.ResolveActionDownloadInfoAsync(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<Guid>(), It.IsAny<ActionReferenceList>(), It.IsAny<CancellationToken>()))
.Returns((Guid scopeIdentifier, string hubName, Guid planId, ActionReferenceList actions, CancellationToken cancellationToken) =>
{
var result = new ActionDownloadInfoCollection { Actions = new Dictionary<string, ActionDownloadInfo>() };
foreach (var action in actions.Actions)
{
var key = $"{action.NameWithOwner}@{action.Ref}";
result.Actions[key] = new ActionDownloadInfo
{
NameWithOwner = action.NameWithOwner,
Ref = action.Ref,
TarballUrl = $"<GITHUB_API_URL>/repos/{action.NameWithOwner}/tarball/{action.Ref}",
ZipballUrl = $"<GITHUB_API_URL>/repos/{action.NameWithOwner}/zipball/{action.Ref}",
};
}
return Task.FromResult(result);
});
_pluginManager = new Mock<IRunnerPluginManager>(); _pluginManager = new Mock<IRunnerPluginManager>();
_pluginManager.Setup(x => x.GetPluginAction(It.IsAny<string>())).Returns(new RunnerPluginActionInfo() { PluginTypeName = "plugin.class, plugin", PostPluginTypeName = "plugin.cleanup, plugin" }); _pluginManager.Setup(x => x.GetPluginAction(It.IsAny<string>())).Returns(new RunnerPluginActionInfo() { PluginTypeName = "plugin.class, plugin", PostPluginTypeName = "plugin.cleanup, plugin" });
@@ -3600,6 +3621,7 @@ runs:
actionManifest.Initialize(_hc); actionManifest.Initialize(_hc);
_hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object); _hc.SetSingleton<IDockerCommandManager>(_dockerManager.Object);
_hc.SetSingleton<IJobServer>(_jobServer.Object);
_hc.SetSingleton<IRunnerPluginManager>(_pluginManager.Object); _hc.SetSingleton<IRunnerPluginManager>(_pluginManager.Object);
_hc.SetSingleton<IActionManifestManager>(actionManifest); _hc.SetSingleton<IActionManifestManager>(actionManifest);
_hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory()); _hc.SetSingleton<IHttpClientHandlerFactory>(new HttpClientHandlerFactory());