Files
runner/src/Runner.Worker/Handlers/CompositeActionHandler.cs
T

251 lines
10 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GitHub.DistributedTask.ObjectTemplating.Tokens;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using Pipelines = GitHub.DistributedTask.Pipelines;
namespace GitHub.Runner.Worker.Handlers
{
[ServiceLocator(Default = typeof(CompositeActionHandler))]
public interface ICompositeActionHandler : IHandler
{
CompositeActionExecutionData Data { get; set; }
}
public sealed class CompositeActionHandler : Handler, ICompositeActionHandler
{
public CompositeActionExecutionData Data { get; set; }
2020-07-17 16:31:48 -04:00
public async Task RunAsync(ActionRunStage stage)
{
2021-04-30 15:48:53 -05:00
// Validate args
Trace.Entering();
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
ArgUtil.NotNull(Inputs, nameof(Inputs));
2020-07-17 16:31:48 -04:00
ArgUtil.NotNull(Data.Steps, nameof(Data.Steps));
2020-07-17 16:31:48 -04:00
try
{
2021-04-30 15:48:53 -05:00
// Inputs of the composite step
var inputsData = new DictionaryContextData();
foreach (var i in Inputs)
{
inputsData[i.Key] = new StringContextData(i.Value);
}
2020-07-13 17:23:19 -04:00
2021-04-30 15:48:53 -05:00
// Temporary hack until after M271-ish. After M271-ish the server will never send an empty
// context name. Generated context names start with "__"
var childScopeName = ExecutionContext.GetFullyQualifiedContextName();
if (string.IsNullOrEmpty(childScopeName))
{
childScopeName = $"__{Guid.NewGuid()}";
}
2021-05-01 12:51:01 -05:00
// Create embedded steps
var embeddedSteps = new List<IStep>();
2021-04-30 15:48:53 -05:00
foreach (Pipelines.ActionStep stepData in Data.Steps)
{
var step = HostContext.CreateService<IActionRunner>();
step.Action = stepData;
step.Stage = stage;
step.Condition = stepData.Condition;
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName);
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
// Shallow copy github context
var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext;
ArgUtil.NotNull(gitHubContext, nameof(gitHubContext));
gitHubContext = gitHubContext.ShallowCopy();
step.ExecutionContext.ExpressionValues["github"] = gitHubContext;
// Set GITHUB_ACTION_PATH
step.ExecutionContext.SetGitHubContext("action_path", ActionDirectory);
2021-05-01 12:51:01 -05:00
embeddedSteps.Add(step);
2021-04-30 15:48:53 -05:00
}
2021-05-01 12:51:01 -05:00
// Run embedded steps
await RunStepsAsync(embeddedSteps);
2021-04-30 15:48:53 -05:00
// Set outputs
2020-07-17 16:31:48 -04:00
ExecutionContext.ExpressionValues["inputs"] = inputsData;
2021-04-30 15:48:53 -05:00
ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
ProcessOutputs();
ExecutionContext.Global.StepsContext.ClearScope(childScopeName);
2020-07-17 16:31:48 -04:00
}
catch (Exception ex)
{
// Composite StepRunner should never throw exception out.
Trace.Error($"Caught exception from composite steps {nameof(CompositeActionHandler)}: {ex}");
ExecutionContext.Error(ex);
ExecutionContext.Result = TaskResult.Failed;
}
}
2021-04-30 15:48:53 -05:00
private void ProcessOutputs()
2020-07-17 16:31:48 -04:00
{
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
// Evaluate the mapped outputs value
if (Data.Outputs != null)
{
// Evaluate the outputs in the steps context to easily retrieve the values
var actionManifestManager = HostContext.GetService<IActionManifestManager>();
// Format ExpressionValues to Dictionary<string, PipelineContextData>
var evaluateContext = new Dictionary<string, PipelineContextData>(StringComparer.OrdinalIgnoreCase);
foreach (var pair in ExecutionContext.ExpressionValues)
{
evaluateContext[pair.Key] = pair.Value;
}
2021-04-30 15:48:53 -05:00
// Evaluate outputs
2020-07-17 16:31:48 -04:00
DictionaryContextData actionOutputs = actionManifestManager.EvaluateCompositeOutputs(ExecutionContext, Data.Outputs, evaluateContext);
2021-04-30 15:48:53 -05:00
// Set outputs
//
// Each pair is structured like:
// {
// "the-output-name": {
// "description": "",
// "value": "the value"
// },
// ...
// }
2020-07-17 16:31:48 -04:00
foreach (var pair in actionOutputs)
{
2021-04-30 15:48:53 -05:00
var outputName = pair.Key;
var outputDefinition = pair.Value as DictionaryContextData;
if (outputDefinition.TryGetValue("value", out var val))
2020-07-17 16:31:48 -04:00
{
2021-04-30 15:48:53 -05:00
var outputValue = val.AssertString("output value");
ExecutionContext.SetOutput(outputName, outputValue.Value, out _);
2020-07-17 16:31:48 -04:00
}
}
}
}
2021-05-01 12:51:01 -05:00
private async Task RunStepsAsync(List<IStep> embeddedSteps)
2020-07-17 16:31:48 -04:00
{
2021-05-01 12:51:01 -05:00
ArgUtil.NotNull(embeddedSteps, nameof(embeddedSteps));
2020-07-17 16:31:48 -04:00
2021-05-01 12:51:01 -05:00
foreach (IStep step in embeddedSteps)
2020-07-17 16:31:48 -04:00
{
2021-05-01 12:51:01 -05:00
Trace.Info($"Processing embedded step: DisplayName='{step.DisplayName}'");
2020-07-17 16:31:48 -04:00
2021-04-30 15:48:53 -05:00
// Initialize env context
2021-05-01 12:51:01 -05:00
Trace.Info("Initialize Env context for embedded step");
2020-07-17 16:31:48 -04:00
#if OS_WINDOWS
var envContext = new DictionaryContextData();
#else
var envContext = new CaseSensitiveDictionaryContextData();
#endif
2021-04-30 15:48:53 -05:00
step.ExecutionContext.ExpressionValues["env"] = envContext;
2020-07-17 16:31:48 -04:00
2021-04-30 15:48:53 -05:00
// Merge global env
foreach (var pair in ExecutionContext.Global.EnvironmentVariables)
2020-07-17 16:31:48 -04:00
{
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
}
2021-04-30 15:48:53 -05:00
// Merge composite-step env
if (ExecutionContext.ExpressionValues.TryGetValue("env", out var envContextData))
2020-07-17 16:31:48 -04:00
{
#if OS_WINDOWS
var dict = envContextData as DictionaryContextData;
#else
var dict = envContextData as CaseSensitiveDictionaryContextData;
#endif
foreach (var pair in dict)
{
envContext[pair.Key] = pair.Value;
}
}
var actionStep = step as IActionRunner;
try
{
2021-05-01 12:51:01 -05:00
// Evaluate and merge embedded-step env
2020-07-17 16:31:48 -04:00
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, Common.Util.VarUtil.EnvironmentVariableKeyComparer);
foreach (var env in actionEnvironment)
{
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
}
}
catch (Exception ex)
{
2021-04-30 15:48:53 -05:00
// Evaluation error
2021-05-01 12:51:01 -05:00
Trace.Info("Caught exception from expression for embedded step.env");
2020-07-17 16:31:48 -04:00
step.ExecutionContext.Error(ex);
step.ExecutionContext.Complete(TaskResult.Failed);
}
await RunStepAsync(step);
2021-04-30 15:48:53 -05:00
// Check failed or canceled
if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled)
2020-07-17 16:31:48 -04:00
{
ExecutionContext.Result = step.ExecutionContext.Result;
break;
}
}
}
private async Task RunStepAsync(IStep step)
{
2021-04-30 15:48:53 -05:00
Trace.Info($"Starting: {step.DisplayName}");
2020-07-17 16:31:48 -04:00
step.ExecutionContext.Debug($"Starting: {step.DisplayName}");
await Common.Util.EncodingUtil.SetEncoding(HostContext, Trace, step.ExecutionContext.CancellationToken);
try
{
await step.RunAsync();
}
catch (OperationCanceledException ex)
{
if (step.ExecutionContext.CancellationToken.IsCancellationRequested &&
!ExecutionContext.Root.CancellationToken.IsCancellationRequested)
2020-07-17 16:31:48 -04:00
{
Trace.Error($"Caught timeout exception from step: {ex.Message}");
step.ExecutionContext.Error("The action has timed out.");
step.ExecutionContext.Result = TaskResult.Failed;
}
else
{
Trace.Error($"Caught cancellation exception from step: {ex}");
step.ExecutionContext.Error(ex);
step.ExecutionContext.Result = TaskResult.Canceled;
}
}
catch (Exception ex)
{
2021-04-30 15:48:53 -05:00
// Log the error and fail the step
2020-07-17 16:31:48 -04:00
Trace.Error($"Caught exception from step: {ex}");
step.ExecutionContext.Error(ex);
step.ExecutionContext.Result = TaskResult.Failed;
}
// Merge execution context result with command result
if (step.ExecutionContext.CommandResult != null)
{
step.ExecutionContext.Result = Common.Util.TaskResultUtil.MergeTaskResults(step.ExecutionContext.Result, step.ExecutionContext.CommandResult.Value);
}
Trace.Info($"Step result: {step.ExecutionContext.Result}");
2021-04-30 15:48:53 -05:00
step.ExecutionContext.Debug($"Finished: {step.DisplayName}");
2020-07-17 16:31:48 -04:00
}
}
}