mirror of
https://github.com/actions/runner.git
synced 2025-12-12 15:13:30 +00:00
Composite Run Steps Outputs (#568)
* Composite Action Run Steps
* Env Flow => Able to get env variables and overwrite current env variables => but it doesn't 'stick'
* clean up
* Clean up trace messages + add Trace debug in ActionManager
* Add debugging message
* Optimize runtime of code
* Change String to string
* Add comma to Composite
* Change JobSteps to a List, Change Register Step function name
* Add TODO, remove unn. content
* Remove unnecessary code
* Fix unit tests
* Fix env format
* Remove comment
* Remove TODO message for context
* Add verbose trace logs which are only viewable by devs
* Initial Start for FileTable stuff
* Progress towards passing FileTable or FileID or FileName
* Sort usings in Composite Action Handler
* Change 0 to location
* Update context variables in composite action yaml
* Add helpful error message for null steps
* Pass fileID to all children token of root action token
* Change confusing term context => templateContext, Eliminate _fileTable and only use ExecutionContext.FileTable + update this table when need be
* Remove unnessary FileID attribute from CompositeActionExecutionData
* Clean up file path for error message
* Remove todo
* Initial start/framework for output handling
* Outline different class vs Handler approach
* Remove InitializeScope
* Remove InitializeScope
* Fix Workflow Step Env overiding Parent Env
* First Approach for Attaching ID + Group ID to each Composite Action Step
* Add GroupID to the ActionDefinitionData
* starting foundation for handling clean up outputs step
* Pass outputs data to each composite action step to enable set-output functionality
* Create ScopeName for whole composite action.
This will enable us to add to the StepsContext[ScopeName] for the composite action which will allow us to use all these outputs in the cleanup step
* Hook up composite output step to handler => tmmrw implement composite output handler
* Add post composite action step to cleanup outputs => triggers composite output cleanup handler
* Fix Outputs Token handling start. Add individual step scope names.
* Set up Scope Name and Context Name naming system{
* Figured out how to pass Parent Execution Context to clean up step
* Figured out how to pass Parent Execution Context and scope names to
clean up step
* Add GetOutput function for StepsContext
* Generate child scope name correctly if parent scope name is null
* Simplify InitializeScope()
* Outputs are set correctly and able to get all final outputs in handler
* Parse through Action Outputs
* Fix null ScopeName + ContextName in CompositeOutputHandler
* Shift over handling of Action Outputs to output handler
* First attempt to fix null retrievals for output variables
* Basic Support for Outputs Done.
* Clean up pt.1
* Refactor outputs to avoid using Action Reference
* Clean up code
* Clean up part 2
* Add clarifying comments for the output handler
* Remove TODO
* Remove env in composite action scope
* Clean up
* Revert back
* revert back
* add back envToken
* Remove unnecessary code
* Add file length check
* Clean up
* Figure out how to handle set-env edge cases
* formatting
* fix unit tests
* Fix windows unit test syntax error
* Fix period
* Sanity check for fileTable add + remove unn. code
* revert back
* Add back line break
* Fix null errors
* Address situation if FileTable is null + add sanity check for adding file to fileTable
* add line
* Revert
* Fix unit tests to instantiate a FileTable
* Fix logic for trimming manifestfile path
* Add null check
* Revert
* Revert
* revert
* spacing
* Add filetable to testing file, remove ? since we know filetable should never be non null
* Fix Throw logic
* Clarify template outputs token
* Add another type support for outputs to avoid container unit tests errors
* Add mapping for parity
* Build support for new outputs format
* Refactor to avoid duplication of action yaml for workflow yaml
* Move SDK work in ActionManifestManager, Condense Code
* Defer runs evaluation till after for loop to ensure order doesn't matter
* Fix logic error in setting scope and context names
* Add Regex + Add Child Context name null resolution
* move private function to bottom of class
This commit is contained in:
@@ -398,8 +398,10 @@ namespace GitHub.Runner.Worker
|
|||||||
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
else if (definition.Data.Execution.ExecutionType == ActionExecutionType.Composite && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
{
|
{
|
||||||
var compositeAction = definition.Data.Execution as CompositeActionExecutionData;
|
var compositeAction = definition.Data.Execution as CompositeActionExecutionData;
|
||||||
Trace.Info($"Load {compositeAction.Steps.Count} action steps.");
|
Trace.Info($"Load {compositeAction.Steps?.Count ?? 0} action steps.");
|
||||||
Trace.Verbose($"Details: {StringUtil.ConvertToJson(compositeAction.Steps)}");
|
Trace.Verbose($"Details: {StringUtil.ConvertToJson(compositeAction?.Steps)}");
|
||||||
|
Trace.Info($"Load: {compositeAction.Outputs?.Count ?? 0} number of outputs");
|
||||||
|
Trace.Info($"Details: {StringUtil.ConvertToJson(compositeAction?.Outputs)}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1222,6 +1224,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public override bool HasPre => false;
|
public override bool HasPre => false;
|
||||||
public override bool HasPost => false;
|
public override bool HasPost => false;
|
||||||
public List<Pipelines.ActionStep> Steps { get; set; }
|
public List<Pipelines.ActionStep> Steps { get; set; }
|
||||||
|
public MappingToken Outputs { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class ActionExecutionData
|
public abstract class ActionExecutionData
|
||||||
|
|||||||
@@ -23,11 +23,15 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile);
|
ActionDefinitionData Load(IExecutionContext executionContext, string manifestFile);
|
||||||
|
|
||||||
|
DictionaryContextData EvaluateCompositeOutputs(IExecutionContext executionContext, TemplateToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
||||||
|
|
||||||
List<string> EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
List<string> EvaluateContainerArguments(IExecutionContext executionContext, SequenceToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
||||||
|
|
||||||
Dictionary<string, string> EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
Dictionary<string, string> EvaluateContainerEnvironment(IExecutionContext executionContext, MappingToken token, IDictionary<string, PipelineContextData> extraExpressionValues);
|
||||||
|
|
||||||
string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token);
|
string EvaluateDefaultInput(IExecutionContext executionContext, string inputName, TemplateToken token);
|
||||||
|
|
||||||
|
void SetAllCompositeOutputs(IExecutionContext parentExecutionContext, DictionaryContextData actionOutputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ActionManifestManager : RunnerService, IActionManifestManager
|
public sealed class ActionManifestManager : RunnerService, IActionManifestManager
|
||||||
@@ -89,6 +93,9 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
var actionMapping = token.AssertMapping("action manifest root");
|
var actionMapping = token.AssertMapping("action manifest root");
|
||||||
|
var actionOutputs = default(MappingToken);
|
||||||
|
var actionRunValueToken = default(TemplateToken);
|
||||||
|
|
||||||
foreach (var actionPair in actionMapping)
|
foreach (var actionPair in actionMapping)
|
||||||
{
|
{
|
||||||
var propertyName = actionPair.Key.AssertString($"action.yml property key");
|
var propertyName = actionPair.Key.AssertString($"action.yml property key");
|
||||||
@@ -99,6 +106,15 @@ namespace GitHub.Runner.Worker
|
|||||||
actionDefinition.Name = actionPair.Value.AssertString("name").Value;
|
actionDefinition.Name = actionPair.Value.AssertString("name").Value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "outputs":
|
||||||
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
||||||
|
{
|
||||||
|
actionOutputs = actionPair.Value.AssertMapping("outputs");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Trace.Info($"Ignore action property outputs. Outputs for a whole action is not supported yet.");
|
||||||
|
break;
|
||||||
|
|
||||||
case "description":
|
case "description":
|
||||||
actionDefinition.Description = actionPair.Value.AssertString("description").Value;
|
actionDefinition.Description = actionPair.Value.AssertString("description").Value;
|
||||||
break;
|
break;
|
||||||
@@ -108,13 +124,21 @@ namespace GitHub.Runner.Worker
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "runs":
|
case "runs":
|
||||||
actionDefinition.Execution = ConvertRuns(executionContext, templateContext, actionPair.Value);
|
// Defer runs token evaluation to after for loop to ensure that order of outputs doesn't matter.
|
||||||
|
actionRunValueToken = actionPair.Value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Trace.Info($"Ignore action property {propertyName}.");
|
Trace.Info($"Ignore action property {propertyName}.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evaluate Runs Last
|
||||||
|
if (actionRunValueToken != null)
|
||||||
|
{
|
||||||
|
actionDefinition.Execution = ConvertRuns(executionContext, templateContext, actionRunValueToken, actionOutputs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -146,6 +170,61 @@ namespace GitHub.Runner.Worker
|
|||||||
return actionDefinition;
|
return actionDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetAllCompositeOutputs(
|
||||||
|
IExecutionContext parentExecutionContext,
|
||||||
|
DictionaryContextData actionOutputs)
|
||||||
|
{
|
||||||
|
// Each pair is structured like this
|
||||||
|
// We ignore "description" for now
|
||||||
|
// {
|
||||||
|
// "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);
|
||||||
|
var outputsValue = val as StringContextData;
|
||||||
|
|
||||||
|
// Set output in the whole composite scope.
|
||||||
|
if (!String.IsNullOrEmpty(outputsName) && !String.IsNullOrEmpty(outputsValue))
|
||||||
|
{
|
||||||
|
parentExecutionContext.SetOutput(outputsName, outputsValue, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DictionaryContextData EvaluateCompositeOutputs(
|
||||||
|
IExecutionContext executionContext,
|
||||||
|
TemplateToken token,
|
||||||
|
IDictionary<string, PipelineContextData> extraExpressionValues)
|
||||||
|
{
|
||||||
|
var result = default(DictionaryContextData);
|
||||||
|
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
var context = CreateContext(executionContext, extraExpressionValues);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = TemplateEvaluator.Evaluate(context, "outputs", token, 0, null, omitHeader: true);
|
||||||
|
context.Errors.Check();
|
||||||
|
result = token.ToContextData().AssertDictionary("composite outputs");
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||||
|
{
|
||||||
|
context.Errors.Add(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Errors.Check();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result ?? new DictionaryContextData();
|
||||||
|
}
|
||||||
|
|
||||||
public List<string> EvaluateContainerArguments(
|
public List<string> EvaluateContainerArguments(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
SequenceToken token,
|
SequenceToken token,
|
||||||
@@ -309,7 +388,8 @@ namespace GitHub.Runner.Worker
|
|||||||
private ActionExecutionData ConvertRuns(
|
private ActionExecutionData ConvertRuns(
|
||||||
IExecutionContext executionContext,
|
IExecutionContext executionContext,
|
||||||
TemplateContext context,
|
TemplateContext context,
|
||||||
TemplateToken inputsToken)
|
TemplateToken inputsToken,
|
||||||
|
MappingToken outputs = null)
|
||||||
{
|
{
|
||||||
var runsMapping = inputsToken.AssertMapping("runs");
|
var runsMapping = inputsToken.AssertMapping("runs");
|
||||||
var usingToken = default(StringToken);
|
var usingToken = default(StringToken);
|
||||||
@@ -439,6 +519,7 @@ namespace GitHub.Runner.Worker
|
|||||||
return new CompositeActionExecutionData()
|
return new CompositeActionExecutionData()
|
||||||
{
|
{
|
||||||
Steps = stepsLoaded,
|
Steps = stepsLoaded,
|
||||||
|
Outputs = outputs
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Globalization;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
@@ -52,7 +53,6 @@ namespace GitHub.Runner.Worker
|
|||||||
IDictionary<String, IDictionary<String, String>> JobDefaults { get; }
|
IDictionary<String, IDictionary<String, String>> JobDefaults { get; }
|
||||||
Dictionary<string, VariableValue> JobOutputs { get; }
|
Dictionary<string, VariableValue> JobOutputs { get; }
|
||||||
IDictionary<String, String> EnvironmentVariables { get; }
|
IDictionary<String, String> EnvironmentVariables { get; }
|
||||||
IDictionary<String, ContextScope> Scopes { get; }
|
|
||||||
IList<String> FileTable { get; }
|
IList<String> FileTable { get; }
|
||||||
StepsContext StepsContext { get; }
|
StepsContext StepsContext { get; }
|
||||||
DictionaryContextData ExpressionValues { get; }
|
DictionaryContextData ExpressionValues { get; }
|
||||||
@@ -70,6 +70,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
bool EchoOnActionCommand { get; set; }
|
bool EchoOnActionCommand { get; set; }
|
||||||
|
|
||||||
|
IExecutionContext FinalizeContext { get; set; }
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
||||||
void CancelToken();
|
void CancelToken();
|
||||||
@@ -105,7 +107,7 @@ namespace GitHub.Runner.Worker
|
|||||||
// others
|
// others
|
||||||
void ForceTaskComplete();
|
void ForceTaskComplete();
|
||||||
void RegisterPostJobStep(IStep step);
|
void RegisterPostJobStep(IStep step);
|
||||||
void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int location, Dictionary<string, string> envData);
|
IStep RegisterNestedStep(IActionRunner step, DictionaryContextData inputsData, int location, Dictionary<string, string> envData, bool cleanUp = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
public sealed class ExecutionContext : RunnerService, IExecutionContext
|
||||||
@@ -120,6 +122,9 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
private event OnMatcherChanged _onMatcherChanged;
|
private event OnMatcherChanged _onMatcherChanged;
|
||||||
|
|
||||||
|
// Regex used for checking if ScopeName meets the condition that shows that its id is null.
|
||||||
|
private readonly static Regex _generatedContextNamePattern = new Regex("^__[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private IssueMatcherConfig[] _matchers;
|
private IssueMatcherConfig[] _matchers;
|
||||||
|
|
||||||
private IPagingLogger _logger;
|
private IPagingLogger _logger;
|
||||||
@@ -149,7 +154,6 @@ namespace GitHub.Runner.Worker
|
|||||||
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
|
public IDictionary<String, IDictionary<String, String>> JobDefaults { get; private set; }
|
||||||
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
|
public Dictionary<string, VariableValue> JobOutputs { get; private set; }
|
||||||
public IDictionary<String, String> EnvironmentVariables { get; private set; }
|
public IDictionary<String, String> EnvironmentVariables { get; private set; }
|
||||||
public IDictionary<String, ContextScope> Scopes { get; private set; }
|
|
||||||
public IList<String> FileTable { get; private set; }
|
public IList<String> FileTable { get; private set; }
|
||||||
public StepsContext StepsContext { get; private set; }
|
public StepsContext StepsContext { get; private set; }
|
||||||
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
public DictionaryContextData ExpressionValues { get; } = new DictionaryContextData();
|
||||||
@@ -170,6 +174,8 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public bool EchoOnActionCommand { get; set; }
|
public bool EchoOnActionCommand { get; set; }
|
||||||
|
|
||||||
|
public IExecutionContext FinalizeContext { get; set; }
|
||||||
|
|
||||||
public TaskResult? Result
|
public TaskResult? Result
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -270,17 +276,36 @@ namespace GitHub.Runner.Worker
|
|||||||
/// Helper function used in CompositeActionHandler::RunAsync to
|
/// 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.
|
/// add a child node, aka a step, to the current job to the Root.JobSteps based on the location.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RegisterNestedStep(IStep step, DictionaryContextData inputsData, int location, Dictionary<string, string> envData)
|
public IStep RegisterNestedStep(
|
||||||
|
IActionRunner step,
|
||||||
|
DictionaryContextData inputsData,
|
||||||
|
int location,
|
||||||
|
Dictionary<string, string> envData,
|
||||||
|
bool cleanUp = false)
|
||||||
{
|
{
|
||||||
// TODO: For UI purposes, look at figuring out how to condense steps in one node => maybe use the same previous GUID
|
// TODO: For UI purposes, look at figuring out how to condense steps in one node => maybe use the same previous GUID
|
||||||
var newGuid = Guid.NewGuid();
|
var newGuid = Guid.NewGuid();
|
||||||
step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), null, null);
|
|
||||||
|
// If the context name is empty and the scope name is empty, we would generate a unique scope name for this child in the following format:
|
||||||
|
// "__<GUID>"
|
||||||
|
var safeContextName = !string.IsNullOrEmpty(ContextName) ? ContextName : $"__{newGuid}";
|
||||||
|
|
||||||
|
// Set Scope Name. Note, for our design, we consider each step in a composite action to have the same scope
|
||||||
|
// This makes it much simpler to handle their outputs at the end of the Composite Action
|
||||||
|
var childScopeName = !string.IsNullOrEmpty(ScopeName) ? $"{ScopeName}.{safeContextName}" : safeContextName;
|
||||||
|
|
||||||
|
var childContextName = !string.IsNullOrEmpty(step.Action.ContextName) ? step.Action.ContextName : $"__{Guid.NewGuid()}";
|
||||||
|
|
||||||
|
step.ExecutionContext = Root.CreateChild(newGuid, step.DisplayName, newGuid.ToString("N"), childScopeName, childContextName);
|
||||||
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
||||||
|
|
||||||
|
// Set Parent Attribute for Clean Up Step
|
||||||
|
if (cleanUp)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.FinalizeContext = this;
|
||||||
|
}
|
||||||
|
|
||||||
// Add the composite action environment variables to each step.
|
// Add the composite action environment variables to each step.
|
||||||
// If the key already exists, we override it since the composite action env variables will have higher precedence
|
|
||||||
// Note that for each composite action step, it's environment variables will be set in the StepRunner automatically
|
|
||||||
// step.ExecutionContext.SetEnvironmentVariables(envData);
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
var envContext = new DictionaryContextData();
|
var envContext = new DictionaryContextData();
|
||||||
#else
|
#else
|
||||||
@@ -293,6 +318,8 @@ namespace GitHub.Runner.Worker
|
|||||||
step.ExecutionContext.ExpressionValues["env"] = envContext;
|
step.ExecutionContext.ExpressionValues["env"] = envContext;
|
||||||
|
|
||||||
Root.JobSteps.Insert(location, step);
|
Root.JobSteps.Insert(location, step);
|
||||||
|
|
||||||
|
return step;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null)
|
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null)
|
||||||
@@ -317,7 +344,6 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
child.EnvironmentVariables = EnvironmentVariables;
|
child.EnvironmentVariables = EnvironmentVariables;
|
||||||
child.JobDefaults = JobDefaults;
|
child.JobDefaults = JobDefaults;
|
||||||
child.Scopes = Scopes;
|
|
||||||
child.FileTable = FileTable;
|
child.FileTable = FileTable;
|
||||||
child.StepsContext = StepsContext;
|
child.StepsContext = StepsContext;
|
||||||
foreach (var pair in ExpressionValues)
|
foreach (var pair in ExpressionValues)
|
||||||
@@ -466,7 +492,8 @@ namespace GitHub.Runner.Worker
|
|||||||
{
|
{
|
||||||
ArgUtil.NotNullOrEmpty(name, nameof(name));
|
ArgUtil.NotNullOrEmpty(name, nameof(name));
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(ContextName))
|
// if the ContextName follows the __GUID format which is set as the default value for ContextName if null for Composite Actions.
|
||||||
|
if (String.IsNullOrEmpty(ContextName) || _generatedContextNamePattern.IsMatch(ContextName))
|
||||||
{
|
{
|
||||||
reference = null;
|
reference = null;
|
||||||
return;
|
return;
|
||||||
@@ -633,16 +660,6 @@ namespace GitHub.Runner.Worker
|
|||||||
// Steps context (StepsRunner manages adding the scoped steps context)
|
// Steps context (StepsRunner manages adding the scoped steps context)
|
||||||
StepsContext = new StepsContext();
|
StepsContext = new StepsContext();
|
||||||
|
|
||||||
// Scopes
|
|
||||||
Scopes = new Dictionary<String, ContextScope>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
if (message.Scopes?.Count > 0)
|
|
||||||
{
|
|
||||||
foreach (var scope in message.Scopes)
|
|
||||||
{
|
|
||||||
Scopes[scope.Name] = scope;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// File table
|
// File table
|
||||||
FileTable = new List<String>(message.FileTable ?? new string[0]);
|
FileTable = new List<String>(message.FileTable ?? new string[0]);
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
|
|
||||||
// Add each composite action step to the front of the queue
|
// Add each composite action step to the front of the queue
|
||||||
int location = 0;
|
int location = 0;
|
||||||
|
|
||||||
foreach (Pipelines.ActionStep aStep in actionSteps)
|
foreach (Pipelines.ActionStep aStep in actionSteps)
|
||||||
{
|
{
|
||||||
// Ex:
|
// Ex:
|
||||||
@@ -85,12 +86,35 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
actionRunner.Condition = aStep.Condition;
|
actionRunner.Condition = aStep.Condition;
|
||||||
actionRunner.DisplayName = aStep.DisplayName;
|
actionRunner.DisplayName = aStep.DisplayName;
|
||||||
|
|
||||||
ExecutionContext.RegisterNestedStep(actionRunner, inputsData, location, Environment);
|
var step = ExecutionContext.RegisterNestedStep(actionRunner, inputsData, location, Environment);
|
||||||
|
|
||||||
|
InitializeScope(step);
|
||||||
|
|
||||||
location++;
|
location++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a step that handles all the composite action steps' outputs
|
||||||
|
Pipelines.ActionStep cleanOutputsStep = new Pipelines.ActionStep();
|
||||||
|
cleanOutputsStep.ContextName = ExecutionContext.ContextName;
|
||||||
|
cleanOutputsStep.DisplayName = "Composite Action Steps Cleanup";
|
||||||
|
// Use the same reference type as our composite steps.
|
||||||
|
cleanOutputsStep.Reference = Action;
|
||||||
|
|
||||||
|
var actionRunner2 = HostContext.CreateService<IActionRunner>();
|
||||||
|
actionRunner2.Action = cleanOutputsStep;
|
||||||
|
actionRunner2.Stage = ActionRunStage.Main;
|
||||||
|
actionRunner2.Condition = "always()";
|
||||||
|
actionRunner2.DisplayName = "Composite Action Steps Cleanup";
|
||||||
|
ExecutionContext.RegisterNestedStep(actionRunner2, inputsData, location, Environment, true);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitializeScope(IStep step)
|
||||||
|
{
|
||||||
|
var stepsContext = step.ExecutionContext.StepsContext;
|
||||||
|
var scopeName = step.ExecutionContext.ScopeName;
|
||||||
|
step.ExecutionContext.ExpressionValues["steps"] = stepsContext.GetScope(scopeName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/Runner.Worker/Handlers/CompositeActionOutputHandler.cs
Normal file
53
src/Runner.Worker/Handlers/CompositeActionOutputHandler.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GitHub.DistributedTask.ObjectTemplating.Schema;
|
||||||
|
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(CompositeActionOutputHandler))]
|
||||||
|
public interface ICompositeActionOutputHandler : IHandler
|
||||||
|
{
|
||||||
|
CompositeActionExecutionData Data { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CompositeActionOutputHandler : Handler, ICompositeActionOutputHandler
|
||||||
|
{
|
||||||
|
public CompositeActionExecutionData Data { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public Task RunAsync(ActionRunStage stage)
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the evluated composite outputs' values mapped to the outputs named
|
||||||
|
DictionaryContextData actionOutputs = actionManifestManager.EvaluateCompositeOutputs(ExecutionContext, Data.Outputs, evaluateContext);
|
||||||
|
|
||||||
|
// Set the outputs for the outputs object in the whole composite action
|
||||||
|
actionManifestManager.SetAllCompositeOutputs(ExecutionContext.FinalizeContext, actionOutputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,8 +68,16 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
else if (data.ExecutionType == ActionExecutionType.Composite)
|
else if (data.ExecutionType == ActionExecutionType.Composite)
|
||||||
{
|
{
|
||||||
handler = HostContext.CreateService<ICompositeActionHandler>();
|
if (executionContext.FinalizeContext == null)
|
||||||
(handler as ICompositeActionHandler).Data = data as CompositeActionExecutionData;
|
{
|
||||||
|
handler = HostContext.CreateService<ICompositeActionHandler>();
|
||||||
|
(handler as ICompositeActionHandler).Data = data as CompositeActionExecutionData;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handler = HostContext.CreateService<ICompositeActionOutputHandler>();
|
||||||
|
(handler as ICompositeActionOutputHandler).Data = data as CompositeActionExecutionData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
var step = jobContext.JobSteps[0];
|
var step = jobContext.JobSteps[0];
|
||||||
jobContext.JobSteps.RemoveAt(0);
|
jobContext.JobSteps.RemoveAt(0);
|
||||||
var nextStep = jobContext.JobSteps.Count > 0 ? jobContext.JobSteps[0] : null;
|
|
||||||
|
|
||||||
Trace.Info($"Processing step: DisplayName='{step.DisplayName}'");
|
Trace.Info($"Processing step: DisplayName='{step.DisplayName}'");
|
||||||
ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext));
|
ArgUtil.NotNull(step.ExecutionContext, nameof(step.ExecutionContext));
|
||||||
@@ -83,171 +82,170 @@ namespace GitHub.Runner.Worker
|
|||||||
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0));
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<SuccessFunction>(PipelineTemplateConstants.Success, 0, 0));
|
||||||
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue));
|
step.ExecutionContext.ExpressionFunctions.Add(new FunctionInfo<HashFilesFunction>(PipelineTemplateConstants.HashFiles, 1, byte.MaxValue));
|
||||||
|
|
||||||
// Initialize scope
|
step.ExecutionContext.ExpressionValues["steps"] = step.ExecutionContext.StepsContext.GetScope(step.ExecutionContext.ScopeName);
|
||||||
if (InitializeScope(step, scopeInputs))
|
|
||||||
{
|
|
||||||
// Populate env context for each step
|
|
||||||
Trace.Info("Initialize Env context for step");
|
|
||||||
#if OS_WINDOWS
|
|
||||||
var envContext = new DictionaryContextData();
|
|
||||||
#else
|
|
||||||
var envContext = new CaseSensitiveDictionaryContextData();
|
|
||||||
#endif
|
|
||||||
// Global env
|
|
||||||
foreach (var pair in step.ExecutionContext.EnvironmentVariables)
|
|
||||||
{
|
|
||||||
envContext[pair.Key] = new StringContextData(pair.Value ?? string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stomps over with outside step env
|
// Populate env context for each step
|
||||||
if (step.ExecutionContext.ExpressionValues.TryGetValue("env", out var envContextData))
|
Trace.Info("Initialize Env context for step");
|
||||||
{
|
|
||||||
#if OS_WINDOWS
|
#if OS_WINDOWS
|
||||||
var dict = envContextData as DictionaryContextData;
|
var envContext = new DictionaryContextData();
|
||||||
#else
|
#else
|
||||||
var dict = envContextData as CaseSensitiveDictionaryContextData;
|
var envContext = new CaseSensitiveDictionaryContextData();
|
||||||
#endif
|
#endif
|
||||||
foreach (var pair in dict)
|
|
||||||
|
// Global env
|
||||||
|
foreach (var pair in step.ExecutionContext.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))
|
||||||
|
{
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step.ExecutionContext.ExpressionValues["env"] = envContext;
|
||||||
|
|
||||||
|
bool evaluateStepEnvFailed = false;
|
||||||
|
if (step is IActionRunner actionStep)
|
||||||
|
{
|
||||||
|
// Set GITHUB_ACTION
|
||||||
|
step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Evaluate and merge action's env block to env context
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
||||||
|
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
||||||
|
foreach (var env in actionEnvironment)
|
||||||
{
|
{
|
||||||
envContext[pair.Key] = pair.Value;
|
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
step.ExecutionContext.ExpressionValues["env"] = envContext;
|
|
||||||
|
|
||||||
bool evaluateStepEnvFailed = false;
|
|
||||||
if (step is IActionRunner actionStep)
|
|
||||||
{
|
{
|
||||||
// Set GITHUB_ACTION
|
// fail the step since there is an evaluate error.
|
||||||
step.ExecutionContext.SetGitHubContext("action", actionStep.Action.Name);
|
Trace.Info("Caught exception from expression for step.env");
|
||||||
|
evaluateStepEnvFailed = true;
|
||||||
|
step.ExecutionContext.Error(ex);
|
||||||
|
CompleteStep(step, TaskResult.Failed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
if (!evaluateStepEnvFailed)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
||||||
|
if (!jobContext.CancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
// Evaluate and merge action's env block to env context
|
// Test the condition again. The job was canceled after the condition was originally evaluated.
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator();
|
jobCancelRegister = jobContext.CancellationToken.Register(() =>
|
||||||
var actionEnvironment = templateEvaluator.EvaluateStepEnvironment(actionStep.Action.Environment, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, VarUtil.EnvironmentVariableKeyComparer);
|
|
||||||
foreach (var env in actionEnvironment)
|
|
||||||
{
|
{
|
||||||
envContext[env.Key] = new StringContextData(env.Value ?? string.Empty);
|
// mark job as cancelled
|
||||||
|
jobContext.Result = TaskResult.Canceled;
|
||||||
|
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||||
|
|
||||||
|
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
||||||
|
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
||||||
|
var conditionReTestResult = false;
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Cancel the step since we get exception while re-evaluate step condition.
|
||||||
|
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
||||||
|
step.ExecutionContext.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conditionReTestResult)
|
||||||
|
{
|
||||||
|
// Cancel the step.
|
||||||
|
Trace.Info("Cancel current running step.");
|
||||||
|
step.ExecutionContext.CancelToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (jobContext.Result != TaskResult.Canceled)
|
||||||
|
{
|
||||||
|
// mark job as cancelled
|
||||||
|
jobContext.Result = TaskResult.Canceled;
|
||||||
|
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
// Evaluate condition.
|
||||||
|
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
||||||
|
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
|
||||||
|
var conditionResult = false;
|
||||||
|
var conditionEvaluateError = default(Exception);
|
||||||
|
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
||||||
|
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
||||||
|
conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Info("Caught exception from expression.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
conditionEvaluateError = ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no evaluate error but condition is false
|
||||||
|
if (!conditionResult && conditionEvaluateError == null)
|
||||||
|
{
|
||||||
|
// Condition == false
|
||||||
|
Trace.Info("Skipping step due to condition evaluation.");
|
||||||
|
CompleteStep(step, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace);
|
||||||
|
}
|
||||||
|
else if (conditionEvaluateError != null)
|
||||||
{
|
{
|
||||||
// fail the step since there is an evaluate error.
|
// fail the step since there is an evaluate error.
|
||||||
Trace.Info("Caught exception from expression for step.env");
|
step.ExecutionContext.Error(conditionEvaluateError);
|
||||||
evaluateStepEnvFailed = true;
|
CompleteStep(step, TaskResult.Failed);
|
||||||
step.ExecutionContext.Error(ex);
|
}
|
||||||
CompleteStep(step, nextStep, TaskResult.Failed);
|
else
|
||||||
|
{
|
||||||
|
// Run the step.
|
||||||
|
await RunStepAsync(step, jobContext.CancellationToken);
|
||||||
|
CompleteStep(step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
if (!evaluateStepEnvFailed)
|
|
||||||
{
|
{
|
||||||
try
|
if (jobCancelRegister != null)
|
||||||
{
|
{
|
||||||
// Register job cancellation call back only if job cancellation token not been fire before each step run
|
jobCancelRegister?.Dispose();
|
||||||
if (!jobContext.CancellationToken.IsCancellationRequested)
|
jobCancelRegister = null;
|
||||||
{
|
|
||||||
// Test the condition again. The job was canceled after the condition was originally evaluated.
|
|
||||||
jobCancelRegister = jobContext.CancellationToken.Register(() =>
|
|
||||||
{
|
|
||||||
// mark job as cancelled
|
|
||||||
jobContext.Result = TaskResult.Canceled;
|
|
||||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
|
||||||
|
|
||||||
step.ExecutionContext.Debug($"Re-evaluate condition on job cancellation for step: '{step.DisplayName}'.");
|
|
||||||
var conditionReTestTraceWriter = new ConditionTraceWriter(Trace, null); // host tracing only
|
|
||||||
var conditionReTestResult = false;
|
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
step.ExecutionContext.Debug($"Skip Re-evaluate condition on runner shutdown.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionReTestTraceWriter);
|
|
||||||
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
|
||||||
conditionReTestResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Cancel the step since we get exception while re-evaluate step condition.
|
|
||||||
Trace.Info("Caught exception from expression when re-test condition on job cancellation.");
|
|
||||||
step.ExecutionContext.Error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!conditionReTestResult)
|
|
||||||
{
|
|
||||||
// Cancel the step.
|
|
||||||
Trace.Info("Cancel current running step.");
|
|
||||||
step.ExecutionContext.CancelToken();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (jobContext.Result != TaskResult.Canceled)
|
|
||||||
{
|
|
||||||
// mark job as cancelled
|
|
||||||
jobContext.Result = TaskResult.Canceled;
|
|
||||||
jobContext.JobContext.Status = jobContext.Result?.ToActionResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate condition.
|
|
||||||
step.ExecutionContext.Debug($"Evaluating condition for step: '{step.DisplayName}'");
|
|
||||||
var conditionTraceWriter = new ConditionTraceWriter(Trace, step.ExecutionContext);
|
|
||||||
var conditionResult = false;
|
|
||||||
var conditionEvaluateError = default(Exception);
|
|
||||||
if (HostContext.RunnerShutdownToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
step.ExecutionContext.Debug($"Skip evaluate condition on runner shutdown.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var templateEvaluator = step.ExecutionContext.ToPipelineTemplateEvaluator(conditionTraceWriter);
|
|
||||||
var condition = new BasicExpressionToken(null, null, null, step.Condition);
|
|
||||||
conditionResult = templateEvaluator.EvaluateStepIf(condition, step.ExecutionContext.ExpressionValues, step.ExecutionContext.ExpressionFunctions, step.ExecutionContext.ToExpressionState());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Info("Caught exception from expression.");
|
|
||||||
Trace.Error(ex);
|
|
||||||
conditionEvaluateError = ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no evaluate error but condition is false
|
|
||||||
if (!conditionResult && conditionEvaluateError == null)
|
|
||||||
{
|
|
||||||
// Condition == false
|
|
||||||
Trace.Info("Skipping step due to condition evaluation.");
|
|
||||||
CompleteStep(step, nextStep, TaskResult.Skipped, resultCode: conditionTraceWriter.Trace);
|
|
||||||
}
|
|
||||||
else if (conditionEvaluateError != null)
|
|
||||||
{
|
|
||||||
// fail the step since there is an evaluate error.
|
|
||||||
step.ExecutionContext.Error(conditionEvaluateError);
|
|
||||||
CompleteStep(step, nextStep, TaskResult.Failed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Run the step.
|
|
||||||
await RunStepAsync(step, jobContext.CancellationToken);
|
|
||||||
CompleteStep(step, nextStep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (jobCancelRegister != null)
|
|
||||||
{
|
|
||||||
jobCancelRegister?.Dispose();
|
|
||||||
jobCancelRegister = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,125 +399,9 @@ namespace GitHub.Runner.Worker
|
|||||||
step.ExecutionContext.Debug($"Finishing: {step.DisplayName}");
|
step.ExecutionContext.Debug($"Finishing: {step.DisplayName}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool InitializeScope(IStep step, Dictionary<string, PipelineContextData> scopeInputs)
|
private void CompleteStep(IStep step, TaskResult? result = null, string resultCode = null)
|
||||||
{
|
{
|
||||||
var executionContext = step.ExecutionContext;
|
var executionContext = step.ExecutionContext;
|
||||||
var stepsContext = executionContext.StepsContext;
|
|
||||||
if (!string.IsNullOrEmpty(executionContext.ScopeName))
|
|
||||||
{
|
|
||||||
// Gather uninitialized current and ancestor scopes
|
|
||||||
var scope = executionContext.Scopes[executionContext.ScopeName];
|
|
||||||
var scopesToInitialize = default(Stack<ContextScope>);
|
|
||||||
while (scope != null && !scopeInputs.ContainsKey(scope.Name))
|
|
||||||
{
|
|
||||||
if (scopesToInitialize == null)
|
|
||||||
{
|
|
||||||
scopesToInitialize = new Stack<ContextScope>();
|
|
||||||
}
|
|
||||||
scopesToInitialize.Push(scope);
|
|
||||||
scope = string.IsNullOrEmpty(scope.ParentName) ? null : executionContext.Scopes[scope.ParentName];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize current and ancestor scopes
|
|
||||||
while (scopesToInitialize?.Count > 0)
|
|
||||||
{
|
|
||||||
scope = scopesToInitialize.Pop();
|
|
||||||
executionContext.Debug($"Initializing scope '{scope.Name}'");
|
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.ParentName);
|
|
||||||
// TODO: Fix this temporary workaround for Composite Actions
|
|
||||||
if (!executionContext.ExpressionValues.ContainsKey("inputs") && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
|
||||||
{
|
|
||||||
executionContext.ExpressionValues["inputs"] = !String.IsNullOrEmpty(scope.ParentName) ? scopeInputs[scope.ParentName] : null;
|
|
||||||
}
|
|
||||||
var templateEvaluator = executionContext.ToPipelineTemplateEvaluator();
|
|
||||||
var inputs = default(DictionaryContextData);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
inputs = templateEvaluator.EvaluateStepScopeInputs(scope.Inputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Info($"Caught exception from initialize scope '{scope.Name}'");
|
|
||||||
Trace.Error(ex);
|
|
||||||
executionContext.Error(ex);
|
|
||||||
executionContext.Complete(TaskResult.Failed);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
scopeInputs[scope.Name] = inputs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup expression values
|
|
||||||
var scopeName = executionContext.ScopeName;
|
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scopeName);
|
|
||||||
// TODO: Fix this temporary workaround for Composite Actions
|
|
||||||
if (!executionContext.ExpressionValues.ContainsKey("inputs") && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TESTING_COMPOSITE_ACTIONS_ALPHA")))
|
|
||||||
{
|
|
||||||
executionContext.ExpressionValues["inputs"] = string.IsNullOrEmpty(scopeName) ? null : scopeInputs[scopeName];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CompleteStep(IStep step, IStep nextStep, TaskResult? result = null, string resultCode = null)
|
|
||||||
{
|
|
||||||
var executionContext = step.ExecutionContext;
|
|
||||||
if (!string.IsNullOrEmpty(executionContext.ScopeName))
|
|
||||||
{
|
|
||||||
// Gather current and ancestor scopes to finalize
|
|
||||||
var scope = executionContext.Scopes[executionContext.ScopeName];
|
|
||||||
var scopesToFinalize = default(Queue<ContextScope>);
|
|
||||||
var nextStepScopeName = nextStep?.ExecutionContext.ScopeName;
|
|
||||||
while (scope != null &&
|
|
||||||
!string.Equals(nextStepScopeName, scope.Name, StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!(nextStepScopeName ?? string.Empty).StartsWith($"{scope.Name}.", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (scopesToFinalize == null)
|
|
||||||
{
|
|
||||||
scopesToFinalize = new Queue<ContextScope>();
|
|
||||||
}
|
|
||||||
scopesToFinalize.Enqueue(scope);
|
|
||||||
scope = string.IsNullOrEmpty(scope.ParentName) ? null : executionContext.Scopes[scope.ParentName];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize current and ancestor scopes
|
|
||||||
var stepsContext = step.ExecutionContext.StepsContext;
|
|
||||||
while (scopesToFinalize?.Count > 0)
|
|
||||||
{
|
|
||||||
scope = scopesToFinalize.Dequeue();
|
|
||||||
executionContext.Debug($"Finalizing scope '{scope.Name}'");
|
|
||||||
executionContext.ExpressionValues["steps"] = stepsContext.GetScope(scope.Name);
|
|
||||||
executionContext.ExpressionValues["inputs"] = null;
|
|
||||||
var templateEvaluator = executionContext.ToPipelineTemplateEvaluator();
|
|
||||||
var outputs = default(DictionaryContextData);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
outputs = templateEvaluator.EvaluateStepScopeOutputs(scope.Outputs, executionContext.ExpressionValues, executionContext.ExpressionFunctions);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Trace.Info($"Caught exception from finalize scope '{scope.Name}'");
|
|
||||||
Trace.Error(ex);
|
|
||||||
executionContext.Error(ex);
|
|
||||||
executionContext.Complete(TaskResult.Failed);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outputs?.Count > 0)
|
|
||||||
{
|
|
||||||
var parentScopeName = scope.ParentName;
|
|
||||||
var contextName = scope.ContextName;
|
|
||||||
foreach (var pair in outputs)
|
|
||||||
{
|
|
||||||
var outputName = pair.Key;
|
|
||||||
var outputValue = pair.Value.ToString();
|
|
||||||
stepsContext.SetOutput(parentScopeName, contextName, outputName, outputValue, out var reference);
|
|
||||||
executionContext.Debug($"{reference}='{outputValue}'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executionContext.Complete(result, resultCode: resultCode);
|
executionContext.Complete(result, resultCode: resultCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"name": "string",
|
"name": "string",
|
||||||
"description": "string",
|
"description": "string",
|
||||||
"inputs": "inputs",
|
"inputs": "inputs",
|
||||||
"runs": "runs"
|
"runs": "runs",
|
||||||
|
"outputs": "outputs"
|
||||||
},
|
},
|
||||||
"loose-key-type": "non-empty-string",
|
"loose-key-type": "non-empty-string",
|
||||||
"loose-value-type": "any"
|
"loose-value-type": "any"
|
||||||
@@ -28,6 +29,20 @@
|
|||||||
"loose-value-type": "any"
|
"loose-value-type": "any"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"outputs": {
|
||||||
|
"mapping": {
|
||||||
|
"loose-key-type": "non-empty-string",
|
||||||
|
"loose-value-type": "outputs-attributes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outputs-attributes": {
|
||||||
|
"mapping": {
|
||||||
|
"properties": {
|
||||||
|
"description": "string",
|
||||||
|
"value": "output-value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"runs": {
|
"runs": {
|
||||||
"one-of": [
|
"one-of": [
|
||||||
"container-runs",
|
"container-runs",
|
||||||
@@ -95,19 +110,13 @@
|
|||||||
"composite-steps": {
|
"composite-steps": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
"needs",
|
|
||||||
"strategy",
|
"strategy",
|
||||||
"matrix",
|
"matrix",
|
||||||
"secrets",
|
|
||||||
"steps",
|
"steps",
|
||||||
"inputs",
|
"inputs",
|
||||||
"job",
|
"job",
|
||||||
"runner",
|
"runner",
|
||||||
"env",
|
"env",
|
||||||
"always(0,0)",
|
|
||||||
"failure(0,0)",
|
|
||||||
"cancelled(0,0)",
|
|
||||||
"success(0,0)",
|
|
||||||
"hashFiles(1,255)"
|
"hashFiles(1,255)"
|
||||||
],
|
],
|
||||||
"sequence": {
|
"sequence": {
|
||||||
@@ -120,6 +129,19 @@
|
|||||||
],
|
],
|
||||||
"string": {}
|
"string": {}
|
||||||
},
|
},
|
||||||
|
"output-value": {
|
||||||
|
"context": [
|
||||||
|
"github",
|
||||||
|
"strategy",
|
||||||
|
"matrix",
|
||||||
|
"steps",
|
||||||
|
"inputs",
|
||||||
|
"job",
|
||||||
|
"runner",
|
||||||
|
"env"
|
||||||
|
],
|
||||||
|
"string": {}
|
||||||
|
},
|
||||||
"input-default-context": {
|
"input-default-context": {
|
||||||
"context": [
|
"context": [
|
||||||
"github",
|
"github",
|
||||||
|
|||||||
Reference in New Issue
Block a user