Skip to content

Commit

Permalink
minor cleanup in composite (actions#1045)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericsciple authored Apr 30, 2021
1 parent 5941cce commit 7cc689b
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 172 deletions.
52 changes: 17 additions & 35 deletions src/Runner.Worker/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,15 @@ public interface IExecutionContext : IRunnerService

bool EchoOnActionCommand { get; set; }

bool InsideComposite { get; }
bool IsEmbedded { get; }

ExecutionContext Root { get; }

// Initialize
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
void CancelToken();
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool insideComposite = false, CancellationTokenSource cancellationTokenSource = null);
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null);
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName);

// logging
long Write(string tag, string message);
Expand Down Expand Up @@ -99,7 +100,6 @@ public interface IExecutionContext : IRunnerService
// others
void ForceTaskComplete();
void RegisterPostJobStep(IStep step);
IStep CreateCompositeStep(string scopeName, IActionRunner step, DictionaryContextData inputsData, Dictionary<string, string> envData);
}

public sealed class ExecutionContext : RunnerService, IExecutionContext
Expand Down Expand Up @@ -157,7 +157,9 @@ public sealed class ExecutionContext : RunnerService, IExecutionContext

public bool EchoOnActionCommand { get; set; }

public bool InsideComposite { get; private set; }
// An embedded execution context shares the same record ID, record name, and logger
// as its enclosing execution context.
public bool IsEmbedded { get; private set; }

public TaskResult? Result
{
Expand Down Expand Up @@ -253,36 +255,7 @@ public void RegisterPostJobStep(IStep step)
Root.PostJobSteps.Push(step);
}

/// <summary>
/// Helper function used in CompositeActionHandler::RunAsync to
/// add a child node, aka a step, to the current job to the Root.JobSteps based on the location.
/// </summary>
public IStep CreateCompositeStep(
string scopeName,
IActionRunner step,
DictionaryContextData inputsData,
Dictionary<string, string> envData)
{
step.ExecutionContext = Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, step.Action.ContextName, logger: _logger, insideComposite: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token));
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
step.ExecutionContext.ExpressionValues["steps"] = Global.StepsContext.GetScope(step.ExecutionContext.GetFullyQualifiedContextName());

// Add the composite action environment variables to each step.
#if OS_WINDOWS
var envContext = new DictionaryContextData();
#else
var envContext = new CaseSensitiveDictionaryContextData();
#endif
foreach (var pair in envData)
{
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
}
step.ExecutionContext.ExpressionValues["env"] = envContext;

return step;
}

public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool insideComposite = false, CancellationTokenSource cancellationTokenSource = null)
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null)
{
Trace.Entering();

Expand Down Expand Up @@ -329,11 +302,20 @@ public IExecutionContext CreateChild(Guid recordId, string displayName, string r
child._logger.Setup(_mainTimelineId, recordId);
}

child.InsideComposite = insideComposite;
child.IsEmbedded = isEmbedded;

return child;
}

/// <summary>
/// An embedded execution context shares the same record ID, record name, logger,
/// and a linked cancellation token.
/// </summary>
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName)
{
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token));
}

public void Start(string currentOperation = null)
{
_record.CurrentOperation = currentOperation ?? _record.CurrentOperation;
Expand Down
186 changes: 77 additions & 109 deletions src/Runner.Worker/Handlers/CompositeActionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,65 +26,60 @@ public sealed class CompositeActionHandler : Handler, ICompositeActionHandler

public async Task RunAsync(ActionRunStage stage)
{
// Validate args.
// Validate args
Trace.Entering();
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
ArgUtil.NotNull(Inputs, nameof(Inputs));
ArgUtil.NotNull(Data.Steps, nameof(Data.Steps));

// Resolve action steps
var actionSteps = Data.Steps;

// Create Context Data to reuse for each composite action step
var inputsData = new DictionaryContextData();
foreach (var i in Inputs)
{
inputsData[i.Key] = new StringContextData(i.Value);
}

// Initialize Composite Steps List of Steps
var compositeSteps = new List<IStep>();

// 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()}";
}

foreach (Pipelines.ActionStep actionStep in actionSteps)
try
{
var actionRunner = HostContext.CreateService<IActionRunner>();
actionRunner.Action = actionStep;
actionRunner.Stage = stage;
actionRunner.Condition = actionStep.Condition;

var step = ExecutionContext.CreateCompositeStep(childScopeName, actionRunner, inputsData, Environment);

// 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;
// Inputs of the composite step
var inputsData = new DictionaryContextData();
foreach (var i in Inputs)
{
inputsData[i.Key] = new StringContextData(i.Value);
}

// Set GITHUB_ACTION_PATH
step.ExecutionContext.SetGitHubContext("action_path", ActionDirectory);
// 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()}";
}

compositeSteps.Add(step);
}
// Create nested steps
var nestedSteps = new List<IStep>();
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);

nestedSteps.Add(step);
}

try
{
// This is where we run each step.
await RunStepsAsync(compositeSteps);
// Run nested steps
await RunStepsAsync(nestedSteps);

// Get the pointer of the correct "steps" object and pass it to the ExecutionContext so that we can process the outputs correctly
// Set outputs
ExecutionContext.ExpressionValues["inputs"] = inputsData;
ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(ExecutionContext.GetFullyQualifiedContextName());

ProcessCompositeActionOutputs();

ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
ProcessOutputs();
ExecutionContext.Global.StepsContext.ClearScope(childScopeName);
}
catch (Exception ex)
Expand All @@ -96,7 +91,7 @@ public async Task RunAsync(ActionRunStage stage)
}
}

