#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
using System;
using System.Collections.Generic;
using System.Threading;
using GitHub.Actions.Expressions;
using GitHub.Actions.Expressions.Data;
using GitHub.Actions.Expressions.Sdk.Functions;
using GitHub.Actions.WorkflowParser.Conversion;
using GitHub.Actions.WorkflowParser.ObjectTemplating;
using GitHub.Actions.WorkflowParser.ObjectTemplating.Schema;
using GitHub.Actions.WorkflowParser.ObjectTemplating.Tokens;
using ITraceWriter = GitHub.Actions.WorkflowParser.ObjectTemplating.ITraceWriter;
namespace GitHub.Actions.WorkflowParser
{
///
/// Evaluates parts of the workflow DOM. For example, a job strategy or step inputs.
///
public class WorkflowTemplateEvaluator
{
///
/// Creates a new instance for evaluating tokens within a workflow template.
///
/// Optional trace writer for telemetry
/// Optional file table from the workflow template, for better error messages
/// Optional workflow features
public WorkflowTemplateEvaluator(
ITraceWriter trace,
IList fileTable,
WorkflowFeatures features)
{
m_trace = trace ?? new EmptyTraceWriter();
m_fileTable = fileTable;
m_features = features ?? WorkflowFeatures.GetDefaults();
m_schema = WorkflowSchemaFactory.GetSchema(m_features);
}
///
/// Creates a new instance for evaluating tokens within a workflow template.
///
/// Optional trace writer for telemetry
/// Optional file table from the workflow template, for better error messages
/// Optional workflow features
/// Optional parent memory counter, for byte tracking across evaluation calls.
public WorkflowTemplateEvaluator(
ITraceWriter trace,
IList fileTable,
WorkflowFeatures features,
TemplateMemory parentMemory)
{
m_trace = trace ?? new EmptyTraceWriter();
m_fileTable = fileTable;
m_features = features ?? WorkflowFeatures.GetDefaults();
m_schema = WorkflowSchemaFactory.GetSchema(m_features);
m_parentMemory = parentMemory;
}
public Int32 MaxDepth => 50;
///
/// Gets the maximum error message length before the message will be truncated.
///
public Int32 MaxErrorMessageLength { get; set; } = 500;
///
/// Gets the maximum number of errors that can be recorded when parsing a workflow.
///
public Int32 MaxErrors => 10;
public Int32 MaxEvents => 1000000; // 1 million
public Int32 MaxResultSize { get; set; } = 10 * 1024 * 1024; // 10 mb
public Boolean EvaluateStageIf(
String stageId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions,
IEnumerable> expressionState)
{
var result = default(Boolean?);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.If}' for stage '{stageId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions, expressionState);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.JobIfResult, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToIfResult(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result ?? throw new InvalidOperationException("Stage if cannot be null");
}
public Boolean EvaluateJobIf(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions,
IEnumerable> expressionState)
{
var result = default(Boolean?);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.If}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions, expressionState);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.JobIfResult, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToIfResult(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result ?? throw new InvalidOperationException("Job if cannot be null");
}
///
/// Evaluates a job strategy token
///
/// The default job display name (any display name expression is evaluated after strategy)
public Strategy EvaluateStrategy(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions,
String jobName)
{
var result = new Strategy();
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.Strategy}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.Strategy, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToStrategy(context, token, jobName);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
if (result.Configurations.Count == 0)
{
var configuration = new StrategyConfiguration
{
Id = WorkflowConstants.DefaultJobName,
Name = new JobNameBuilder(jobName).Build(),
};
configuration.ExpressionData.Add(WorkflowTemplateConstants.Matrix, null);
configuration.ExpressionData.Add(
WorkflowTemplateConstants.Strategy,
new DictionaryExpressionData
{
{
"fail-fast",
new BooleanExpressionData(result.FailFast)
},
{
"job-index",
new NumberExpressionData(0)
},
{
"job-total",
new NumberExpressionData(1)
},
{
"max-parallel",
new NumberExpressionData(1)
}
});
result.Configurations.Add(configuration);
}
return result;
}
public String EvaluateJobName(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions,
String defaultName)
{
var result = default(String);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.Name}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.StringStrategyContext, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToJobName(context, token);
if (string.IsNullOrEmpty(result))
{
result = defaultName;
context.Memory.AddBytes(defaultName);
}
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result;
}
public DictionaryExpressionData EvaluateWorkflowJobInputs(
ReusableWorkflowJob workflowJob,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var inputDefinitions = workflowJob.InputDefinitions;
var inputValues = workflowJob.InputValues;
var result = default(DictionaryExpressionData);
if (inputDefinitions != null && inputDefinitions.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
var inputDefinitionsToken = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.WorkflowCallInputs, inputDefinitions, 0, null);
context.Errors.Check();
var inputValuesToken = default(TemplateToken);
if (inputValues != null && inputValues.Type != TokenType.Null)
{
inputValuesToken = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.WorkflowJobWith, inputValues, 0, null);
context.Errors.Check();
}
result = WorkflowTemplateConverter.ConvertToWorkflowJobInputs(context, inputDefinitionsToken, inputValuesToken, workflowJob);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? new DictionaryExpressionData();
}
public IDictionary EvaluateWorkflowJobOutputs(
MappingToken outputDefinitions,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(IDictionary);
if (outputDefinitions != null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
var outputs = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.WorkflowCallOutputs, outputDefinitions, 0, null);
result = WorkflowTemplateConverter.ConvertToWorkflowJobOutputs(outputs);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? new Dictionary(StringComparer.OrdinalIgnoreCase);
}
public ActionsEnvironmentReference EvaluateJobEnvironment(
string jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(ActionsEnvironmentReference);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.Environment}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
// Set "addMissingContexts:false" because the environment contains some properties
// that are intended to be evaluated on the server, and others on the runner.
//
// For example:
// environment:
// name: ${{ this evaluates on the server }}
// url: ${{ this evaluates on the runner }}
var context = CreateContext(expressionData, expressionFunctions, addMissingContexts: false);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.JobEnvironment, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToActionEnvironmentReference(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result;
}
public TemplateToken EvaluateJobEnvironmentUrl(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(TemplateToken);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.StringRunnerContextNoSecrets, token, 0, null);
context.Errors.Check();
result = token.AssertString("environment.url");
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public GroupPermitSetting EvaluateConcurrency(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(GroupPermitSetting);
string type;
string errorPrefix;
if (String.IsNullOrEmpty(jobId))
{
type = WorkflowTemplateConstants.WorkflowConcurrency;
errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.Concurrency}'.";
}
else
{
type = WorkflowTemplateConstants.JobConcurrency;
errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.Concurrency}' for job '{jobId}'.";
}
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, type, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToConcurrency(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result;
}
public RunsOn EvaluateRunsOn(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(RunsOn);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.RunsOn}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.RunsOn, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToRunsOn(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result ?? throw new InvalidOperationException("Job target cannot be null");
}
public Snapshot EvaluateSnapshot(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Snapshot);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.Snapshot}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.Snapshot, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToSnapshot(context, token);
}
catch (Exception ex) when (ex is not TemplateValidationException)
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result;
}
public Int32 EvaluateJobTimeout(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Int32?);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.TimeoutMinutes}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.NumberStrategyContext, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToJobTimeout(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result ?? WorkflowConstants.DefaultJobTimeoutInMinutes;
}
public Int32 EvaluateJobCancelTimeout(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Int32?);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.CancelTimeoutMinutes}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.NumberStrategyContext, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToJobCancelTimeout(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result ?? WorkflowConstants.DefaultJobCancelTimeoutInMinutes;
}
public Boolean EvaluateJobContinueOnError(
String jobId,
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Boolean?);
var errorPrefix = $"Error when evaluating '{WorkflowTemplateConstants.ContinueOnError}' for job '{jobId}'.";
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.BooleanStrategyContext, token, 0, null);
context.Errors.Check(errorPrefix);
result = WorkflowTemplateConverter.ConvertToJobContinueOnError(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check(errorPrefix);
}
return result ?? false;
}
public Boolean EvaluateStepContinueOnError(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Boolean?);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.BooleanStepsContext, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToStepContinueOnError(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? false;
}
public String EvaluateStepName(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(String);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.StringStepsContext, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToStepName(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public Boolean EvaluateStepIf(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions,
IEnumerable> expressionState)
{
var result = default(Boolean?);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions, expressionState);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.StepIfResult, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToIfResult(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? throw new InvalidOperationException("Step if cannot be null");
}
public Dictionary EvaluateStepEnvironment(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions,
StringComparer keyComparer)
{
var result = default(Dictionary);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.StepEnv, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToStepEnvironment(context, token, keyComparer);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? new Dictionary(keyComparer);
}
public Dictionary EvaluateStepInputs(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Dictionary);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.StepWith, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToStepInputs(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? new Dictionary(StringComparer.OrdinalIgnoreCase);
}
public Int32 EvaluateStepTimeout(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Int32?);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.NumberStepsContext, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToStepTimeout(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? 0;
}
public JobContainer EvaluateJobContainer(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(JobContainer);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.Container, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToJobContainer(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public IList> EvaluateJobServiceContainers(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(List>);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.Services, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToJobServiceContainers(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public Dictionary EvaluateJobDefaultsRun(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Dictionary);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.JobDefaultsRun, token, 0, null);
context.Errors.Check();
result = new Dictionary(StringComparer.OrdinalIgnoreCase);
var mapping = token.AssertMapping("defaults run");
foreach (var pair in mapping)
{
// Literal key
var key = pair.Key.AssertString("defaults run key");
// Literal value
var value = pair.Value.AssertString("defaults run value");
result[key.Value] = value.Value;
}
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public Dictionary EvaluateJobOutputs(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(Dictionary);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.JobOutputs, token, 0, null);
context.Errors.Check();
result = new Dictionary(StringComparer.OrdinalIgnoreCase);
var mapping = token.AssertMapping("outputs");
foreach (var pair in mapping)
{
// Literal key
var key = pair.Key.AssertString("output key");
// Literal value
var value = pair.Value.AssertString("output value");
result[key.Value] = value.Value;
}
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result;
}
public IDictionary EvaluateWorkflowJobSecrets(
TemplateToken token,
DictionaryExpressionData expressionData,
IList expressionFunctions)
{
var result = default(IDictionary);
if (token != null && token.Type != TokenType.Null)
{
var context = CreateContext(expressionData, expressionFunctions);
try
{
token = TemplateEvaluator.Evaluate(context, WorkflowTemplateConstants.WorkflowJobSecrets, token, 0, null);
context.Errors.Check();
result = WorkflowTemplateConverter.ConvertToWorkflowJobSecrets(context, token);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
context.Errors.Add(ex);
}
context.Errors.Check();
}
return result ?? new Dictionary(StringComparer.OrdinalIgnoreCase);
}
private TemplateContext CreateContext(
DictionaryExpressionData expressionData,
IList expressionFunctions,
IEnumerable> expressionState = null,
Boolean addMissingContexts = true)
{
var result = new TemplateContext
{
CancellationToken = CancellationToken.None,
Errors = new TemplateValidationErrors(MaxErrors, MaxErrorMessageLength),
Memory = new TemplateMemory(
maxDepth: MaxDepth,
maxEvents: MaxEvents,
maxBytes: MaxResultSize,
parent: m_parentMemory),
Schema = m_schema,
StrictJsonParsing = m_features.StrictJsonParsing,
TraceWriter = m_trace,
};
result.SetFeatures(m_features);
// Add the file table
if (m_fileTable?.Count > 0)
{
foreach (var file in m_fileTable)
{
result.GetFileId(file);
}
}
// Add named values
if (expressionData != null)
{
foreach (var pair in expressionData)
{
result.ExpressionValues[pair.Key] = pair.Value;
}
}
// Add functions
var functionNames = new HashSet(StringComparer.OrdinalIgnoreCase);
if (expressionFunctions?.Count > 0)
{
foreach (var function in expressionFunctions)
{
result.ExpressionFunctions.Add(function);
functionNames.Add(function.Name);
}
}
// Add missing expression values and expression functions.
// This solves the following problems:
// - Compat for new agent against old server (new contexts not sent down in job message)
// - Evaluating early when all referenced contexts are available, even though all allowed
// contexts may not yet be available. For example, evaluating step name can often
// be performed early.
if (addMissingContexts)
{
foreach (var name in s_expressionValueNames)
{
if (!result.ExpressionValues.ContainsKey(name))
{
result.ExpressionValues[name] = null;
}
}
foreach (var name in s_expressionFunctionNames)
{
if (!functionNames.Contains(name))
{
result.ExpressionFunctions.Add(new FunctionInfo(name, 0, Int32.MaxValue));
}
}
}
// Add vars context even if addMissingContexts is false to avoid
// JobEnvironment Evaluation errors
if(!result.ExpressionValues.ContainsKey(WorkflowTemplateConstants.Vars))
{
result.ExpressionValues[WorkflowTemplateConstants.Vars] = null;
}
// Add state
if (expressionState != null)
{
foreach (var pair in expressionState)
{
result.State[pair.Key] = pair.Value;
}
}
return result;
}
private readonly ITraceWriter m_trace;
private readonly TemplateSchema m_schema;
private readonly IList m_fileTable;
private readonly WorkflowFeatures m_features;
private readonly TemplateMemory m_parentMemory;
private readonly String[] s_expressionValueNames = new[]
{
WorkflowTemplateConstants.GitHub,
WorkflowTemplateConstants.Needs,
WorkflowTemplateConstants.Strategy,
WorkflowTemplateConstants.Matrix,
WorkflowTemplateConstants.Secrets,
WorkflowTemplateConstants.Vars,
WorkflowTemplateConstants.Steps,
WorkflowTemplateConstants.Inputs,
WorkflowTemplateConstants.Jobs,
WorkflowTemplateConstants.Job,
WorkflowTemplateConstants.Runner,
WorkflowTemplateConstants.Env,
};
private readonly String[] s_expressionFunctionNames = new[]
{
WorkflowTemplateConstants.Always,
WorkflowTemplateConstants.Cancelled,
WorkflowTemplateConstants.Failure,
WorkflowTemplateConstants.HashFiles,
WorkflowTemplateConstants.Success,
};
}
}