Skip to content

Commit b5b7986

Browse files
authored
Compare updated template evaluator (#4092)
1 parent 53d69ff commit b5b7986

File tree

188 files changed

+27222
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

188 files changed

+27222
-4
lines changed

src/Runner.Common/Constants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public static class Features
172172
public static readonly string ContainerActionRunnerTemp = "actions_container_action_runner_temp";
173173
public static readonly string SnapshotPreflightHostedRunnerCheck = "actions_snapshot_preflight_hosted_runner_check";
174174
public static readonly string SnapshotPreflightImageGenPoolCheck = "actions_snapshot_preflight_image_gen_pool_check";
175+
public static readonly string CompareTemplateEvaluator = "actions_runner_compare_template_evaluator";
175176
}
176177

177178
// Node version migration related constants

src/Runner.Worker/ExecutionContext.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,10 +1306,14 @@ public void ApplyContinueOnError(TemplateToken continueOnErrorToken)
13061306
UpdateGlobalStepsContext();
13071307
}
13081308

1309-
private static void NoOp()
1309+
internal IPipelineTemplateEvaluator ToPipelineTemplateEvaluatorInternal(ObjectTemplating.ITraceWriter traceWriter = null)
13101310
{
1311+
return new PipelineTemplateEvaluatorWrapper(HostContext, this, traceWriter);
13111312
}
13121313

1314+
private static void NoOp()
1315+
{
1316+
}
13131317
}
13141318

13151319
// The Error/Warning/etc methods are created as extension methods to simplify unit testing.
@@ -1390,8 +1394,15 @@ public static IEnumerable<KeyValuePair<string, object>> ToExpressionState(this I
13901394
return new[] { new KeyValuePair<string, object>(nameof(IExecutionContext), context) };
13911395
}
13921396

1393-
public static PipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null)
1397+
public static IPipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null)
13941398
{
1399+
// Create wrapper?
1400+
if ((context.Global.Variables.GetBoolean(Constants.Runner.Features.CompareTemplateEvaluator) ?? false) || StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_COMPARE_TEMPLATE_EVALUATOR")))
1401+
{
1402+
return (context as ExecutionContext).ToPipelineTemplateEvaluatorInternal(traceWriter);
1403+
}
1404+
1405+
// Legacy
13951406
if (traceWriter == null)
13961407
{
13971408
traceWriter = context.ToTemplateTraceWriter();

src/Runner.Worker/Expressions/AlwaysFunction.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,13 @@ protected override Object EvaluateCore(EvaluationContext context, out ResultMemo
2222
return true;
2323
}
2424
}
25+
26+
public sealed class NewAlwaysFunction : GitHub.Actions.Expressions.Sdk.Function
27+
{
28+
protected override Object EvaluateCore(GitHub.Actions.Expressions.Sdk.EvaluationContext context, out GitHub.Actions.Expressions.Sdk.ResultMemory resultMemory)
29+
{
30+
resultMemory = null;
31+
return true;
32+
}
33+
}
2534
}

src/Runner.Worker/Expressions/CancelledFunction.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,18 @@ protected sealed override object EvaluateCore(EvaluationContext evaluationContex
2828
return jobStatus == ActionResult.Cancelled;
2929
}
3030
}
31+
32+
public sealed class NewCancelledFunction : GitHub.Actions.Expressions.Sdk.Function
33+
{
34+
protected sealed override object EvaluateCore(GitHub.Actions.Expressions.Sdk.EvaluationContext evaluationContext, out GitHub.Actions.Expressions.Sdk.ResultMemory resultMemory)
35+
{
36+
resultMemory = null;
37+
var templateContext = evaluationContext.State as GitHub.Actions.WorkflowParser.ObjectTemplating.TemplateContext;
38+
ArgUtil.NotNull(templateContext, nameof(templateContext));
39+
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
40+
ArgUtil.NotNull(executionContext, nameof(executionContext));
41+
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
42+
return jobStatus == ActionResult.Cancelled;
43+
}
44+
}
3145
}

