mirror of
https://github.com/actions/runner.git
synced 2025-12-11 04:46:58 +00:00
@@ -41,6 +41,8 @@ namespace GitHub.Runner.Common
|
|||||||
public static string PluginTracePrefix = "##[plugin.trace]";
|
public static string PluginTracePrefix = "##[plugin.trace]";
|
||||||
public static readonly int RunnerDownloadRetryMaxAttempts = 3;
|
public static readonly int RunnerDownloadRetryMaxAttempts = 3;
|
||||||
|
|
||||||
|
public static readonly int CompositeActionsMaxDepth = 9;
|
||||||
|
|
||||||
// This enum is embedded within the Constants class to make it easier to reference and avoid
|
// This enum is embedded within the Constants class to make it easier to reference and avoid
|
||||||
// ambiguous type reference with System.Runtime.InteropServices.OSPlatform and System.Runtime.InteropServices.Architecture
|
// ambiguous type reference with System.Runtime.InteropServices.OSPlatform and System.Runtime.InteropServices.Architecture
|
||||||
public enum OSPlatform
|
public enum OSPlatform
|
||||||
|
|||||||
@@ -53,30 +53,63 @@ namespace GitHub.Runner.Worker
|
|||||||
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
public Dictionary<Guid, ContainerInfo> CachedActionContainers => _cachedActionContainers;
|
||||||
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
public async Task<PrepareResult> PrepareActionsAsync(IExecutionContext executionContext, IEnumerable<Pipelines.JobStep> steps)
|
||||||
{
|
{
|
||||||
|
// Assert inputs
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
ArgUtil.NotNull(steps, nameof(steps));
|
ArgUtil.NotNull(steps, nameof(steps));
|
||||||
|
var state = new PrepareActionsState
|
||||||
executionContext.Output("Prepare all required actions");
|
|
||||||
Dictionary<string, List<Guid>> imagesToPull = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
Dictionary<string, List<Guid>> imagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
Dictionary<string, ActionContainer> imagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
List<JobExtensionRunner> containerSetupSteps = new List<JobExtensionRunner>();
|
|
||||||
Dictionary<Guid, IActionRunner> preStepTracker = new Dictionary<Guid, IActionRunner>();
|
|
||||||
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
|
||||||
|
|
||||||
// TODO: Deprecate the PREVIEW_ACTION_TOKEN
|
|
||||||
// Log even if we aren't using it to ensure users know.
|
|
||||||
if (!string.IsNullOrEmpty(executionContext.Global.Variables.Get("PREVIEW_ACTION_TOKEN")))
|
|
||||||
{
|
{
|
||||||
executionContext.Warning("The 'PREVIEW_ACTION_TOKEN' secret is deprecated. Please remove it from the repository's secrets");
|
ImagesToBuild = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase),
|
||||||
|
ImagesToPull = new Dictionary<string, List<Guid>>(StringComparer.OrdinalIgnoreCase),
|
||||||
|
ImagesToBuildInfo = new Dictionary<string, ActionContainer>(StringComparer.OrdinalIgnoreCase),
|
||||||
|
PreStepTracker = new Dictionary<Guid, IActionRunner>()
|
||||||
|
};
|
||||||
|
var containerSetupSteps = new List<JobExtensionRunner>();
|
||||||
|
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
||||||
|
IEnumerable<Pipelines.ActionStep> actions = steps.OfType<Pipelines.ActionStep>();
|
||||||
|
executionContext.Output("Prepare all required actions");
|
||||||
|
var result = await PrepareActionsRecursiveAsync(executionContext, state, actions, 0);
|
||||||
|
if (state.ImagesToPull.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var imageToPull in result.ImagesToPull)
|
||||||
|
{
|
||||||
|
Trace.Info($"{imageToPull.Value.Count} steps need to pull image '{imageToPull.Key}'");
|
||||||
|
containerSetupSteps.Add(new JobExtensionRunner(runAsync: this.PullActionContainerAsync,
|
||||||
|
condition: $"{PipelineTemplateConstants.Success}()",
|
||||||
|
displayName: $"Pull {imageToPull.Key}",
|
||||||
|
data: new ContainerSetupInfo(imageToPull.Value, imageToPull.Key)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the cache (for self-hosted runners)
|
if (result.ImagesToBuild.Count > 0)
|
||||||
IOUtil.DeleteDirectory(HostContext.GetDirectory(WellKnownDirectory.Actions), executionContext.CancellationToken);
|
{
|
||||||
|
foreach (var imageToBuild in result.ImagesToBuild)
|
||||||
|
{
|
||||||
|
var setupInfo = result.ImagesToBuildInfo[imageToBuild.Key];
|
||||||
|
Trace.Info($"{imageToBuild.Value.Count} steps need to build image from '{setupInfo.Dockerfile}'");
|
||||||
|
containerSetupSteps.Add(new JobExtensionRunner(runAsync: this.BuildActionContainerAsync,
|
||||||
|
condition: $"{PipelineTemplateConstants.Success}()",
|
||||||
|
displayName: $"Build {setupInfo.ActionRepository}",
|
||||||
|
data: new ContainerSetupInfo(imageToBuild.Value, setupInfo.Dockerfile, setupInfo.WorkingDirectory)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
#if !OS_LINUX
|
||||||
var newActionMetadata = executionContext.Global.Variables.GetBoolean("DistributedTask.NewActionMetadata") ?? false;
|
if (containerSetupSteps.Count > 0)
|
||||||
|
{
|
||||||
|
executionContext.Output("Container action is only supported on Linux, skip pull and build docker images.");
|
||||||
|
containerSetupSteps.Clear();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return new PrepareResult(containerSetupSteps, result.PreStepTracker);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<PrepareActionsState> PrepareActionsRecursiveAsync(IExecutionContext executionContext, PrepareActionsState state, IEnumerable<Pipelines.ActionStep> actions, Int32 depth = 0)
|
||||||
|
{
|
||||||
|
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
||||||
|
if (depth > Constants.CompositeActionsMaxDepth)
|
||||||
|
{
|
||||||
|
throw new Exception($"Composite action depth exceeded max depth {Constants.CompositeActionsMaxDepth}");
|
||||||
|
}
|
||||||
var repositoryActions = new List<Pipelines.ActionStep>();
|
var repositoryActions = new List<Pipelines.ActionStep>();
|
||||||
|
|
||||||
foreach (var action in actions)
|
foreach (var action in actions)
|
||||||
@@ -88,66 +121,15 @@ namespace GitHub.Runner.Worker
|
|||||||
ArgUtil.NotNull(containerReference, nameof(containerReference));
|
ArgUtil.NotNull(containerReference, nameof(containerReference));
|
||||||
ArgUtil.NotNullOrEmpty(containerReference.Image, nameof(containerReference.Image));
|
ArgUtil.NotNullOrEmpty(containerReference.Image, nameof(containerReference.Image));
|
||||||
|
|
||||||
if (!imagesToPull.ContainsKey(containerReference.Image))
|
if (!state.ImagesToPull.ContainsKey(containerReference.Image))
|
||||||
{
|
{
|
||||||
imagesToPull[containerReference.Image] = new List<Guid>();
|
state.ImagesToPull[containerReference.Image] = new List<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) needs to pull image '{containerReference.Image}'");
|
Trace.Info($"Action {action.Name} ({action.Id}) needs to pull image '{containerReference.Image}'");
|
||||||
imagesToPull[containerReference.Image].Add(action.Id);
|
state.ImagesToPull[containerReference.Image].Add(action.Id);
|
||||||
}
|
}
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository)
|
||||||
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository && !newActionMetadata)
|
|
||||||
{
|
|
||||||
// only download the repository archive
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, action);
|
|
||||||
|
|
||||||
// more preparation base on content in the repository (action.yml)
|
|
||||||
var setupInfo = PrepareRepositoryActionAsync(executionContext, action);
|
|
||||||
if (setupInfo != null)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(setupInfo.Image))
|
|
||||||
{
|
|
||||||
if (!imagesToPull.ContainsKey(setupInfo.Image))
|
|
||||||
{
|
|
||||||
imagesToPull[setupInfo.Image] = new List<Guid>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to pull image '{setupInfo.Image}'");
|
|
||||||
imagesToPull[setupInfo.Image].Add(action.Id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ArgUtil.NotNullOrEmpty(setupInfo.ActionRepository, nameof(setupInfo.ActionRepository));
|
|
||||||
|
|
||||||
if (!imagesToBuild.ContainsKey(setupInfo.ActionRepository))
|
|
||||||
{
|
|
||||||
imagesToBuild[setupInfo.ActionRepository] = new List<Guid>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to build image '{setupInfo.Dockerfile}'");
|
|
||||||
imagesToBuild[setupInfo.ActionRepository].Add(action.Id);
|
|
||||||
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
|
||||||
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
|
||||||
{
|
|
||||||
var definition = LoadAction(executionContext, action);
|
|
||||||
if (definition.Data.Execution.HasPre)
|
|
||||||
{
|
|
||||||
var actionRunner = HostContext.CreateService<IActionRunner>();
|
|
||||||
actionRunner.Action = action;
|
|
||||||
actionRunner.Stage = ActionRunStage.Pre;
|
|
||||||
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
|
||||||
|
|
||||||
Trace.Info($"Add 'pre' execution for {action.Id}");
|
|
||||||
preStepTracker[action.Id] = actionRunner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (action.Reference.Type == Pipelines.ActionSourceType.Repository && newActionMetadata)
|
|
||||||
{
|
{
|
||||||
repositoryActions.Add(action);
|
repositoryActions.Add(action);
|
||||||
}
|
}
|
||||||
@@ -179,38 +161,42 @@ namespace GitHub.Runner.Worker
|
|||||||
foreach (var action in repositoryActions)
|
foreach (var action in repositoryActions)
|
||||||
{
|
{
|
||||||
var setupInfo = PrepareRepositoryActionAsync(executionContext, action);
|
var setupInfo = PrepareRepositoryActionAsync(executionContext, action);
|
||||||
if (setupInfo != null)
|
if (setupInfo != null && setupInfo.Container != null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(setupInfo.Image))
|
if (!string.IsNullOrEmpty(setupInfo.Container.Image))
|
||||||
{
|
{
|
||||||
if (!imagesToPull.ContainsKey(setupInfo.Image))
|
if (!state.ImagesToPull.ContainsKey(setupInfo.Container.Image))
|
||||||
{
|
{
|
||||||
imagesToPull[setupInfo.Image] = new List<Guid>();
|
state.ImagesToPull[setupInfo.Container.Image] = new List<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to pull image '{setupInfo.Image}'");
|
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.Container.ActionRepository}' needs to pull image '{setupInfo.Container.Image}'");
|
||||||
imagesToPull[setupInfo.Image].Add(action.Id);
|
state.ImagesToPull[setupInfo.Container.Image].Add(action.Id);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(setupInfo.ActionRepository, nameof(setupInfo.ActionRepository));
|
ArgUtil.NotNullOrEmpty(setupInfo.Container.ActionRepository, nameof(setupInfo.Container.ActionRepository));
|
||||||
|
|
||||||
if (!imagesToBuild.ContainsKey(setupInfo.ActionRepository))
|
if (!state.ImagesToBuild.ContainsKey(setupInfo.Container.ActionRepository))
|
||||||
{
|
{
|
||||||
imagesToBuild[setupInfo.ActionRepository] = new List<Guid>();
|
state.ImagesToBuild[setupInfo.Container.ActionRepository] = new List<Guid>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.ActionRepository}' needs to build image '{setupInfo.Dockerfile}'");
|
Trace.Info($"Action {action.Name} ({action.Id}) from repository '{setupInfo.Container.ActionRepository}' needs to build image '{setupInfo.Container.Dockerfile}'");
|
||||||
imagesToBuild[setupInfo.ActionRepository].Add(action.Id);
|
state.ImagesToBuild[setupInfo.Container.ActionRepository].Add(action.Id);
|
||||||
imagesToBuildInfo[setupInfo.ActionRepository] = setupInfo;
|
state.ImagesToBuildInfo[setupInfo.Container.ActionRepository] = setupInfo.Container;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(setupInfo != null && setupInfo.Steps != null && setupInfo.Steps.Count > 0)
|
||||||
|
{
|
||||||
|
state = await PrepareActionsRecursiveAsync(executionContext, state, setupInfo.Steps, depth + 1);
|
||||||
|
}
|
||||||
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
var repoAction = action.Reference as Pipelines.RepositoryPathReference;
|
||||||
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
if (repoAction.RepositoryType != Pipelines.PipelineConstants.SelfAlias)
|
||||||
{
|
{
|
||||||
var definition = LoadAction(executionContext, action);
|
var definition = LoadAction(executionContext, action);
|
||||||
if (definition.Data.Execution.HasPre)
|
// TODO: Support pre's in composite actions
|
||||||
|
if (definition.Data.Execution.HasPre && depth < 1)
|
||||||
{
|
{
|
||||||
var actionRunner = HostContext.CreateService<IActionRunner>();
|
var actionRunner = HostContext.CreateService<IActionRunner>();
|
||||||
actionRunner.Action = action;
|
actionRunner.Action = action;
|
||||||
@@ -218,46 +204,13 @@ namespace GitHub.Runner.Worker
|
|||||||
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
actionRunner.Condition = definition.Data.Execution.InitCondition;
|
||||||
|
|
||||||
Trace.Info($"Add 'pre' execution for {action.Id}");
|
Trace.Info($"Add 'pre' execution for {action.Id}");
|
||||||
preStepTracker[action.Id] = actionRunner;
|
state.PreStepTracker[action.Id] = actionRunner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imagesToPull.Count > 0)
|
return state;
|
||||||
{
|
|
||||||
foreach (var imageToPull in imagesToPull)
|
|
||||||
{
|
|
||||||
Trace.Info($"{imageToPull.Value.Count} steps need to pull image '{imageToPull.Key}'");
|
|
||||||
containerSetupSteps.Add(new JobExtensionRunner(runAsync: this.PullActionContainerAsync,
|
|
||||||
condition: $"{PipelineTemplateConstants.Success}()",
|
|
||||||
displayName: $"Pull {imageToPull.Key}",
|
|
||||||
data: new ContainerSetupInfo(imageToPull.Value, imageToPull.Key)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imagesToBuild.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var imageToBuild in imagesToBuild)
|
|
||||||
{
|
|
||||||
var setupInfo = imagesToBuildInfo[imageToBuild.Key];
|
|
||||||
Trace.Info($"{imageToBuild.Value.Count} steps need to build image from '{setupInfo.Dockerfile}'");
|
|
||||||
containerSetupSteps.Add(new JobExtensionRunner(runAsync: this.BuildActionContainerAsync,
|
|
||||||
condition: $"{PipelineTemplateConstants.Success}()",
|
|
||||||
displayName: $"Build {setupInfo.ActionRepository}",
|
|
||||||
data: new ContainerSetupInfo(imageToBuild.Value, setupInfo.Dockerfile, setupInfo.WorkingDirectory)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !OS_LINUX
|
|
||||||
if (containerSetupSteps.Count > 0)
|
|
||||||
{
|
|
||||||
executionContext.Output("Container action is only supported on Linux, skip pull and build docker images.");
|
|
||||||
containerSetupSteps.Clear();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return new PrepareResult(containerSetupSteps, preStepTracker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
|
public Definition LoadAction(IExecutionContext executionContext, Pipelines.ActionStep action)
|
||||||
@@ -647,90 +600,6 @@ namespace GitHub.Runner.Worker
|
|||||||
return actionDownloadInfos.Actions;
|
return actionDownloadInfos.Actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
|
||||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
|
||||||
{
|
|
||||||
Trace.Entering();
|
|
||||||
ArgUtil.NotNull(executionContext, nameof(executionContext));
|
|
||||||
|
|
||||||
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
|
|
||||||
ArgUtil.NotNull(repositoryReference, nameof(repositoryReference));
|
|
||||||
|
|
||||||
if (string.Equals(repositoryReference.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
Trace.Info($"Repository action is in 'self' repository.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.Equals(repositoryReference.RepositoryType, Pipelines.RepositoryTypes.GitHub, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(repositoryReference.RepositoryType);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgUtil.NotNullOrEmpty(repositoryReference.Name, nameof(repositoryReference.Name));
|
|
||||||
ArgUtil.NotNullOrEmpty(repositoryReference.Ref, nameof(repositoryReference.Ref));
|
|
||||||
|
|
||||||
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
|
|
||||||
string watermarkFile = GetWatermarkFilePath(destDirectory);
|
|
||||||
if (File.Exists(watermarkFile))
|
|
||||||
{
|
|
||||||
executionContext.Debug($"Action '{repositoryReference.Name}@{repositoryReference.Ref}' already downloaded at '{destDirectory}'.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// make sure we get a clean folder ready to use.
|
|
||||||
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
|
||||||
Directory.CreateDirectory(destDirectory);
|
|
||||||
executionContext.Output($"Download action repository '{repositoryReference.Name}@{repositoryReference.Ref}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
var configurationStore = HostContext.GetService<IConfigurationStore>();
|
|
||||||
var isHostedServer = configurationStore.GetSettings().IsHostedServer;
|
|
||||||
if (isHostedServer)
|
|
||||||
{
|
|
||||||
string apiUrl = GetApiUrl(executionContext);
|
|
||||||
string archiveLink = BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref);
|
|
||||||
var downloadDetails = new ActionDownloadDetails(archiveLink, ConfigureAuthorizationFromContext);
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, downloadDetails, null, destDirectory);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string apiUrl = GetApiUrl(executionContext);
|
|
||||||
|
|
||||||
// URLs to try:
|
|
||||||
var downloadAttempts = new List<ActionDownloadDetails> {
|
|
||||||
// A built-in action or an action the user has created, on their GHES instance
|
|
||||||
// Example: https://my-ghes/api/v3/repos/my-org/my-action/tarball/v1
|
|
||||||
new ActionDownloadDetails(
|
|
||||||
BuildLinkToActionArchive(apiUrl, repositoryReference.Name, repositoryReference.Ref),
|
|
||||||
ConfigureAuthorizationFromContext),
|
|
||||||
|
|
||||||
// The same action, on GitHub.com
|
|
||||||
// Example: https://api.github.com/repos/my-org/my-action/tarball/v1
|
|
||||||
new ActionDownloadDetails(
|
|
||||||
BuildLinkToActionArchive(_dotcomApiUrl, repositoryReference.Name, repositoryReference.Ref),
|
|
||||||
configureAuthorization: (e,h) => { /* no authorization for dotcom */ })
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var downloadAttempt in downloadAttempts)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, downloadAttempt, null, destDirectory);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (ActionNotFoundException)
|
|
||||||
{
|
|
||||||
Trace.Info($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}' at {downloadAttempt.ArchiveLink}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new ActionNotFoundException($"Failed to find the action '{repositoryReference.Name}' at ref '{repositoryReference.Ref}'. Paths attempted: {string.Join(", ", downloadAttempts.Select(d => d.ArchiveLink))}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo)
|
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.ActionDownloadInfo downloadInfo)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
@@ -754,7 +623,7 @@ namespace GitHub.Runner.Worker
|
|||||||
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
|
executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
await DownloadRepositoryActionAsync(executionContext, null, downloadInfo, destDirectory);
|
await DownloadRepositoryActionAsync(executionContext, downloadInfo, destDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetApiUrl(IExecutionContext executionContext)
|
private string GetApiUrl(IExecutionContext executionContext)
|
||||||
@@ -777,8 +646,7 @@ namespace GitHub.Runner.Worker
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Remove the parameter "actionDownloadDetails" when feature flag DistributedTask.NewActionMetadata is removed
|
private async Task DownloadRepositoryActionAsync(IExecutionContext executionContext, WebApi.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());
|
||||||
@@ -786,10 +654,10 @@ 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;
|
string link = downloadInfo?.ZipballUrl;
|
||||||
#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;
|
string link = downloadInfo?.TarballUrl;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Trace.Info($"Save archive '{link}' into {archiveFile}.");
|
Trace.Info($"Save archive '{link}' into {archiveFile}.");
|
||||||
@@ -810,17 +678,8 @@ namespace GitHub.Runner.Worker
|
|||||||
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
using (FileStream fs = new FileStream(archiveFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: _defaultFileStreamBufferSize, useAsync: true))
|
||||||
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
using (var httpClientHandler = HostContext.CreateHttpClientHandler())
|
||||||
using (var httpClient = new HttpClient(httpClientHandler))
|
using (var httpClient = new HttpClient(httpClientHandler))
|
||||||
{
|
|
||||||
// Legacy
|
|
||||||
if (downloadInfo == null)
|
|
||||||
{
|
|
||||||
actionDownloadDetails.ConfigureAuthorization(executionContext, httpClient);
|
|
||||||
}
|
|
||||||
// FF DistributedTask.NewActionMetadata
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
httpClient.DefaultRequestHeaders.Authorization = CreateAuthHeader(downloadInfo.Authentication?.Token);
|
||||||
}
|
|
||||||
|
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
httpClient.DefaultRequestHeaders.UserAgent.AddRange(HostContext.UserAgents);
|
||||||
using (var response = await httpClient.GetAsync(link))
|
using (var response = await httpClient.GetAsync(link))
|
||||||
@@ -960,7 +819,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
|
||||||
private void ConfigureAuthorizationFromContext(IExecutionContext executionContext, HttpClient httpClient)
|
private void ConfigureAuthorizationFromContext(IExecutionContext executionContext, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
var authToken = Environment.GetEnvironmentVariable("_GITHUB_ACTION_TOKEN");
|
||||||
@@ -986,7 +844,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private string GetWatermarkFilePath(string directory) => directory + ".completed";
|
private string GetWatermarkFilePath(string directory) => directory + ".completed";
|
||||||
|
|
||||||
private ActionContainer PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
private ActionSetupInfo PrepareRepositoryActionAsync(IExecutionContext executionContext, Pipelines.ActionStep repositoryAction)
|
||||||
{
|
{
|
||||||
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
|
var repositoryReference = repositoryAction.Reference as Pipelines.RepositoryPathReference;
|
||||||
if (string.Equals(repositoryReference.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(repositoryReference.RepositoryType, Pipelines.PipelineConstants.SelfAlias, StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -994,8 +852,8 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"Repository action is in 'self' repository.");
|
Trace.Info($"Repository action is in 'self' repository.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
var setupInfo = new ActionSetupInfo();
|
||||||
var setupInfo = new ActionContainer();
|
var actionContainer = new ActionContainer();
|
||||||
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
|
string destDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Actions), repositoryReference.Name.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar), repositoryReference.Ref);
|
||||||
string actionEntryDirectory = destDirectory;
|
string actionEntryDirectory = destDirectory;
|
||||||
string dockerFileRelativePath = repositoryReference.Name;
|
string dockerFileRelativePath = repositoryReference.Name;
|
||||||
@@ -1004,11 +862,11 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
actionEntryDirectory = Path.Combine(destDirectory, repositoryReference.Path);
|
actionEntryDirectory = Path.Combine(destDirectory, repositoryReference.Path);
|
||||||
dockerFileRelativePath = $"{dockerFileRelativePath}/{repositoryReference.Path}";
|
dockerFileRelativePath = $"{dockerFileRelativePath}/{repositoryReference.Path}";
|
||||||
setupInfo.ActionRepository = $"{repositoryReference.Name}/{repositoryReference.Path}@{repositoryReference.Ref}";
|
actionContainer.ActionRepository = $"{repositoryReference.Name}/{repositoryReference.Path}@{repositoryReference.Ref}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setupInfo.ActionRepository = $"{repositoryReference.Name}@{repositoryReference.Ref}";
|
actionContainer.ActionRepository = $"{repositoryReference.Name}@{repositoryReference.Ref}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// find the docker file or action.yml file
|
// find the docker file or action.yml file
|
||||||
@@ -1038,8 +896,9 @@ namespace GitHub.Runner.Worker
|
|||||||
var dockerFileFullPath = Path.Combine(actionEntryDirectory, containerAction.Image);
|
var dockerFileFullPath = Path.Combine(actionEntryDirectory, containerAction.Image);
|
||||||
executionContext.Debug($"Dockerfile for action: '{dockerFileFullPath}'.");
|
executionContext.Debug($"Dockerfile for action: '{dockerFileFullPath}'.");
|
||||||
|
|
||||||
setupInfo.Dockerfile = dockerFileFullPath;
|
actionContainer.Dockerfile = dockerFileFullPath;
|
||||||
setupInfo.WorkingDirectory = destDirectory;
|
actionContainer.WorkingDirectory = destDirectory;
|
||||||
|
setupInfo.Container = actionContainer;
|
||||||
return setupInfo;
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else if (containerAction.Image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
|
else if (containerAction.Image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -1048,7 +907,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
executionContext.Debug($"Container image for action: '{actionImage}'.");
|
executionContext.Debug($"Container image for action: '{actionImage}'.");
|
||||||
|
|
||||||
setupInfo.Image = actionImage;
|
actionContainer.Image = actionImage;
|
||||||
|
setupInfo.Container = actionContainer;
|
||||||
return setupInfo;
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1068,8 +928,21 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite)
|
else if (actionDefinitionData.Execution.ExecutionType == ActionExecutionType.Composite)
|
||||||
{
|
{
|
||||||
Trace.Info($"Action composite: {(actionDefinitionData.Execution as CompositeActionExecutionData).Steps}, no more preparation.");
|
// TODO: we need to generate unique Id's for composite steps
|
||||||
return null;
|
Trace.Info($"Loading Composite steps");
|
||||||
|
var compositeAction = actionDefinitionData.Execution as CompositeActionExecutionData;
|
||||||
|
setupInfo.Steps = compositeAction.Steps;
|
||||||
|
|
||||||
|
foreach (var step in compositeAction.Steps)
|
||||||
|
{
|
||||||
|
step.Id = Guid.NewGuid();
|
||||||
|
if (string.IsNullOrEmpty(executionContext.Global.Variables.Get("DistributedTask.EnableCompositeActions")) && step.Reference.Type != Pipelines.ActionSourceType.Script)
|
||||||
|
{
|
||||||
|
throw new Exception("`uses:` keyword is not currently supported.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1079,15 +952,17 @@ namespace GitHub.Runner.Worker
|
|||||||
else if (File.Exists(dockerFile))
|
else if (File.Exists(dockerFile))
|
||||||
{
|
{
|
||||||
executionContext.Debug($"Dockerfile for action: '{dockerFile}'.");
|
executionContext.Debug($"Dockerfile for action: '{dockerFile}'.");
|
||||||
setupInfo.Dockerfile = dockerFile;
|
actionContainer.Dockerfile = dockerFile;
|
||||||
setupInfo.WorkingDirectory = destDirectory;
|
actionContainer.WorkingDirectory = destDirectory;
|
||||||
|
setupInfo.Container = actionContainer;
|
||||||
return setupInfo;
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else if (File.Exists(dockerFileLowerCase))
|
else if (File.Exists(dockerFileLowerCase))
|
||||||
{
|
{
|
||||||
executionContext.Debug($"Dockerfile for action: '{dockerFileLowerCase}'.");
|
executionContext.Debug($"Dockerfile for action: '{dockerFileLowerCase}'.");
|
||||||
setupInfo.Dockerfile = dockerFileLowerCase;
|
actionContainer.Dockerfile = dockerFileLowerCase;
|
||||||
setupInfo.WorkingDirectory = destDirectory;
|
actionContainer.WorkingDirectory = destDirectory;
|
||||||
|
setupInfo.Container = actionContainer;
|
||||||
return setupInfo;
|
return setupInfo;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1140,20 +1015,6 @@ namespace GitHub.Runner.Worker
|
|||||||
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
HostContext.SecretMasker.AddValue(base64EncodingToken);
|
||||||
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
return new AuthenticationHeaderValue("Basic", base64EncodingToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: Remove when feature flag DistributedTask.NewActionMetadata is removed
|
|
||||||
private class ActionDownloadDetails
|
|
||||||
{
|
|
||||||
public string ArchiveLink { get; }
|
|
||||||
|
|
||||||
public Action<IExecutionContext, HttpClient> ConfigureAuthorization { get; }
|
|
||||||
|
|
||||||
public ActionDownloadDetails(string archiveLink, Action<IExecutionContext, HttpClient> configureAuthorization)
|
|
||||||
{
|
|
||||||
ArchiveLink = archiveLink;
|
|
||||||
ConfigureAuthorization = configureAuthorization;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Definition
|
public sealed class Definition
|
||||||
@@ -1303,4 +1164,18 @@ namespace GitHub.Runner.Worker
|
|||||||
public string WorkingDirectory { get; set; }
|
public string WorkingDirectory { get; set; }
|
||||||
public string ActionRepository { get; set; }
|
public string ActionRepository { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ActionSetupInfo
|
||||||
|
{
|
||||||
|
public ActionContainer Container { get; set; }
|
||||||
|
public List<Pipelines.ActionStep> Steps {get; set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PrepareActionsState
|
||||||
|
{
|
||||||
|
public Dictionary<string, List<Guid>> ImagesToPull;
|
||||||
|
public Dictionary<string, List<Guid>> ImagesToBuild;
|
||||||
|
public Dictionary<string, ActionContainer> ImagesToBuildInfo;
|
||||||
|
public Dictionary<Guid, IActionRunner> PreStepTracker;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,6 +245,12 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public void RegisterPostJobStep(IStep step)
|
public void RegisterPostJobStep(IStep step)
|
||||||
{
|
{
|
||||||
|
// TODO: Remove when we support composite post job steps
|
||||||
|
if (this.IsEmbedded)
|
||||||
|
{
|
||||||
|
throw new Exception("Composite actions do not currently support post steps");
|
||||||
|
|
||||||
|
}
|
||||||
if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
|
if (step is IActionRunner actionRunner && !Root.StepsWithPostRegistered.Add(actionRunner.Action.Id))
|
||||||
{
|
{
|
||||||
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
||||||
|
|||||||
@@ -5,11 +5,15 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.DistributedTask.Expressions2;
|
||||||
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
using GitHub.DistributedTask.ObjectTemplating.Tokens;
|
||||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||||
|
using GitHub.DistributedTask.Pipelines.ObjectTemplating;
|
||||||
using GitHub.DistributedTask.WebApi;
|
using GitHub.DistributedTask.WebApi;
|
||||||
using GitHub.Runner.Common;
|
using GitHub.Runner.Common;
|
||||||
using GitHub.Runner.Sdk;
|
using GitHub.Runner.Sdk;
|
||||||
|
using GitHub.Runner.Worker;
|
||||||
|
using GitHub.Runner.Worker.Expressions;
|
||||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||||
|
|
||||||
|
|
||||||
@@ -142,6 +146,9 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
{
|
{
|
||||||
Trace.Info($"Processing embedded step: DisplayName='{step.DisplayName}'");
|
Trace.Info($"Processing embedded step: DisplayName='{step.DisplayName}'");
|
||||||
|
|
||||||
|
// Add Expression Functions
|
||||||
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue));
|
||||||
|
|
||||||
// Initialize env context
|
// Initialize env context
|
||||||
Trace.Info("Initialize Env context for embedded step");
|
Trace.Info("Initialize Env context for embedded step");
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
|
|||||||
@@ -113,6 +113,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"composite-step":{
|
"composite-step":{
|
||||||
|
"one-of": [
|
||||||
|
"run-step",
|
||||||
|
"uses-step"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"run-step": {
|
||||||
"mapping": {
|
"mapping": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": "string-steps-context",
|
"name": "string-steps-context",
|
||||||
@@ -130,6 +136,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"uses-step": {
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"name": "string-steps-context",
|
||||||
|
"id": "non-empty-string",
|
||||||
|
"uses": {
|
||||||
|
"type": "non-empty-string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"with": "step-with",
|
||||||
|
"env": "step-env"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"container-runs-context": {
|
"container-runs-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"inputs"
|
"inputs"
|
||||||
@@ -195,6 +215,23 @@
|
|||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
"loose-value-type": "string"
|
"loose-value-type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"step-with": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"inputs",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env",
|
||||||
|
"hashFiles(1,255)"
|
||||||
|
],
|
||||||
|
"mapping": {
|
||||||
|
"loose-key-type": "non-empty-string",
|
||||||
|
"loose-value-type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user