mirror of
https://github.com/actions/runner.git
synced 2025-12-13 10:05:23 +00:00
Compare commits
15 Commits
users/tihu
...
users/etha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2c1ea71f9 | ||
|
|
a0d93904ee | ||
|
|
e654a3e885 | ||
|
|
85796d0011 | ||
|
|
885a128fa5 | ||
|
|
46f7da1628 | ||
|
|
7abdde0ad9 | ||
|
|
e601a3f4be | ||
|
|
4cb06b9edb | ||
|
|
65287fb0f0 | ||
|
|
3fed00814d | ||
|
|
e2bb8d9e24 | ||
|
|
5815819f24 | ||
|
|
1aea046932 | ||
|
|
eda463601c |
@@ -23,7 +23,7 @@ An ADR is an Architectural Decision Record. This allows consensus on the direct
|
|||||||
|
|
||||||
### Required Dev Dependencies
|
### Required Dev Dependencies
|
||||||
|
|
||||||
 Git for Windows [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
|
  Git for Windows and Linux [Install Here](https://git-scm.com/downloads) (needed for dev sh script)
|
||||||
|
|
||||||
### To Build, Test, Layout
|
### To Build, Test, Layout
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ Sample developer flow:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/actions/runner
|
git clone https://github.com/actions/runner
|
||||||
|
cd runner
|
||||||
cd ./src
|
cd ./src
|
||||||
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
|
./dev.(sh/cmd) layout # the runner that built from source is in {root}/_layout
|
||||||
<make code changes>
|
<make code changes>
|
||||||
@@ -53,7 +54,7 @@ cd ./src
|
|||||||
### Editors
|
### Editors
|
||||||
|
|
||||||
[Using Visual Studio Code](https://code.visualstudio.com/)
|
[Using Visual Studio Code](https://code.visualstudio.com/)
|
||||||
[Using Visual Studio 2019](https://www.visualstudio.com/vs/)
|
[Using Visual Studio](https://code.visualstudio.com/docs)
|
||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_term.WriteLine("Runner Repo TESTING VERSION OF RUNNER...");
|
||||||
|
Trace.Info("Hey I'm in the log file yay!");
|
||||||
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
VssUtil.InitializeVssClientSettings(HostContext.UserAgents, HostContext.WebProxy);
|
||||||
|
|
||||||
_inConfigStage = true;
|
_inConfigStage = true;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -394,6 +395,9 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}.");
|
Trace.Info($"Action cleanup plugin: {plugin.PluginTypeName}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA"))) {
|
||||||
|
// Don't do anything for now
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(definition.Data.Execution.ExecutionType.ToString());
|
throw new NotSupportedException(definition.Data.Execution.ExecutionType.ToString());
|
||||||
@@ -546,10 +550,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 +562,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 +586,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 +600,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 +729,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 +754,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 +769,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 +792,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 +816,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 +824,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 +857,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);
|
||||||
@@ -1086,6 +1104,12 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Action plugin: {(actionDefinitionData.Execution as PluginActionExecutionData).Plugin}, no more preparation.");
|
Trace.Info($"Action plugin: {(actionDefinitionData.Execution as PluginActionExecutionData).Plugin}, no more preparation.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite && !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
|
{
|
||||||
|
// Trace.Info($"Action composite: {(actionDefinitionData.Execution as CompositeActionExecutionData).Unknown}, no more preparation.");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(actionDefinitionData.Execution.ExecutionType.ToString());
|
throw new NotSupportedException(actionDefinitionData.Execution.ExecutionType.ToString());
|
||||||
@@ -1137,20 +1161,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 +1193,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
|
||||||
@@ -1204,6 +1220,7 @@ namespace GitHub.Runner.Worker
|
|||||||
NodeJS,
|
NodeJS,
|
||||||
Plugin,
|
Plugin,
|
||||||
Script,
|
Script,
|
||||||
|
Composite
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ContainerActionExecutionData : ActionExecutionData
|
public sealed class ContainerActionExecutionData : ActionExecutionData
|
||||||
@@ -1260,6 +1277,25 @@ namespace GitHub.Runner.Worker
|
|||||||
public override bool HasPost => false;
|
public override bool HasPost => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class CompositeActionExecutionData : ActionExecutionData
|
||||||
|
{
|
||||||
|
// Do I actually need this??
|
||||||
|
// Pre => pre execution for checking steps before, ibid for post
|
||||||
|
// Look at Script for dummy example
|
||||||
|
|
||||||
|
public override ActionExecutionType ExecutionType => ActionExecutionType.Composite;
|
||||||
|
public override bool HasPre => false;
|
||||||
|
public override bool HasPost => false;
|
||||||
|
|
||||||
|
public List<Pipelines.ActionStep> Steps {get; set;}
|
||||||
|
|
||||||
|
// public string Script { get; set; }
|
||||||
|
|
||||||
|
// public string Pre { get; set; }
|
||||||
|
|
||||||
|
// public string Post { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class ActionExecutionData
|
public abstract class ActionExecutionData
|
||||||
{
|
{
|
||||||
private string _initCondition = $"{Constants.Expressions.Always}()";
|
private string _initCondition = $"{Constants.Expressions.Always}()";
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using YamlDotNet.Core;
|
|||||||
using YamlDotNet.Core.Events;
|
using YamlDotNet.Core.Events;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
namespace GitHub.Runner.Worker
|
namespace GitHub.Runner.Worker
|
||||||
{
|
{
|
||||||
@@ -92,7 +93,7 @@ namespace GitHub.Runner.Worker
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "runs":
|
case "runs":
|
||||||
actionDefinition.Execution = ConvertRuns(context, actionPair.Value);
|
actionDefinition.Execution = ConvertRuns(executionContext, context, actionPair.Value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Trace.Info($"Ignore action property {propertyName}.");
|
Trace.Info($"Ignore action property {propertyName}.");
|
||||||
@@ -294,8 +295,10 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ActionExecutionData ConvertRuns(
|
private ActionExecutionData ConvertRuns(
|
||||||
|
IExecutionContext executionContext,
|
||||||
TemplateContext context,
|
TemplateContext context,
|
||||||
TemplateToken inputsToken)
|
TemplateToken inputsToken
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var runsMapping = inputsToken.AssertMapping("runs");
|
var runsMapping = inputsToken.AssertMapping("runs");
|
||||||
var usingToken = default(StringToken);
|
var usingToken = default(StringToken);
|
||||||
@@ -311,6 +314,15 @@ namespace GitHub.Runner.Worker
|
|||||||
var postToken = default(StringToken);
|
var postToken = default(StringToken);
|
||||||
var postEntrypointToken = default(StringToken);
|
var postEntrypointToken = default(StringToken);
|
||||||
var postIfToken = default(StringToken);
|
var postIfToken = default(StringToken);
|
||||||
|
// TODO: How do I represent stepsToken as a list of steps?
|
||||||
|
// Well for default value, we can set it to this?
|
||||||
|
// var stepsToken = runsMapping.AssertMapping("steps");
|
||||||
|
// Actually, not sure, let's just set it to MappingToken since AssertMapping("steps")
|
||||||
|
// returns a MappingToken
|
||||||
|
// var stepsToken = default(SequenceToken);
|
||||||
|
var stepsLoaded = default(List<ActionStep>);
|
||||||
|
// It should be a array (aka sequence)
|
||||||
|
|
||||||
foreach (var run in runsMapping)
|
foreach (var run in runsMapping)
|
||||||
{
|
{
|
||||||
var runsKey = run.Key.AssertString("runs key").Value;
|
var runsKey = run.Key.AssertString("runs key").Value;
|
||||||
@@ -355,6 +367,22 @@ namespace GitHub.Runner.Worker
|
|||||||
case "pre-if":
|
case "pre-if":
|
||||||
preIfToken = run.Value.AssertString("pre-if");
|
preIfToken = run.Value.AssertString("pre-if");
|
||||||
break;
|
break;
|
||||||
|
case "steps":
|
||||||
|
// stepsToken = run.Value.AssertMapping("steps");
|
||||||
|
// Maybe insert a for loop here instead since MappingToken is not supposed to be used in HandlerFactory.cs
|
||||||
|
// Just support 1 layer of steps w/ just run
|
||||||
|
var steps = run.Value.AssertSequence("steps");
|
||||||
|
// foreach (var s in steps) {
|
||||||
|
// // Create list of steps
|
||||||
|
// loadS
|
||||||
|
// }
|
||||||
|
// foreach (var run in runsMapping)
|
||||||
|
// stepsToken = List
|
||||||
|
// Call load steps here
|
||||||
|
// var test = new PipelineTemplateEvaluator();
|
||||||
|
var evaluator = executionContext.ToPipelineTemplateEvaluator();
|
||||||
|
stepsLoaded = evaluator.LoadSteps(steps, null, null);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Trace.Info($"Ignore run property {runsKey}.");
|
Trace.Info($"Ignore run property {runsKey}.");
|
||||||
break;
|
break;
|
||||||
@@ -402,6 +430,24 @@ namespace GitHub.Runner.Worker
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: add composite stuff here
|
||||||
|
else if (string.Equals(usingToken.Value, "composite", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// if (stepsToken.Count <= 0)
|
||||||
|
// {
|
||||||
|
// throw new ArgumentNullException($"No steps provided.");
|
||||||
|
// }
|
||||||
|
if (stepsLoaded == null) {
|
||||||
|
throw new ArgumentNullException($"No steps provided.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new CompositeActionExecutionData()
|
||||||
|
{
|
||||||
|
Steps = stepsLoaded
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker' or 'node12' instead.");
|
throw new ArgumentOutOfRangeException($"'using: {usingToken.Value}' is not supported, use 'docker' or 'node12' instead.");
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
399
src/Runner.Worker/Handlers/CompositeActionHandler.cs
Normal file
399
src/Runner.Worker/Handlers/CompositeActionHandler.cs
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.Runner.Common;
|
||||||
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.DistributedTask.WebApi;
|
||||||
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
|
||||||
|
namespace GitHub.Runner.Worker.Handlers
|
||||||
|
{
|
||||||
|
[ServiceLocator(Default = typeof(CompositeActionHandler))]
|
||||||
|
public interface ICompositeActionHandler : IHandler
|
||||||
|
{
|
||||||
|
CompositeActionExecutionData Data { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: IMPLEMENT LOGIC FOR HANDLER CODE
|
||||||
|
public sealed class CompositeActionHandler : Handler, ICompositeActionHandler
|
||||||
|
{
|
||||||
|
public CompositeActionExecutionData Data { get; set; }
|
||||||
|
|
||||||
|
// TODO: Implement PrintActionDetails()
|
||||||
|
public override void PrintActionDetails(ActionRunStage stage)
|
||||||
|
{
|
||||||
|
// Just keep as same as ScriptHandler.cs for now
|
||||||
|
var target = Data.Steps;
|
||||||
|
var runStepInputs = target[0].Inputs;
|
||||||
|
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
|
var inputs = templateEvaluator.EvaluateStepInputs(runStepInputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
|
||||||
|
var taskManager = HostContext.GetService<IActionManager>();
|
||||||
|
var userInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var runValue = "";
|
||||||
|
foreach (KeyValuePair<string, string> input in inputs)
|
||||||
|
{
|
||||||
|
userInputs.Add(input.Key);
|
||||||
|
userInputs.Add(input.Value);
|
||||||
|
if (input.Key.Equals("run"))
|
||||||
|
{
|
||||||
|
runValue = input.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var contents = runValue ?? string.Empty;
|
||||||
|
if (Action.Type == Pipelines.ActionSourceType.Repository)
|
||||||
|
{
|
||||||
|
var firstLine = contents.TrimStart(' ', '\t', '\r', '\n');
|
||||||
|
var firstNewLine = firstLine.IndexOfAny(new[] { '\r', '\n' });
|
||||||
|
if (firstNewLine >= 0)
|
||||||
|
{
|
||||||
|
firstLine = firstLine.Substring(0, firstNewLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutionContext.Output($"##[group]Run {firstLine}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Invalid action type {Action.Type} for {nameof(ScriptHandler)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var multiLines = contents.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
|
||||||
|
foreach (var line in multiLines)
|
||||||
|
{
|
||||||
|
// Bright Cyan color
|
||||||
|
ExecutionContext.Output($"\x1b[36;1m{line}\x1b[0m");
|
||||||
|
}
|
||||||
|
|
||||||
|
string argFormat;
|
||||||
|
string shellCommand;
|
||||||
|
string shellCommandPath = null;
|
||||||
|
bool validateShellOnHost = !(StepHost is ContainerStepHost);
|
||||||
|
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
||||||
|
string shell = null;
|
||||||
|
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
runDefaults.TryGetValue("shell", out shell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (string.IsNullOrEmpty(shell))
|
||||||
|
{
|
||||||
|
#if OS_WINDOWS
|
||||||
|
shellCommand = "pwsh";
|
||||||
|
if (validateShellOnHost)
|
||||||
|
{
|
||||||
|
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||||
|
if (string.IsNullOrEmpty(shellCommandPath))
|
||||||
|
{
|
||||||
|
shellCommand = "powershell";
|
||||||
|
Trace.Info($"Defaulting to {shellCommand}");
|
||||||
|
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
shellCommand = "sh";
|
||||||
|
if (validateShellOnHost)
|
||||||
|
{
|
||||||
|
shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||||
|
shellCommand = parsed.shellCommand;
|
||||||
|
if (validateShellOnHost)
|
||||||
|
{
|
||||||
|
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||||
|
if (string.IsNullOrEmpty(argFormat))
|
||||||
|
{
|
||||||
|
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(shellCommandPath))
|
||||||
|
{
|
||||||
|
ExecutionContext.Output($"shell: {shellCommandPath} {argFormat}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecutionContext.Output($"shell: {shellCommand} {argFormat}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.Environment?.Count > 0)
|
||||||
|
{
|
||||||
|
ExecutionContext.Output("env:");
|
||||||
|
foreach (var env in this.Environment)
|
||||||
|
{
|
||||||
|
ExecutionContext.Output($" {env.Key}: {env.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecutionContext.Output("##[endgroup]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RunAsync(ActionRunStage stage)
|
||||||
|
{
|
||||||
|
// DELETE LATER
|
||||||
|
// await Task.Yield();
|
||||||
|
|
||||||
|
|
||||||
|
// Basically, the only difference from ScriptHandler.cs is that "contents" is not just each step under "run: "
|
||||||
|
// It might make more sense to:
|
||||||
|
// 1) Abstract the core functionality of the ScriptHandler.cs that we need for BOTH CompositeActionHandler.cs and ScriptHandler.cs
|
||||||
|
// 2) Call those functions in both handlers
|
||||||
|
// * There is already a file called ScriptHandlerHelpers.cs that might be a good location to add more functions.
|
||||||
|
|
||||||
|
// Copied from ScriptHandler.cs
|
||||||
|
// Validate args.
|
||||||
|
Trace.Entering();
|
||||||
|
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
|
||||||
|
ArgUtil.NotNull(Inputs, nameof(Inputs));
|
||||||
|
|
||||||
|
var githubContext = ExecutionContext.ExpressionValues["github"] as GitHubContext;
|
||||||
|
ArgUtil.NotNull(githubContext, nameof(githubContext));
|
||||||
|
|
||||||
|
var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp);
|
||||||
|
|
||||||
|
// Resolve steps
|
||||||
|
var target = Data.Steps;
|
||||||
|
if (target == null) {
|
||||||
|
Trace.Error("Data.Steps in CompositeActionHandler is null");
|
||||||
|
} else {
|
||||||
|
Trace.Info($"Data Steps Value for Composite Actions is: {target}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// For now, just assume it is 1 Run step
|
||||||
|
// We will adapt this in the future.
|
||||||
|
// Copied from ActionRunner.cs RunAsync() function => Maybe we don't need a handler and need to avoid this preplicatoin in the future?
|
||||||
|
var runStepInputs = target[0].Inputs;
|
||||||
|
if (runStepInputs == null) {
|
||||||
|
Trace.Error("runStepInputs in CompositeActionHandler is null");
|
||||||
|
} else {
|
||||||
|
Trace.Info($"runStepInputs Value for Composite Actions is: {runStepInputs}.");
|
||||||
|
}
|
||||||
|
var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
|
var inputs = templateEvaluator.EvaluateStepInputs(runStepInputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions);
|
||||||
|
var taskManager = HostContext.GetService<IActionManager>();
|
||||||
|
|
||||||
|
var userInputs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var runValue = "";
|
||||||
|
foreach (KeyValuePair<string, string> input in inputs)
|
||||||
|
{
|
||||||
|
userInputs.Add(input.Key);
|
||||||
|
userInputs.Add(input.Value);
|
||||||
|
Trace.Info($"Composite Action Handler. Key: {input.Key} Value: {input.Value}");
|
||||||
|
// Why is the key should not be "run" => because the "run" keyword is recorgnized as something that will be a "script"?
|
||||||
|
// Or should we create another key that delineates between "script" and "run"?
|
||||||
|
// ^ Perhaps we can explore this in the next version with the changes the to action.yaml template
|
||||||
|
if (input.Key.Equals("script"))
|
||||||
|
{
|
||||||
|
runValue = input.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// string message = "";
|
||||||
|
// if (definition.Data?.Deprecated?.TryGetValue(input.Key, out message) == true)
|
||||||
|
// {
|
||||||
|
// ExecutionContext.Warning(String.Format("Input '{0}' has been deprecated with message: {1}", input.Key, message));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Get the run bash value that we want to run
|
||||||
|
// In the future, we would apply and validate the template => maybe using the manifest manager to recursively load the json schema.
|
||||||
|
}
|
||||||
|
|
||||||
|
Trace.Info($"Run Value for Composite Actions is: {runValue}.");
|
||||||
|
|
||||||
|
// Let's think about validating inputs later
|
||||||
|
// Validate inputs only for actions with action.yml
|
||||||
|
// var unexpectedInputs = new List<string>();
|
||||||
|
// foreach (var input in userInputs)
|
||||||
|
// {
|
||||||
|
// if (!validInputs.Contains(input))
|
||||||
|
// {
|
||||||
|
// unexpectedInputs.Add(input);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (unexpectedInputs.Count > 0)
|
||||||
|
// {
|
||||||
|
// ExecutionContext.Warning($"Unexpected input(s) '{string.Join("', '", unexpectedInputs)}', valid inputs are ['{string.Join("', '", validInputs)}']");
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
//TODO:
|
||||||
|
// 6/11/20 EOD thoughts
|
||||||
|
// => What functions do I need to use from ScriptHandlerHelpers.cs?
|
||||||
|
// Do I need to Reverse the string for prepending the path??
|
||||||
|
// ^ why or why not?
|
||||||
|
// How do I process the Inputs?
|
||||||
|
// => It's a TemplateToken
|
||||||
|
// How do I incorporate Async? we call the StepHost for Async when you call ExecuteAsync()
|
||||||
|
|
||||||
|
// Detect operating system for fileName + arguments
|
||||||
|
|
||||||
|
var contents = runValue ?? string.Empty;
|
||||||
|
|
||||||
|
string workingDirectory = null;
|
||||||
|
if (!Inputs.TryGetValue("workingDirectory", out workingDirectory))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
if (runDefaults.TryGetValue("working-directory", out workingDirectory))
|
||||||
|
{
|
||||||
|
ExecutionContext.Debug("Overwrite 'working-directory' base on job defaults.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var workspaceDir = githubContext["workspace"] as StringContextData;
|
||||||
|
workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty);
|
||||||
|
|
||||||
|
|
||||||
|
string shell = null;
|
||||||
|
if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell))
|
||||||
|
{
|
||||||
|
// TODO: figure out how defaults interact with template later
|
||||||
|
// for now, we won't check job.defaults if we are inside a template.
|
||||||
|
if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults))
|
||||||
|
{
|
||||||
|
if (runDefaults.TryGetValue("shell", out shell))
|
||||||
|
{
|
||||||
|
ExecutionContext.Debug("Overwrite 'shell' base on job defaults.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isContainerStepHost = StepHost is ContainerStepHost;
|
||||||
|
|
||||||
|
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
|
||||||
|
string commandPath, argFormat, shellCommand;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(shell))
|
||||||
|
{
|
||||||
|
#if OS_WINDOWS
|
||||||
|
shellCommand = "pwsh";
|
||||||
|
commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
|
||||||
|
if (string.IsNullOrEmpty(commandPath))
|
||||||
|
{
|
||||||
|
shellCommand = "powershell";
|
||||||
|
Trace.Info($"Defaulting to {shellCommand}");
|
||||||
|
commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
|
||||||
|
}
|
||||||
|
ArgUtil.NotNullOrEmpty(commandPath, "Default Shell");
|
||||||
|
#else
|
||||||
|
shellCommand = "sh";
|
||||||
|
commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
|
||||||
|
#endif
|
||||||
|
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
|
||||||
|
shellCommand = parsed.shellCommand;
|
||||||
|
// For non-ContainerStepHost, the command must be located on the host by Which
|
||||||
|
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
|
||||||
|
argFormat = $"{parsed.shellArgs}".TrimStart();
|
||||||
|
if (string.IsNullOrEmpty(argFormat))
|
||||||
|
{
|
||||||
|
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No arg format was given, shell must be a built-in
|
||||||
|
if (string.IsNullOrEmpty(argFormat) || !argFormat.Contains("{0}"))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid shell option. Shell must be a valid built-in (bash, sh, cmd, powershell, pwsh) or a format string containing '{0}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not not the full path until we know what shell is being used, so that we can determine the file extension
|
||||||
|
var scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}");
|
||||||
|
var resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}";
|
||||||
|
|
||||||
|
// Format arg string with script path
|
||||||
|
var arguments = string.Format(argFormat, resolvedScriptPath);
|
||||||
|
|
||||||
|
// Fix up and write the script
|
||||||
|
contents = ScriptHandlerHelpers.FixUpScriptContents(shellCommand, contents);
|
||||||
|
#if OS_WINDOWS
|
||||||
|
// Normalize Windows line endings
|
||||||
|
contents = contents.Replace("\r\n", "\n").Replace("\n", "\r\n");
|
||||||
|
var encoding = ExecutionContext.Variables.Retain_Default_Encoding && Console.InputEncoding.CodePage != 65001
|
||||||
|
? Console.InputEncoding
|
||||||
|
: new UTF8Encoding(false);
|
||||||
|
#else
|
||||||
|
// Don't add a BOM. It causes the script to fail on some operating systems (e.g. on Ubuntu 14).
|
||||||
|
var encoding = new UTF8Encoding(false);
|
||||||
|
#endif
|
||||||
|
// Script is written to local path (ie host) but executed relative to the StepHost, which may be a container
|
||||||
|
File.WriteAllText(scriptFilePath, contents, encoding);
|
||||||
|
|
||||||
|
// Prepend PATH
|
||||||
|
AddPrependPathToEnvironment();
|
||||||
|
|
||||||
|
// expose context to environment
|
||||||
|
foreach (var context in ExecutionContext.ExpressionValues)
|
||||||
|
{
|
||||||
|
if (context.Value is IEnvironmentContextData runtimeContext && runtimeContext != null)
|
||||||
|
{
|
||||||
|
foreach (var env in runtimeContext.GetRuntimeEnvironmentVariables())
|
||||||
|
{
|
||||||
|
Environment[env.Key] = env.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump out the command
|
||||||
|
var fileName = isContainerStepHost ? shellCommand : commandPath;
|
||||||
|
#if OS_OSX
|
||||||
|
if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS
|
||||||
|
{
|
||||||
|
// launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process
|
||||||
|
string node12 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}");
|
||||||
|
string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js");
|
||||||
|
arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}";
|
||||||
|
fileName = node12;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ExecutionContext.Debug($"{fileName} {arguments}");
|
||||||
|
|
||||||
|
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||||
|
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
|
||||||
|
{
|
||||||
|
StepHost.OutputDataReceived += stdoutManager.OnDataReceived;
|
||||||
|
StepHost.ErrorDataReceived += stderrManager.OnDataReceived;
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
int exitCode = await StepHost.ExecuteAsync(workingDirectory: StepHost.ResolvePathForStepHost(workingDirectory),
|
||||||
|
fileName: fileName,
|
||||||
|
arguments: arguments,
|
||||||
|
environment: Environment,
|
||||||
|
requireExitCodeZero: false,
|
||||||
|
outputEncoding: null,
|
||||||
|
killProcessOnCancel: false,
|
||||||
|
inheritConsoleHandler: !ExecutionContext.Variables.Retain_Default_Encoding,
|
||||||
|
cancellationToken: ExecutionContext.CancellationToken);
|
||||||
|
|
||||||
|
// Error
|
||||||
|
if (exitCode != 0)
|
||||||
|
{
|
||||||
|
ExecutionContext.Error($"Process completed with exit code {exitCode}.");
|
||||||
|
ExecutionContext.Result = TaskResult.Failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,14 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
handler = HostContext.CreateService<IRunnerPluginHandler>();
|
handler = HostContext.CreateService<IRunnerPluginHandler>();
|
||||||
(handler as IRunnerPluginHandler).Data = data as PluginActionExecutionData;
|
(handler as IRunnerPluginHandler).Data = data as PluginActionExecutionData;
|
||||||
}
|
}
|
||||||
|
else if (data.ExecutionType == ActionExecutionType.Composite)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
// Runner plugin
|
||||||
|
handler = HostContext.CreateService<ICompositeActionHandler>();
|
||||||
|
// handler = CompositeHandler;
|
||||||
|
(handler as ICompositeActionHandler).Data = data as CompositeActionExecutionData;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// This should never happen.
|
// This should never happen.
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
actionRunner.TryEvaluateDisplayName(contextData, context);
|
actionRunner.TryEvaluateDisplayName(contextData, context);
|
||||||
|
|
||||||
jobSteps.Add(actionRunner);
|
jobSteps.Add(actionRunner);
|
||||||
|
|
||||||
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
|
if (prepareResult.PreStepTracker.TryGetValue(step.Id, out var preStep))
|
||||||
@@ -284,6 +285,13 @@ namespace GitHub.Runner.Worker
|
|||||||
intraActionStates.TryGetValue(actionStep.Action.Id, out var intraActionState);
|
intraActionStates.TryGetValue(actionStep.Action.Id, out var intraActionState);
|
||||||
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName, intraActionState);
|
actionStep.ExecutionContext = jobContext.CreateChild(actionStep.Action.Id, actionStep.DisplayName, actionStep.Action.Name, actionStep.Action.ScopeName, actionStep.Action.ContextName, intraActionState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Maybe add EvaluateStep stuff here too?
|
||||||
|
// TODO: INSERT CONVERTSTEPS FUNCTION HERE FOR EVALUATING STEPS
|
||||||
|
// Maybe we don't need to do this here do we need to initialize the job?
|
||||||
|
// context.Debug("Evaluating job evaluating steps");
|
||||||
|
// var stepsEvaluation = templateEvaluator.EvaluateSteps(contextData, context, context.ExpressionFunctions);
|
||||||
|
////////
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IStep> steps = new List<IStep>();
|
List<IStep> steps = new List<IStep>();
|
||||||
|
|||||||
@@ -60,9 +60,11 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
// Create the job execution context.
|
// Create the job execution context.
|
||||||
jobContext = HostContext.CreateService<IExecutionContext>();
|
jobContext = HostContext.CreateService<IExecutionContext>();
|
||||||
|
// HERE
|
||||||
jobContext.InitializeJob(message, jobRequestCancellationToken);
|
jobContext.InitializeJob(message, jobRequestCancellationToken);
|
||||||
Trace.Info("Starting the job execution context.");
|
Trace.Info("Starting the job execution context.");
|
||||||
jobContext.Start();
|
jobContext.Start();
|
||||||
|
// User will see this message too
|
||||||
jobContext.Debug($"Starting: {message.JobDisplayName}");
|
jobContext.Debug($"Starting: {message.JobDisplayName}");
|
||||||
|
|
||||||
runnerShutdownRegistration = HostContext.RunnerShutdownToken.Register(() =>
|
runnerShutdownRegistration = HostContext.RunnerShutdownToken.Register(() =>
|
||||||
|
|||||||
@@ -32,7 +32,8 @@
|
|||||||
"one-of": [
|
"one-of": [
|
||||||
"container-runs",
|
"container-runs",
|
||||||
"node12-runs",
|
"node12-runs",
|
||||||
"plugin-runs"
|
"plugin-runs",
|
||||||
|
"composite-runs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"container-runs": {
|
"container-runs": {
|
||||||
@@ -83,6 +84,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"composite-runs": {
|
||||||
|
"mapping" : {
|
||||||
|
"properties": {
|
||||||
|
"using": "non-empty-string",
|
||||||
|
"steps": "steps"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps": {
|
||||||
|
"sequence": {
|
||||||
|
"item-type": "steps-item"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps-item": {
|
||||||
|
"one-of": [
|
||||||
|
"run-step"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"run-step": {
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"run": "non-empty-string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"container-runs-context": {
|
"container-runs-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"inputs"
|
"inputs"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
{
|
{
|
||||||
internal static class PipelineTemplateConverter
|
internal static class PipelineTemplateConverter
|
||||||
{
|
{
|
||||||
|
|
||||||
internal static Boolean ConvertToIfResult(
|
internal static Boolean ConvertToIfResult(
|
||||||
TemplateContext context,
|
TemplateContext context,
|
||||||
TemplateToken ifResult)
|
TemplateToken ifResult)
|
||||||
@@ -29,7 +30,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult);
|
var evaluationResult = EvaluationResult.CreateIntermediateResult(null, ifResult);
|
||||||
return evaluationResult.IsTruthy;
|
return evaluationResult.IsTruthy;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Boolean? ConvertToStepContinueOnError(
|
internal static Boolean? ConvertToStepContinueOnError(
|
||||||
TemplateContext context,
|
TemplateContext context,
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
@@ -264,5 +264,351 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Note: originally was List<Step> but we need to change to List<ActionStep> to use the "Inputs" attribute
|
||||||
|
internal static List<ActionStep> ConvertToSteps(
|
||||||
|
TemplateContext context,
|
||||||
|
TemplateToken steps)
|
||||||
|
{
|
||||||
|
var stepsSequence = steps.AssertSequence($"job {PipelineTemplateConstants.Steps}");
|
||||||
|
|
||||||
|
var result = new List<ActionStep>();
|
||||||
|
foreach (var stepsItem in stepsSequence)
|
||||||
|
{
|
||||||
|
var step = ConvertToStep(context, stepsItem);
|
||||||
|
if (step != null) // step = null means we are hitting error during step conversion, there should be an error in context.errors
|
||||||
|
{
|
||||||
|
if (step.Enabled)
|
||||||
|
{
|
||||||
|
result.Add(step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActionStep ConvertToStep(
|
||||||
|
TemplateContext context,
|
||||||
|
TemplateToken stepsItem)
|
||||||
|
{
|
||||||
|
var step = stepsItem.AssertMapping($"{PipelineTemplateConstants.Steps} item");
|
||||||
|
var continueOnError = default(ScalarToken);
|
||||||
|
var env = default(TemplateToken);
|
||||||
|
var id = default(StringToken);
|
||||||
|
var ifCondition = default(String);
|
||||||
|
var ifToken = default(ScalarToken);
|
||||||
|
var name = default(ScalarToken);
|
||||||
|
var run = default(ScalarToken);
|
||||||
|
var scope = default(StringToken);
|
||||||
|
var timeoutMinutes = default(ScalarToken);
|
||||||
|
var uses = default(StringToken);
|
||||||
|
var with = default(TemplateToken);
|
||||||
|
var workingDir = default(ScalarToken);
|
||||||
|
var path = default(ScalarToken);
|
||||||
|
var clean = default(ScalarToken);
|
||||||
|
var fetchDepth = default(ScalarToken);
|
||||||
|
var lfs = default(ScalarToken);
|
||||||
|
var submodules = default(ScalarToken);
|
||||||
|
var shell = default(ScalarToken);
|
||||||
|
|
||||||
|
foreach (var stepProperty in step)
|
||||||
|
{
|
||||||
|
var propertyName = stepProperty.Key.AssertString($"{PipelineTemplateConstants.Steps} item key");
|
||||||
|
|
||||||
|
switch (propertyName.Value)
|
||||||
|
{
|
||||||
|
case PipelineTemplateConstants.Clean:
|
||||||
|
clean = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Clean}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.ContinueOnError:
|
||||||
|
ConvertToStepContinueOnError(context, stepProperty.Value, allowExpressions: true); // Validate early if possible
|
||||||
|
continueOnError = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} {PipelineTemplateConstants.ContinueOnError}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Env:
|
||||||
|
ConvertToStepEnvironment(context, stepProperty.Value, StringComparer.Ordinal, allowExpressions: true); // Validate early if possible
|
||||||
|
env = stepProperty.Value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.FetchDepth:
|
||||||
|
fetchDepth = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.FetchDepth}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Id:
|
||||||
|
id = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Id}");
|
||||||
|
if (!NameValidation.IsValid(id.Value, true))
|
||||||
|
{
|
||||||
|
context.Error(id, $"Step id {id.Value} is invalid. Ids must start with a letter or '_' and contain only alphanumeric characters, '-', or '_'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.If:
|
||||||
|
ifToken = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.If}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Lfs:
|
||||||
|
lfs = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Lfs}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Name:
|
||||||
|
name = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Name}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Path:
|
||||||
|
path = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Path}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Run:
|
||||||
|
run = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Run}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Shell:
|
||||||
|
shell = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Shell}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Scope:
|
||||||
|
scope = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Scope}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Submodules:
|
||||||
|
submodules = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Submodules}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.TimeoutMinutes:
|
||||||
|
ConvertToStepTimeout(context, stepProperty.Value, allowExpressions: true); // Validate early if possible
|
||||||
|
timeoutMinutes = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.TimeoutMinutes}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.Uses:
|
||||||
|
uses = stepProperty.Value.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Uses}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.With:
|
||||||
|
ConvertToStepInputs(context, stepProperty.Value, allowExpressions: true); // Validate early if possible
|
||||||
|
with = stepProperty.Value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PipelineTemplateConstants.WorkingDirectory:
|
||||||
|
workingDir = stepProperty.Value.AssertScalar($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.WorkingDirectory}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
propertyName.AssertUnexpectedValue($"{PipelineTemplateConstants.Steps} item key"); // throws
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixup the if-condition
|
||||||
|
var isDefaultScope = String.IsNullOrEmpty(scope?.Value);
|
||||||
|
ifCondition = ConvertToIfCondition(context, ifToken, false, isDefaultScope);
|
||||||
|
|
||||||
|
if (run != null)
|
||||||
|
{
|
||||||
|
var result = new ActionStep
|
||||||
|
{
|
||||||
|
ScopeName = scope?.Value,
|
||||||
|
ContextName = id?.Value,
|
||||||
|
ContinueOnError = continueOnError,
|
||||||
|
DisplayNameToken = name,
|
||||||
|
Condition = ifCondition,
|
||||||
|
TimeoutInMinutes = timeoutMinutes,
|
||||||
|
Environment = env,
|
||||||
|
Reference = new ScriptReference(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var inputs = new MappingToken(null, null, null);
|
||||||
|
inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.Script), run);
|
||||||
|
|
||||||
|
if (workingDir != null)
|
||||||
|
{
|
||||||
|
inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.WorkingDirectory), workingDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shell != null)
|
||||||
|
{
|
||||||
|
inputs.Add(new StringToken(null, null, null, PipelineConstants.ScriptStepInputs.Shell), shell);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Inputs = inputs;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uses.AssertString($"{PipelineTemplateConstants.Steps} item {PipelineTemplateConstants.Uses}");
|
||||||
|
var result = new ActionStep
|
||||||
|
{
|
||||||
|
ScopeName = scope?.Value,
|
||||||
|
ContextName = id?.Value,
|
||||||
|
ContinueOnError = continueOnError,
|
||||||
|
DisplayNameToken = name,
|
||||||
|
Condition = ifCondition,
|
||||||
|
TimeoutInMinutes = timeoutMinutes,
|
||||||
|
Inputs = with,
|
||||||
|
Environment = env,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (uses.Value.StartsWith("docker://", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var image = uses.Value.Substring("docker://".Length);
|
||||||
|
result.Reference = new ContainerRegistryReference { Image = image };
|
||||||
|
}
|
||||||
|
else if (uses.Value.StartsWith("./") || uses.Value.StartsWith(".\\"))
|
||||||
|
{
|
||||||
|
result.Reference = new RepositoryPathReference
|
||||||
|
{
|
||||||
|
RepositoryType = PipelineConstants.SelfAlias,
|
||||||
|
Path = uses.Value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var usesSegments = uses.Value.Split('@');
|
||||||
|
var pathSegments = usesSegments[0].Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var gitRef = usesSegments.Length == 2 ? usesSegments[1] : String.Empty;
|
||||||
|
|
||||||
|
if (usesSegments.Length != 2 ||
|
||||||
|
pathSegments.Length < 2 ||
|
||||||
|
String.IsNullOrEmpty(pathSegments[0]) ||
|
||||||
|
String.IsNullOrEmpty(pathSegments[1]) ||
|
||||||
|
String.IsNullOrEmpty(gitRef))
|
||||||
|
{
|
||||||
|
// todo: loc
|
||||||
|
context.Error(uses, $"Expected format {{org}}/{{repo}}[/path]@ref. Actual '{uses.Value}'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var repositoryName = $"{pathSegments[0]}/{pathSegments[1]}";
|
||||||
|
var directoryPath = pathSegments.Length > 2 ? String.Join("/", pathSegments.Skip(2)) : String.Empty;
|
||||||
|
|
||||||
|
result.Reference = new RepositoryPathReference
|
||||||
|
{
|
||||||
|
RepositoryType = RepositoryTypes.GitHub,
|
||||||
|
Name = repositoryName,
|
||||||
|
Ref = gitRef,
|
||||||
|
Path = directoryPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When empty, default to "success()".
|
||||||
|
/// When a status function is not referenced, format as "success() && <CONDITION>".
|
||||||
|
/// </summary>
|
||||||
|
private static String ConvertToIfCondition(
|
||||||
|
TemplateContext context,
|
||||||
|
TemplateToken token,
|
||||||
|
Boolean isJob,
|
||||||
|
Boolean isDefaultScope)
|
||||||
|
{
|
||||||
|
String condition;
|
||||||
|
if (token is null)
|
||||||
|
{
|
||||||
|
condition = null;
|
||||||
|
}
|
||||||
|
else if (token is BasicExpressionToken expressionToken)
|
||||||
|
{
|
||||||
|
condition = expressionToken.Expression;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var stringToken = token.AssertString($"{(isJob ? "job" : "step")} {PipelineTemplateConstants.If}");
|
||||||
|
condition = stringToken.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(condition))
|
||||||
|
{
|
||||||
|
return $"{PipelineTemplateConstants.Success}()";
|
||||||
|
}
|
||||||
|
|
||||||
|
var expressionParser = new ExpressionParser();
|
||||||
|
var functions = default(IFunctionInfo[]);
|
||||||
|
var namedValues = default(INamedValueInfo[]);
|
||||||
|
if (isJob)
|
||||||
|
{
|
||||||
|
namedValues = s_jobIfNamedValues;
|
||||||
|
// TODO: refactor into seperate functions
|
||||||
|
// functions = PhaseCondition.FunctionInfo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
namedValues = isDefaultScope ? s_stepNamedValues : s_stepInTemplateNamedValues;
|
||||||
|
functions = s_stepConditionFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
var node = default(ExpressionNode);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
node = expressionParser.CreateTree(condition, null, namedValues, functions) as ExpressionNode;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
context.Error(token, ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
return $"{PipelineTemplateConstants.Success}()";
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasStatusFunction = node.Traverse().Any(x =>
|
||||||
|
{
|
||||||
|
if (x is Function function)
|
||||||
|
{
|
||||||
|
return String.Equals(function.Name, PipelineTemplateConstants.Always, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
String.Equals(function.Name, PipelineTemplateConstants.Cancelled, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
String.Equals(function.Name, PipelineTemplateConstants.Failure, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
String.Equals(function.Name, PipelineTemplateConstants.Success, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasStatusFunction ? condition : $"{PipelineTemplateConstants.Success}() && ({condition})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly INamedValueInfo[] s_jobIfNamedValues = new INamedValueInfo[]
|
||||||
|
{
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.GitHub),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Needs),
|
||||||
|
};
|
||||||
|
private static readonly INamedValueInfo[] s_stepNamedValues = new INamedValueInfo[]
|
||||||
|
{
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Strategy),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Matrix),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Steps),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.GitHub),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Job),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Runner),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Env),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Needs),
|
||||||
|
};
|
||||||
|
private static readonly INamedValueInfo[] s_stepInTemplateNamedValues = new INamedValueInfo[]
|
||||||
|
{
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Strategy),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Matrix),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Steps),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Inputs),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.GitHub),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Job),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Runner),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Env),
|
||||||
|
new NamedValueInfo<NoOperationNamedValue>(PipelineTemplateConstants.Needs),
|
||||||
|
};
|
||||||
|
private static readonly IFunctionInfo[] s_stepConditionFunctions = new IFunctionInfo[]
|
||||||
|
{
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.Always, 0, 0),
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.Cancelled, 0, 0),
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.Failure, 0, 0),
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.Success, 0, 0),
|
||||||
|
new FunctionInfo<NoOperation>(PipelineTemplateConstants.HashFiles, 1, Byte.MaxValue),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,44 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add function here that says Evaluate Steps but it will return whatever ConvertToSteps returns.
|
||||||
|
// return: List<ActionStep>
|
||||||
|
// used to be evaluatesteps
|
||||||
|
public List<ActionStep> LoadSteps(
|
||||||
|
TemplateToken token,
|
||||||
|
DictionaryContextData contextData,
|
||||||
|
IList<IFunctionInfo> expressionFunctions
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Similar to EvaluateStepDisplayName() except that we pass in PipelineTemplateConstants.Steps as the type for evaluation
|
||||||
|
var result = default(List<ActionStep>);
|
||||||
|
|
||||||
|
if (token != null && token.Type != TokenType.Null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(contextData, expressionFunctions, setMissingContext: false);
|
||||||
|
// This will keep it from preexpanding
|
||||||
|
// TODO: we might want to to have a bool to prevent it from filling in with missing context w/ dummy variables
|
||||||
|
// context.ExpressionFunctions.Clear();
|
||||||
|
// context.ExpressionValues.Clear();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Steps, token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = PipelineTemplateConverter.ConvertToSteps(context, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||||
|
{
|
||||||
|
context.Errors.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Errors.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Dictionary<String, String> EvaluateStepEnvironment(
|
public Dictionary<String, String> EvaluateStepEnvironment(
|
||||||
TemplateToken token,
|
TemplateToken token,
|
||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
@@ -400,7 +438,8 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
private TemplateContext CreateContext(
|
private TemplateContext CreateContext(
|
||||||
DictionaryContextData contextData,
|
DictionaryContextData contextData,
|
||||||
IList<IFunctionInfo> expressionFunctions,
|
IList<IFunctionInfo> expressionFunctions,
|
||||||
IEnumerable<KeyValuePair<String, Object>> expressionState = null)
|
IEnumerable<KeyValuePair<String, Object>> expressionState = null,
|
||||||
|
bool setMissingContext = true)
|
||||||
{
|
{
|
||||||
var result = new TemplateContext
|
var result = new TemplateContext
|
||||||
{
|
{
|
||||||
@@ -449,6 +488,8 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
// - Evaluating early when all referenced contexts are available, even though all allowed
|
// - Evaluating early when all referenced contexts are available, even though all allowed
|
||||||
// contexts may not yet be available. For example, evaluating step display name can often
|
// contexts may not yet be available. For example, evaluating step display name can often
|
||||||
// be performed early.
|
// be performed early.
|
||||||
|
if (setMissingContext)
|
||||||
|
{
|
||||||
foreach (var name in s_expressionValueNames)
|
foreach (var name in s_expressionValueNames)
|
||||||
{
|
{
|
||||||
if (!result.ExpressionValues.ContainsKey(name))
|
if (!result.ExpressionValues.ContainsKey(name))
|
||||||
@@ -463,6 +504,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
|||||||
result.ExpressionFunctions.Add(new FunctionInfo<NoOperation>(name, 0, Int32.MaxValue));
|
result.ExpressionFunctions.Add(new FunctionInfo<NoOperation>(name, 0, Int32.MaxValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add state
|
// Add state
|
||||||
if (expressionState != null)
|
if (expressionState != null)
|
||||||
|
|||||||
@@ -94,5 +94,12 @@ namespace GitHub.DistributedTask.Pipelines
|
|||||||
public static readonly String Resources = "resources";
|
public static readonly String Resources = "resources";
|
||||||
public static readonly String All = "all";
|
public static readonly String All = "all";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ScriptStepInputs
|
||||||
|
{
|
||||||
|
public static readonly String Script = "script";
|
||||||
|
public static readonly String WorkingDirectory = "workingDirectory";
|
||||||
|
public static readonly String Shell = "shell";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
src/Sdk/DTWebApi/WebApi/ActionDownloadInfo.cs
Normal file
40
src/Sdk/DTWebApi/WebApi/ActionDownloadInfo.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Sdk/DTWebApi/WebApi/ActionDownloadInfoCollection.cs
Normal file
16
src/Sdk/DTWebApi/WebApi/ActionDownloadInfoCollection.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/Sdk/DTWebApi/WebApi/ActionReference.cs
Normal file
22
src/Sdk/DTWebApi/WebApi/ActionReference.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Sdk/DTWebApi/WebApi/ActionReferenceList.cs
Normal file
16
src/Sdk/DTWebApi/WebApi/ActionReferenceList.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
|||||||
51
src/dev.sh
51
src/dev.sh
@@ -173,57 +173,6 @@ function package ()
|
|||||||
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"${window_path}\", \"${zip_name}\")"
|
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"${window_path}\", \"${zip_name}\")"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
runner_patch_pkg_name="actions-runner-${RUNTIME_ID}-${runner_ver}-patch"
|
|
||||||
|
|
||||||
heading "Packaging Patch Version ${runner_patch_pkg_name}"
|
|
||||||
|
|
||||||
echo "Downloading latest runner ..."
|
|
||||||
|
|
||||||
mkdir -p "_temp"
|
|
||||||
pushd "_temp" > /dev/null
|
|
||||||
|
|
||||||
latest_version_label=$(curl -s -X GET 'https://api.github.com/repos/actions/runner/releases/latest' | jq -r '.tag_name')
|
|
||||||
latest_version=$(echo ${latest_version_label:1})
|
|
||||||
latest_version_runner_file=""
|
|
||||||
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
|
|
||||||
latest_version_runner_file="actions-runner-${RUNTIME_ID}-${latest_version}.tar.gz"
|
|
||||||
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
|
|
||||||
latest_version_runner_file="actions-runner-${RUNTIME_ID}-${latest_version}.zip"
|
|
||||||
fi
|
|
||||||
|
|
||||||
latest_version_download_url="https://github.com/actions/runner/releases/download/${latest_version_label}/${latest_version_runner_file}"
|
|
||||||
|
|
||||||
echo "Downloading ${latest_version_label} for ${RUNTIME_ID} ..."
|
|
||||||
echo $latest_version_download_url
|
|
||||||
|
|
||||||
curl -O -L ${latest_version_download_url}
|
|
||||||
|
|
||||||
mkdir -p "latest_runner"
|
|
||||||
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
|
|
||||||
tar xzf "./${latest_version_runner_file}" -C latest_runner
|
|
||||||
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
|
|
||||||
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::ExtractToDirectory(\"./${latest_version_runner_file}\", \"latest_runner\")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "_patch"
|
|
||||||
diff -qrN "${LAYOUT_DIR}" "latest_runner" | cut -d " " -f 2 | xargs -t -I file cp file "_patch/"$(echo file | cut -c ${#LAYOUT_DIR}- | cut -c 3-)
|
|
||||||
|
|
||||||
popd > /dev/null
|
|
||||||
|
|
||||||
if [[ ("$CURRENT_PLATFORM" == "linux") || ("$CURRENT_PLATFORM" == "darwin") ]]; then
|
|
||||||
patch_tar_name="${runner_patch_pkg_name}.tar.gz"
|
|
||||||
echo "Creating $patch_tar_name in ${PACKAGE_DIR}/_temp/_patch"
|
|
||||||
tar -czf "${patch_tar_name}" -C "${PACKAGE_DIR}/_temp/_patch" .
|
|
||||||
elif [[ ("$CURRENT_PLATFORM" == "windows") ]]; then
|
|
||||||
patch_zip_name="${runner_patch_pkg_name}.zip"
|
|
||||||
echo "Convert ${PACKAGE_DIR} to Windows style path"
|
|
||||||
window_path=${PACKAGE_DIR:1}
|
|
||||||
window_path=${window_path:0:1}:${window_path:1}
|
|
||||||
echo "Creating $patch_zip_name in ${window_path}/_temp/_patch"
|
|
||||||
powershell -NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "Add-Type -Assembly \"System.IO.Compression.FileSystem\"; [System.IO.Compression.ZipFile]::CreateFromDirectory(\"${window_path}\", \"${patch_zip_name}\")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -Rf "${PACKAGE_DIR}/_temp"
|
|
||||||
popd > /dev/null
|
popd > /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user