private void ProcessCompositeActionOutputs()
private void ProcessOutputs()
{
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));

Expand All @@ -113,69 +108,57 @@ private void ProcessCompositeActionOutputs()
evaluateContext[pair.Key] = pair.Value;
}

// Get the evluated composite outputs' values mapped to the outputs named
// Evaluate outputs
DictionaryContextData actionOutputs = actionManifestManager.EvaluateCompositeOutputs(ExecutionContext, Data.Outputs, evaluateContext);

// Set the outputs for the outputs object in the whole composite action
// Each pair is structured like this
// We ignore "description" for now
// {
// "the-output-name": {
// "description": "",
// "value": "the value"
// },
// ...
// }
// Set outputs
//
// Each pair is structured like:
// {
// "the-output-name": {
// "description": "",
// "value": "the value"
// },
// ...
// }
foreach (var pair in actionOutputs)
{
var outputsName = pair.Key;
var outputsAttributes = pair.Value as DictionaryContextData;
outputsAttributes.TryGetValue("value", out var val);

if (val != null)
var outputName = pair.Key;
var outputDefinition = pair.Value as DictionaryContextData;
if (outputDefinition.TryGetValue("value", out var val))
{
var outputsValue = val as StringContextData;
// Set output in the whole composite scope.
if (!String.IsNullOrEmpty(outputsValue))
{
ExecutionContext.SetOutput(outputsName, outputsValue, out _);
}
else
{
ExecutionContext.SetOutput(outputsName, "", out _);
}
var outputValue = val.AssertString("output value");
ExecutionContext.SetOutput(outputName, outputValue.Value, out _);
}
}
}
}

private async Task RunStepsAsync(List<IStep> compositeSteps)
private async Task RunStepsAsync(List<IStep> nestedSteps)
{
ArgUtil.NotNull(compositeSteps, nameof(compositeSteps));
ArgUtil.NotNull(nestedSteps, nameof(nestedSteps));

// The parent StepsRunner of the whole Composite Action Step handles the cancellation stuff already.
foreach (IStep step in compositeSteps)
foreach (IStep step in nestedSteps)
{
Trace.Info($"Processing composite step: DisplayName='{step.DisplayName}'");

step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(step.ExecutionContext.ScopeName);
Trace.Info($"Processing nested step: DisplayName='{step.DisplayName}'");

// Populate env context for each step
Trace.Info("Initialize Env context for step");
// Initialize env context
Trace.Info("Initialize Env context for nested step");
#if OS_WINDOWS
var envContext = new DictionaryContextData();
#else
var envContext = new CaseSensitiveDictionaryContextData();
#endif
step.ExecutionContext.ExpressionValues["env"] = envContext;

// Global env
// Merge global env
foreach (var pair in ExecutionContext.Global.EnvironmentVariables)
{
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
}

// Stomps over with outside step env
if (step.ExecutionContext.ExpressionValues.TryGetValue("env", out var envContextData))
// Merge composite-step env
if (ExecutionContext.ExpressionValues.TryGetValue("env", out var envContextData))
{
#if OS_WINDOWS
var dict = envContextData as DictionaryContextData;
Expand All @@ -188,13 +171,11 @@ private async Task RunStepsAsync(List<IStep> compositeSteps)
}
}

step.ExecutionContext.ExpressionValues["env"] = envContext;

var actionStep = step as IActionRunner;

try
{
// Evaluate and merge action's env block to env context
// Evaluate and merge nested-step env
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)
Expand All @@ -204,39 +185,28 @@ private async Task RunStepsAsync(List<IStep> compositeSteps)
}
catch (Exception ex)
{
// fail the step since there is an evaluate error.
Trace.Info("Caught exception in Composite Steps Runner from expression for step.env");
// evaluateStepEnvFailed = true;
// Evaluation error
Trace.Info("Caught exception from expression for nested step.env");
step.ExecutionContext.Error(ex);
step.ExecutionContext.Complete(TaskResult.Failed);
}

await RunStepAsync(step);

// Directly after the step, check if the step has failed or cancelled
// If so, return that to the output
// Check failed or canceled
if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled)
{
ExecutionContext.Result = step.ExecutionContext.Result;
break;
}

// TODO: Add compat for other types of steps.
}
// Completion Status handled by StepsRunner for the whole Composite Action Step
}

private async Task RunStepAsync(IStep step)
{
// Start the step.
Trace.Info("Starting the step.");
Trace.Info($"Starting: {step.DisplayName}");
step.ExecutionContext.Debug($"Starting: {step.DisplayName}");

// TODO: Fix for Step Level Timeout Attributes for an individual Composite Run Step
// For now, we are not going to support this for an individual composite run step

var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();

await Common.Util.EncodingUtil.SetEncoding(HostContext, Trace, step.ExecutionContext.CancellationToken);

try
Expand All @@ -261,7 +231,7 @@ private async Task RunStepAsync(IStep step)
}
catch (Exception ex)
{
// Log the error and fail the step.
// Log the error and fail the step
Trace.Error($"Caught exception from step: {ex}");
step.ExecutionContext.Error(ex);
step.ExecutionContext.Result = TaskResult.Failed;
Expand All @@ -274,9 +244,7 @@ private async Task RunStepAsync(IStep step)
}

Trace.Info($"Step result: {step.ExecutionContext.Result}");

// Complete the step context.
step.ExecutionContext.Debug($"Finishing: {step.DisplayName}");
step.ExecutionContext.Debug($"Finished: {step.DisplayName}");
}
}
}
Loading

0 comments on commit 7cc689b

Please sign in to comment.