src/Runner.Worker/Expressions/FailureFunction.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,29 @@ protected sealed override object EvaluateCore(EvaluationContext evaluationContex
3939
}
4040
}
4141
}
42+
43+
public sealed class NewFailureFunction : GitHub.Actions.Expressions.Sdk.Function
44+
{
45+
protected sealed override object EvaluateCore(GitHub.Actions.Expressions.Sdk.EvaluationContext evaluationContext, out GitHub.Actions.Expressions.Sdk.ResultMemory resultMemory)
46+
{
47+
resultMemory = null;
48+
var templateContext = evaluationContext.State as GitHub.Actions.WorkflowParser.ObjectTemplating.TemplateContext;
49+
ArgUtil.NotNull(templateContext, nameof(templateContext));
50+
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
51+
ArgUtil.NotNull(executionContext, nameof(executionContext));
52+
53+
// Decide based on 'action_status' for composite MAIN steps and 'job.status' for pre, post and job-level steps
54+
var isCompositeMainStep = executionContext.IsEmbedded && executionContext.Stage == ActionRunStage.Main;
55+
if (isCompositeMainStep)
56+
{
57+
ActionResult actionStatus = EnumUtil.TryParse<ActionResult>(executionContext.GetGitHubContext("action_status")) ?? ActionResult.Success;
58+
return actionStatus == ActionResult.Failure;
59+
}
60+
else
61+
{
62+
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
63+
return jobStatus == ActionResult.Failure;
64+
}
65+
}
66+
}
4267
}

src/Runner.Worker/Expressions/HashFilesFunction.cs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,137 @@ public void Verbose(string message)
143143
}
144144
}
145145
}
146+
147+
public sealed class NewHashFilesFunction : GitHub.Actions.Expressions.Sdk.Function
148+
{
149+
private const int _hashFileTimeoutSeconds = 120;
150+
151+
protected sealed override Object EvaluateCore(
152+
GitHub.Actions.Expressions.Sdk.EvaluationContext context,
153+
out GitHub.Actions.Expressions.Sdk.ResultMemory resultMemory)
154+
{
155+
resultMemory = null;
156+
var templateContext = context.State as GitHub.Actions.WorkflowParser.ObjectTemplating.TemplateContext;
157+
ArgUtil.NotNull(templateContext, nameof(templateContext));
158+
templateContext.ExpressionValues.TryGetValue(PipelineTemplateConstants.GitHub, out var githubContextData);
159+
ArgUtil.NotNull(githubContextData, nameof(githubContextData));
160+
var githubContext = githubContextData as GitHub.Actions.Expressions.Data.DictionaryExpressionData;
161+
ArgUtil.NotNull(githubContext, nameof(githubContext));
162+
163+
if (!githubContext.TryGetValue(PipelineTemplateConstants.HostWorkspace, out var workspace))
164+
{
165+
githubContext.TryGetValue(PipelineTemplateConstants.Workspace, out workspace);
166+
}
167+
ArgUtil.NotNull(workspace, nameof(workspace));
168+
169+
var workspaceData = workspace as GitHub.Actions.Expressions.Data.StringExpressionData;
170+
ArgUtil.NotNull(workspaceData, nameof(workspaceData));
171+
172+
string githubWorkspace = workspaceData.Value;
173+
174+
bool followSymlink = false;
175+
List<string> patterns = new();
176+
var firstParameter = true;
177+
foreach (var parameter in Parameters)
178+
{
179+
var parameterString = parameter.Evaluate(context).ConvertToString();
180+
if (firstParameter)
181+
{
182+
firstParameter = false;
183+
if (parameterString.StartsWith("--"))
184+
{
185+
if (string.Equals(parameterString, "--follow-symbolic-links", StringComparison.OrdinalIgnoreCase))
186+
{
187+
followSymlink = true;
188+
continue;
189+
}
190+
else
191+
{
192+
throw new ArgumentOutOfRangeException($"Invalid glob option {parameterString}, avaliable option: '--follow-symbolic-links'.");
193+
}
194+
}
195+
}
196+
197+
patterns.Add(parameterString);
198+
}
199+
200+
context.Trace.Info($"Search root directory: '{githubWorkspace}'");
201+
context.Trace.Info($"Search pattern: '{string.Join(", ", patterns)}'");
202+
203+
string binDir = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
204+
string runnerRoot = new DirectoryInfo(binDir).Parent.FullName;
205+
206+
string node = Path.Combine(runnerRoot, "externals", NodeUtil.GetInternalNodeVersion(), "bin", $"node{IOUtil.ExeExtension}");
207+
string hashFilesScript = Path.Combine(binDir, "hashFiles");
208+
var hashResult = string.Empty;
209+
var p = new ProcessInvoker(new NewHashFilesTrace(context.Trace));
210+
p.ErrorDataReceived += ((_, data) =>
211+
{
212+
if (!string.IsNullOrEmpty(data.Data) && data.Data.StartsWith("__OUTPUT__") && data.Data.EndsWith("__OUTPUT__"))
213+
{
214+
hashResult = data.Data.Substring(10, data.Data.Length - 20);
215+
context.Trace.Info($"Hash result: '{hashResult}'");
216+
}
217+
else
218+
{
219+
context.Trace.Info(data.Data);
220+
}
221+
});
222+
223+
p.OutputDataReceived += ((_, data) =>
224+
{
225+
context.Trace.Info(data.Data);
226+
});
227+
228+
var env = new Dictionary<string, string>();
229+
if (followSymlink)
230+
{
231+
env["followSymbolicLinks"] = "true";
232+
}
233+
env["patterns"] = string.Join(Environment.NewLine, patterns);
234+
235+
using (var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(_hashFileTimeoutSeconds)))
236+
{
237+
try
238+
{
239+
int exitCode = p.ExecuteAsync(workingDirectory: githubWorkspace,
240+
fileName: node,
241+
arguments: $"\"{hashFilesScript.Replace("\"", "\\\"")}\"",
242+
environment: env,
243+
requireExitCodeZero: false,
244+
cancellationToken: tokenSource.Token).GetAwaiter().GetResult();
245+
246+
if (exitCode != 0)
247+
{
248+
throw new InvalidOperationException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') failed. Fail to hash files under directory '{githubWorkspace}'");
249+
}
250+
}
251+
catch (OperationCanceledException) when (tokenSource.IsCancellationRequested)
252+
{
253+
throw new TimeoutException($"hashFiles('{ExpressionUtility.StringEscape(string.Join(", ", patterns))}') couldn't finish within {_hashFileTimeoutSeconds} seconds.");
254+
}
255+
256+
return hashResult;
257+
}
258+
}
259+
260+
private sealed class NewHashFilesTrace : ITraceWriter
261+
{
262+
private GitHub.Actions.Expressions.ITraceWriter _trace;
263+
264+
public NewHashFilesTrace(GitHub.Actions.Expressions.ITraceWriter trace)
265+
{
266+
_trace = trace;
267+
}
268+
public void Info(string message)
269+
{
270+
_trace.Info(message);
271+
}
272+
273+
public void Verbose(string message)
274+
{
275+
_trace.Info(message);
276+
}
277+
}
278+
}
146279
}

src/Runner.Worker/Expressions/SuccessFunction.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,29 @@ protected sealed override object EvaluateCore(EvaluationContext evaluationContex
3939
}
4040
}
4141
}
42+
43+
public sealed class NewSuccessFunction : GitHub.Actions.Expressions.Sdk.Function
44+
{
45+
protected sealed override object EvaluateCore(GitHub.Actions.Expressions.Sdk.EvaluationContext evaluationContext, out GitHub.Actions.Expressions.Sdk.ResultMemory resultMemory)
46+
{
47+
resultMemory = null;
48+
var templateContext = evaluationContext.State as GitHub.Actions.WorkflowParser.ObjectTemplating.TemplateContext;
49+
ArgUtil.NotNull(templateContext, nameof(templateContext));
50+
var executionContext = templateContext.State[nameof(IExecutionContext)] as IExecutionContext;
51+
ArgUtil.NotNull(executionContext, nameof(executionContext));
52+
53+
// Decide based on 'action_status' for composite MAIN steps and 'job.status' for pre, post and job-level steps
54+
var isCompositeMainStep = executionContext.IsEmbedded && executionContext.Stage == ActionRunStage.Main;
55+
if (isCompositeMainStep)
56+
{
57+
ActionResult actionStatus = EnumUtil.TryParse<ActionResult>(executionContext.GetGitHubContext("action_status")) ?? ActionResult.Success;
58+
return actionStatus == ActionResult.Success;
59+
}
60+
else
61+
{
62+
ActionResult jobStatus = executionContext.JobContext.Status ?? ActionResult.Success;
63+
return jobStatus == ActionResult.Success;
64+
}
65+
}
66+
}
4267
}

src/Runner.Worker/GlobalContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ public sealed class GlobalContext
2929
public bool WriteDebug { get; set; }
3030
public string InfrastructureFailureCategory { get; set; }
3131
public JObject ContainerHookState { get; set; }
32+
public bool HasTemplateEvaluatorMismatch { get; set; }
3233
}
3334
}

0 commit comments

Comments
 (0)