mirror of
https://github.com/actions/runner.git
synced 2026-03-05 13:23:03 +08:00
Compare commits
1 Commits
dependabot
...
releases/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a153d802ff |
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "Actions Runner Devcontainer",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:noble",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:focal",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/dotnet": {
|
||||
"version": "8.0.418"
|
||||
},
|
||||
|
||||
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
||||
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -133,37 +133,37 @@ jobs:
|
||||
|
||||
# Download runner package tar.gz/zip produced by 'build' job
|
||||
- name: Download Artifact (win-x64)
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: runner-packages-win-x64
|
||||
path: ./
|
||||
- name: Download Artifact (win-arm64)
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: runner-packages-win-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-x64)
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: runner-packages-osx-x64
|
||||
path: ./
|
||||
- name: Download Artifact (osx-arm64)
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: runner-packages-osx-arm64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-x64)
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: runner-packages-linux-x64
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm)
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: runner-packages-linux-arm
|
||||
path: ./
|
||||
- name: Download Artifact (linux-arm64)
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: runner-packages-linux-arm64
|
||||
path: ./
|
||||
@@ -312,7 +312,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<Update to ./src/runnerversion when creating release>
|
||||
2.332.0
|
||||
|
||||
@@ -7,7 +7,7 @@ NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download
|
||||
# When you update Node versions you must also create a new release of alpine_nodejs at that updated version.
|
||||
# Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started
|
||||
NODE20_VERSION="20.20.0"
|
||||
NODE24_VERSION="24.14.0"
|
||||
NODE24_VERSION="24.13.1"
|
||||
|
||||
get_abs_path() {
|
||||
# exploits the fact that pwd will print abs path when no args
|
||||
|
||||
@@ -172,7 +172,6 @@ namespace GitHub.Runner.Common
|
||||
public static readonly string SnapshotPreflightHostedRunnerCheck = "actions_snapshot_preflight_hosted_runner_check";
|
||||
public static readonly string SnapshotPreflightImageGenPoolCheck = "actions_snapshot_preflight_image_gen_pool_check";
|
||||
public static readonly string CompareWorkflowParser = "actions_runner_compare_workflow_parser";
|
||||
public static readonly string ServiceContainerCommand = "actions_service_container_command";
|
||||
public static readonly string SetOrchestrationIdEnvForActions = "actions_set_orchestration_id_env_for_actions";
|
||||
public static readonly string SendJobLevelAnnotations = "actions_send_job_level_annotations";
|
||||
public static readonly string EmitCompositeMarkers = "actions_runner_emit_composite_markers";
|
||||
|
||||
@@ -120,10 +120,8 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error(ex);
|
||||
_terminal.WriteError($"Runner update failed: {ex.Message}");
|
||||
_updateTrace.Enqueue(ex.ToString());
|
||||
return false;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -120,10 +120,8 @@ namespace GitHub.Runner.Listener
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Error(ex);
|
||||
_terminal.WriteError($"Runner update failed: {ex.Message}");
|
||||
_updateTrace.Enqueue(ex.ToString());
|
||||
return false;
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace GitHub.Runner.Worker
|
||||
{
|
||||
// Log the error and fail the PrepareActionsAsync Initialization.
|
||||
Trace.Error($"Caught exception from PrepareActionsAsync Initialization: {ex}");
|
||||
executionContext.InfrastructureError(ex.InnerException?.Message ?? ex.Message, category: "resolve_action");
|
||||
executionContext.InfrastructureError(ex.Message, category: "resolve_action");
|
||||
executionContext.Result = TaskResult.Failed;
|
||||
throw;
|
||||
}
|
||||
@@ -818,7 +818,7 @@ namespace GitHub.Runner.Worker
|
||||
try
|
||||
{
|
||||
Trace.Info($"Found unpacked action directory '{cacheDirectory}' in cache directory '{actionArchiveCacheDir}'");
|
||||
|
||||
|
||||
// repository archive from github always contains a nested folder
|
||||
var nestedDirectories = new DirectoryInfo(cacheDirectory).GetDirectories();
|
||||
if (nestedDirectories.Length != 1)
|
||||
@@ -832,14 +832,14 @@ namespace GitHub.Runner.Worker
|
||||
IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken);
|
||||
IOUtil.CreateSymbolicLink(destDirectory, nestedDirectories[0].FullName);
|
||||
}
|
||||
|
||||
|
||||
executionContext.Debug($"Created symlink from cached directory '{cacheDirectory}' to '{destDirectory}'");
|
||||
executionContext.Global.JobTelemetry.Add(new JobTelemetry()
|
||||
{
|
||||
Type = JobTelemetryType.General,
|
||||
Message = $"Action archive cache usage: {downloadInfo.ResolvedNameWithOwner}@{downloadInfo.ResolvedSha} use cache {useActionArchiveCache} has cache {hasActionArchiveCache} via symlink"
|
||||
});
|
||||
|
||||
|
||||
Trace.Info("Finished getting action repository.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ namespace GitHub.Runner.Worker.Container
|
||||
this.ContainerImage = containerImage;
|
||||
this.ContainerDisplayName = $"{container.Alias}_{Pipelines.Validation.NameValidation.Sanitize(containerImage)}_{Guid.NewGuid().ToString("N").Substring(0, 6)}";
|
||||
this.ContainerCreateOptions = container.Options;
|
||||
this.ContainerEntryPoint = container.Entrypoint;
|
||||
this.ContainerEntryPointArgs = container.Command;
|
||||
_environmentVariables = container.Environment;
|
||||
this.IsJobContainer = isJobContainer;
|
||||
this.ContainerNetworkAlias = networkAlias;
|
||||
|
||||
@@ -1328,9 +1328,9 @@ namespace GitHub.Runner.Worker
|
||||
UpdateGlobalStepsContext();
|
||||
}
|
||||
|
||||
internal IPipelineTemplateEvaluator ToPipelineTemplateEvaluatorInternal(bool allowServiceContainerCommand, ObjectTemplating.ITraceWriter traceWriter = null)
|
||||
internal IPipelineTemplateEvaluator ToPipelineTemplateEvaluatorInternal(ObjectTemplating.ITraceWriter traceWriter = null)
|
||||
{
|
||||
return new PipelineTemplateEvaluatorWrapper(HostContext, this, allowServiceContainerCommand, traceWriter);
|
||||
return new PipelineTemplateEvaluatorWrapper(HostContext, this, traceWriter);
|
||||
}
|
||||
|
||||
private static void NoOp()
|
||||
@@ -1418,13 +1418,10 @@ namespace GitHub.Runner.Worker
|
||||
|
||||
public static IPipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecutionContext context, ObjectTemplating.ITraceWriter traceWriter = null)
|
||||
{
|
||||
var allowServiceContainerCommand = (context.Global.Variables.GetBoolean(Constants.Runner.Features.ServiceContainerCommand) ?? false)
|
||||
|| StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_SERVICE_CONTAINER_COMMAND"));
|
||||
|
||||
// Create wrapper?
|
||||
if ((context.Global.Variables.GetBoolean(Constants.Runner.Features.CompareWorkflowParser) ?? false) || StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("ACTIONS_RUNNER_COMPARE_WORKFLOW_PARSER")))
|
||||
{
|
||||
return (context as ExecutionContext).ToPipelineTemplateEvaluatorInternal(allowServiceContainerCommand, traceWriter);
|
||||
return (context as ExecutionContext).ToPipelineTemplateEvaluatorInternal(traceWriter);
|
||||
}
|
||||
|
||||
// Legacy
|
||||
@@ -1436,7 +1433,6 @@ namespace GitHub.Runner.Worker
|
||||
return new PipelineTemplateEvaluator(traceWriter, schema, context.Global.FileTable)
|
||||
{
|
||||
MaxErrorMessageLength = int.MaxValue, // Don't truncate error messages otherwise we might not scrub secrets correctly
|
||||
AllowServiceContainerCommand = allowServiceContainerCommand,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -312,14 +312,7 @@ namespace GitHub.Runner.Worker.Handlers
|
||||
// Emit start marker after full context setup so display name expressions resolve correctly
|
||||
if (emitCompositeMarkers)
|
||||
{
|
||||
try
|
||||
{
|
||||
step.EvaluateDisplayName(step.ExecutionContext.ExpressionValues, step.ExecutionContext, out _);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Warning("Caught exception while evaluating embedded step display name. {0}", ex);
|
||||
}
|
||||
step.TryUpdateDisplayName(out _);
|
||||
ExecutionContext.Output($"##[start-action display={EscapeProperty(SanitizeDisplayName(step.DisplayName))};id={EscapeProperty(markerId)}]");
|
||||
stepStopwatch = Stopwatch.StartNew();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace GitHub.Runner.Worker
|
||||
public PipelineTemplateEvaluatorWrapper(
|
||||
IHostContext hostContext,
|
||||
IExecutionContext context,
|
||||
bool allowServiceContainerCommand,
|
||||
ObjectTemplating.ITraceWriter traceWriter = null)
|
||||
{
|
||||
ArgUtil.NotNull(hostContext, nameof(hostContext));
|
||||
@@ -41,14 +40,11 @@ namespace GitHub.Runner.Worker
|
||||
_legacyEvaluator = new PipelineTemplateEvaluator(traceWriter, schema, context.Global.FileTable)
|
||||
{
|
||||
MaxErrorMessageLength = int.MaxValue, // Don't truncate error messages otherwise we might not scrub secrets correctly
|
||||
AllowServiceContainerCommand = allowServiceContainerCommand,
|
||||
};
|
||||
|
||||
// New evaluator
|
||||
var newTraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter();
|
||||
var features = WorkflowFeatures.GetDefaults();
|
||||
features.AllowServiceContainerCommand = allowServiceContainerCommand;
|
||||
_newEvaluator = new WorkflowTemplateEvaluator(newTraceWriter, context.Global.FileTable, features)
|
||||
_newEvaluator = new WorkflowTemplateEvaluator(newTraceWriter, context.Global.FileTable, features: null)
|
||||
{
|
||||
MaxErrorMessageLength = int.MaxValue, // Don't truncate error messages otherwise we might not scrub secrets correctly
|
||||
};
|
||||
@@ -405,18 +401,6 @@ namespace GitHub.Runner.Worker
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(legacyResult.Entrypoint, newResult.Entrypoint, StringComparison.Ordinal))
|
||||
{
|
||||
_trace.Info($"CompareJobContainer mismatch - Entrypoint differs (legacy='{legacyResult.Entrypoint}', new='{newResult.Entrypoint}')");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.Equals(legacyResult.Command, newResult.Command, StringComparison.Ordinal))
|
||||
{
|
||||
_trace.Info($"CompareJobContainer mismatch - Command differs (legacy='{legacyResult.Command}', new='{newResult.Command}')");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CompareDictionaries(legacyResult.Environment, newResult.Environment, "Environment"))
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -39,24 +39,6 @@ namespace GitHub.DistributedTask.Pipelines
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the container entrypoint override.
|
||||
/// </summary>
|
||||
public String Entrypoint
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the container command and args (after the image name).
|
||||
/// </summary>
|
||||
public String Command
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the volumes which are mounted into the container.
|
||||
/// </summary>
|
||||
|
||||
@@ -47,8 +47,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
public const String NumberStrategyContext = "number-strategy-context";
|
||||
public const String On = "on";
|
||||
public const String Options = "options";
|
||||
public const String Entrypoint = "entrypoint";
|
||||
public const String Command = "command";
|
||||
public const String Outputs = "outputs";
|
||||
public const String OutputsPattern = "needs.*.outputs";
|
||||
public const String Password = "password";
|
||||
|
||||
@@ -237,8 +237,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
internal static JobContainer ConvertToJobContainer(
|
||||
TemplateContext context,
|
||||
TemplateToken value,
|
||||
bool allowExpressions = false,
|
||||
bool allowServiceContainerCommand = false)
|
||||
bool allowExpressions = false)
|
||||
{
|
||||
var result = new JobContainer();
|
||||
if (allowExpressions && value.Traverse().Any(x => x is ExpressionToken))
|
||||
@@ -281,22 +280,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
case PipelineTemplateConstants.Options:
|
||||
result.Options = containerPropertyPair.Value.AssertString($"{PipelineTemplateConstants.Container} {propertyName}").Value;
|
||||
break;
|
||||
case PipelineTemplateConstants.Entrypoint:
|
||||
if (!allowServiceContainerCommand)
|
||||
{
|
||||
context.Error(containerPropertyPair.Key, $"The key '{PipelineTemplateConstants.Entrypoint}' is not allowed");
|
||||
break;
|
||||
}
|
||||
result.Entrypoint = containerPropertyPair.Value.AssertString($"{PipelineTemplateConstants.Container} {propertyName}").Value;
|
||||
break;
|
||||
case PipelineTemplateConstants.Command:
|
||||
if (!allowServiceContainerCommand)
|
||||
{
|
||||
context.Error(containerPropertyPair.Key, $"The key '{PipelineTemplateConstants.Command}' is not allowed");
|
||||
break;
|
||||
}
|
||||
result.Command = containerPropertyPair.Value.AssertString($"{PipelineTemplateConstants.Container} {propertyName}").Value;
|
||||
break;
|
||||
case PipelineTemplateConstants.Ports:
|
||||
var ports = containerPropertyPair.Value.AssertSequence($"{PipelineTemplateConstants.Container} {propertyName}");
|
||||
var portList = new List<String>(ports.Count);
|
||||
@@ -343,8 +326,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
internal static List<KeyValuePair<String, JobContainer>> ConvertToJobServiceContainers(
|
||||
TemplateContext context,
|
||||
TemplateToken services,
|
||||
bool allowExpressions = false,
|
||||
bool allowServiceContainerCommand = false)
|
||||
bool allowExpressions = false)
|
||||
{
|
||||
var result = new List<KeyValuePair<String, JobContainer>>();
|
||||
|
||||
@@ -358,7 +340,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
foreach (var servicePair in servicesMapping)
|
||||
{
|
||||
var networkAlias = servicePair.Key.AssertString("services key").Value;
|
||||
var container = ConvertToJobContainer(context, servicePair.Value, allowExpressions, allowServiceContainerCommand);
|
||||
var container = ConvertToJobContainer(context, servicePair.Value);
|
||||
result.Add(new KeyValuePair<String, JobContainer>(networkAlias, container));
|
||||
}
|
||||
|
||||
|
||||
@@ -51,8 +51,6 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
|
||||
public Int32 MaxResultSize { get; set; } = 10 * 1024 * 1024; // 10 mb
|
||||
|
||||
public bool AllowServiceContainerCommand { get; set; }
|
||||
|
||||
public Boolean EvaluateStepContinueOnError(
|
||||
TemplateToken token,
|
||||
DictionaryContextData contextData,
|
||||
@@ -359,7 +357,7 @@ namespace GitHub.DistributedTask.Pipelines.ObjectTemplating
|
||||
{
|
||||
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Services, token, 0, null, omitHeader: true);
|
||||
context.Errors.Check();
|
||||
result = PipelineTemplateConverter.ConvertToJobServiceContainers(context, token, allowServiceContainerCommand: AllowServiceContainerCommand);
|
||||
result = PipelineTemplateConverter.ConvertToJobServiceContainers(context, token);
|
||||
}
|
||||
catch (Exception ex) when (!(ex is TemplateValidationException))
|
||||
{
|
||||
|
||||
@@ -430,21 +430,6 @@
|
||||
}
|
||||
},
|
||||
|
||||
"service-container-mapping": {
|
||||
"mapping": {
|
||||
"properties": {
|
||||
"image": "string",
|
||||
"options": "string",
|
||||
"entrypoint": "string",
|
||||
"command": "string",
|
||||
"env": "container-env",
|
||||
"ports": "sequence-of-non-empty-string",
|
||||
"volumes": "sequence-of-non-empty-string",
|
||||
"credentials": "container-registry-credentials"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"services": {
|
||||
"context": [
|
||||
"github",
|
||||
@@ -469,7 +454,7 @@
|
||||
],
|
||||
"one-of": [
|
||||
"string",
|
||||
"service-container-mapping"
|
||||
"container-mapping"
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -62,8 +62,6 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
||||
public const String NumberStrategyContext = "number-strategy-context";
|
||||
public const String On = "on";
|
||||
public const String Options = "options";
|
||||
public const String Entrypoint = "entrypoint";
|
||||
public const String Command = "command";
|
||||
public const String Org = "org";
|
||||
public const String Organization = "organization";
|
||||
public const String Outputs = "outputs";
|
||||
|
||||
@@ -1079,8 +1079,7 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
||||
internal static JobContainer ConvertToJobContainer(
|
||||
TemplateContext context,
|
||||
TemplateToken value,
|
||||
bool isEarlyValidation = false,
|
||||
bool isServiceContainer = false)
|
||||
bool isEarlyValidation = false)
|
||||
{
|
||||
var result = new JobContainer();
|
||||
if (isEarlyValidation && value.Traverse().Any(x => x is ExpressionToken))
|
||||
@@ -1090,34 +1089,11 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
||||
|
||||
if (value is StringToken containerLiteral)
|
||||
{
|
||||
// Trim "docker://"
|
||||
var trimmedImage = containerLiteral.Value;
|
||||
var hasDockerPrefix = containerLiteral.Value.StartsWith(WorkflowTemplateConstants.DockerUriPrefix, StringComparison.Ordinal);
|
||||
if (hasDockerPrefix)
|
||||
if (String.IsNullOrEmpty(containerLiteral.Value))
|
||||
{
|
||||
trimmedImage = trimmedImage.Substring(WorkflowTemplateConstants.DockerUriPrefix.Length);
|
||||
}
|
||||
|
||||
// Empty shorthand after trimming "docker://" ?
|
||||
if (String.IsNullOrEmpty(trimmedImage))
|
||||
{
|
||||
// Error at parse-time for:
|
||||
// 1. container: 'docker://'
|
||||
// 2. services.foo: ''
|
||||
// 3. services.foo: 'docker://'
|
||||
//
|
||||
// Do not error for:
|
||||
// 1. container: ''
|
||||
if (isEarlyValidation && (hasDockerPrefix || isServiceContainer))
|
||||
{
|
||||
context.Error(value, "Container image cannot be empty");
|
||||
}
|
||||
|
||||
// Short-circuit
|
||||
return null;
|
||||
}
|
||||
|
||||
// Store original, trimmed further below
|
||||
result.Image = containerLiteral.Value;
|
||||
}
|
||||
else
|
||||
@@ -1146,22 +1122,6 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
||||
case WorkflowTemplateConstants.Options:
|
||||
result.Options = containerPropertyPair.Value.AssertString($"{WorkflowTemplateConstants.Container} {propertyName}").Value;
|
||||
break;
|
||||
case WorkflowTemplateConstants.Entrypoint:
|
||||
if (!context.GetFeatures().AllowServiceContainerCommand)
|
||||
{
|
||||
context.Error(containerPropertyPair.Key, $"The key '{WorkflowTemplateConstants.Entrypoint}' is not allowed");
|
||||
break;
|
||||
}
|
||||
result.Entrypoint = containerPropertyPair.Value.AssertString($"{WorkflowTemplateConstants.Container} {propertyName}").Value;
|
||||
break;
|
||||
case WorkflowTemplateConstants.Command:
|
||||
if (!context.GetFeatures().AllowServiceContainerCommand)
|
||||
{
|
||||
context.Error(containerPropertyPair.Key, $"The key '{WorkflowTemplateConstants.Command}' is not allowed");
|
||||
break;
|
||||
}
|
||||
result.Command = containerPropertyPair.Value.AssertString($"{WorkflowTemplateConstants.Container} {propertyName}").Value;
|
||||
break;
|
||||
case WorkflowTemplateConstants.Ports:
|
||||
var ports = containerPropertyPair.Value.AssertSequence($"{WorkflowTemplateConstants.Container} {propertyName}");
|
||||
var portList = new List<String>(ports.Count);
|
||||
@@ -1192,30 +1152,22 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
||||
}
|
||||
}
|
||||
|
||||
// Trim "docker://"
|
||||
var hadDockerPrefix = false;
|
||||
if (!String.IsNullOrEmpty(result.Image) && result.Image.StartsWith(WorkflowTemplateConstants.DockerUriPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
hadDockerPrefix = true;
|
||||
result.Image = result.Image.Substring(WorkflowTemplateConstants.DockerUriPrefix.Length);
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(result.Image))
|
||||
{
|
||||
// Error at parse-time for:
|
||||
// 1. container: {image: 'docker://'}
|
||||
// 2. services.foo: {image: ''}
|
||||
// 3. services.foo: {image: 'docker://'}
|
||||
//
|
||||
// Do not error for:
|
||||
// 1. container: {image: ''}
|
||||
if (isEarlyValidation && (hadDockerPrefix || isServiceContainer))
|
||||
// Only error during early validation (parse time)
|
||||
// At runtime (expression evaluation), empty image = no container
|
||||
if (isEarlyValidation)
|
||||
{
|
||||
context.Error(value, "Container image cannot be empty");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result.Image.StartsWith(WorkflowTemplateConstants.DockerUriPrefix, StringComparison.Ordinal))
|
||||
{
|
||||
result.Image = result.Image.Substring(WorkflowTemplateConstants.DockerUriPrefix.Length);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1236,7 +1188,7 @@ namespace GitHub.Actions.WorkflowParser.Conversion
|
||||
foreach (var servicePair in servicesMapping)
|
||||
{
|
||||
var networkAlias = servicePair.Key.AssertString("services key").Value;
|
||||
var container = ConvertToJobContainer(context, servicePair.Value, isEarlyValidation, isServiceContainer: true);
|
||||
var container = ConvertToJobContainer(context, servicePair.Value);
|
||||
result.Add(new KeyValuePair<String, JobContainer>(networkAlias, container));
|
||||
}
|
||||
|
||||
|
||||
@@ -35,24 +35,6 @@ namespace GitHub.Actions.WorkflowParser
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the container entrypoint override.
|
||||
/// </summary>
|
||||
public String Entrypoint
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the container command and args (after the image name).
|
||||
/// </summary>
|
||||
public String Command
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the volumes which are mounted into the container.
|
||||
/// </summary>
|
||||
|
||||
@@ -48,13 +48,6 @@ namespace GitHub.Actions.WorkflowParser
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public bool StrictJsonParsing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether service containers may specify "entrypoint" and "command".
|
||||
/// Used during parsing and evaluation.
|
||||
/// </summary>
|
||||
[DataMember(EmitDefaultValue = false)]
|
||||
public bool AllowServiceContainerCommand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default workflow features.
|
||||
/// </summary>
|
||||
@@ -67,7 +60,6 @@ namespace GitHub.Actions.WorkflowParser
|
||||
Snapshot = false, // Default to false since this feature is still in an experimental phase
|
||||
StrictJsonParsing = false, // Default to false since this is temporary for telemetry purposes only
|
||||
AllowModelsPermission = false, // Default to false since we want this to be disabled for all non-production environments
|
||||
AllowServiceContainerCommand = false, // Default to false since this feature is gated by actions_service_container_command
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2589,53 +2589,21 @@
|
||||
"mapping": {
|
||||
"properties": {
|
||||
"image": {
|
||||
"type": "string",
|
||||
"description": "The Docker image to use as the container. The value can be the Docker Hub image or a registry name."
|
||||
"type": "non-empty-string",
|
||||
"description": "Use `jobs.<job_id>.container.image` to define the Docker image to use as the container to run the action. The value can be the Docker Hub image or a registry name."
|
||||
},
|
||||
"options": {
|
||||
"type": "string",
|
||||
"description": "Additional Docker container resource options."
|
||||
"description": "Use `jobs.<job_id>.container.options` to configure additional Docker container resource options."
|
||||
},
|
||||
"env": "container-env",
|
||||
"ports": {
|
||||
"type": "sequence-of-non-empty-string",
|
||||
"description": "An array of ports to expose on the container."
|
||||
"description": "Use `jobs.<job_id>.container.ports` to set an array of ports to expose on the container."
|
||||
},
|
||||
"volumes": {
|
||||
"type": "sequence-of-non-empty-string",
|
||||
"description": "An array of volumes for the container to use. You can use volumes to share data between services or other steps in a job. You can specify named Docker volumes, anonymous Docker volumes, or bind mounts on the host."
|
||||
},
|
||||
"credentials": "container-registry-credentials"
|
||||
}
|
||||
}
|
||||
},
|
||||
"service-container-mapping": {
|
||||
"mapping": {
|
||||
"properties": {
|
||||
"image": {
|
||||
"type": "string",
|
||||
"description": "The Docker image to use as the container. The value can be the Docker Hub image or a registry name."
|
||||
},
|
||||
"options": {
|
||||
"type": "string",
|
||||
"description": "Additional Docker container resource options."
|
||||
},
|
||||
"entrypoint": {
|
||||
"type": "string",
|
||||
"description": "Override the default ENTRYPOINT in the service container image."
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "Override the default CMD in the service container image."
|
||||
},
|
||||
"env": "container-env",
|
||||
"ports": {
|
||||
"type": "sequence-of-non-empty-string",
|
||||
"description": "An array of ports to expose on the container."
|
||||
},
|
||||
"volumes": {
|
||||
"type": "sequence-of-non-empty-string",
|
||||
"description": "An array of volumes for the container to use. You can use volumes to share data between services or other steps in a job. You can specify named Docker volumes, anonymous Docker volumes, or bind mounts on the host."
|
||||
"description": "Use `jobs.<job_id>.container.volumes` to set an array of volumes for the container to use. You can use volumes to share data between services or other steps in a job. You can specify named Docker volumes, anonymous Docker volumes, or bind mounts on the host."
|
||||
},
|
||||
"credentials": "container-registry-credentials"
|
||||
}
|
||||
@@ -2666,12 +2634,12 @@
|
||||
"matrix"
|
||||
],
|
||||
"one-of": [
|
||||
"string",
|
||||
"service-container-mapping"
|
||||
"non-empty-string",
|
||||
"container-mapping"
|
||||
]
|
||||
},
|
||||
"container-registry-credentials": {
|
||||
"description": "If the container registry requires authentication to pull the image, set a map of the username and password. The credentials are the same values that you would provide to the `docker login` command.",
|
||||
"description": "If the image's container registry requires authentication to pull the image, you can use `jobs.<job_id>.container.credentials` to set a map of the username and password. The credentials are the same values that you would provide to the `docker login` command.",
|
||||
"context": [
|
||||
"github",
|
||||
"inputs",
|
||||
@@ -2687,7 +2655,7 @@
|
||||
}
|
||||
},
|
||||
"container-env": {
|
||||
"description": "A map of environment variables to set in the container.",
|
||||
"description": "Use `jobs.<job_id>.container.env` to set a map of variables in the container.",
|
||||
"mapping": {
|
||||
"loose-key-type": "non-empty-string",
|
||||
"loose-value-type": "string-runner-context"
|
||||
|
||||
@@ -228,8 +228,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new TaskAgent()));
|
||||
|
||||
|
||||
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||
Assert.False(result);
|
||||
var ex = await Assert.ThrowsAsync<TaskCanceledException>(() => updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
||||
Assert.Contains($"failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts", ex.Message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -281,8 +281,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
.Returns(Task.FromResult(new TaskAgent()));
|
||||
|
||||
|
||||
var result = await updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||
Assert.False(result);
|
||||
var ex = await Assert.ThrowsAsync<Exception>(() => updater.SelfUpdate(_refreshMessage, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
||||
Assert.Contains("did not match expected Runner Hash", ex.Message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -170,8 +170,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
DownloadUrl = "https://github.com/actions/runner/notexists"
|
||||
};
|
||||
|
||||
var result = await updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||
Assert.False(result);
|
||||
var ex = await Assert.ThrowsAsync<TaskCanceledException>(() => updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
||||
Assert.Contains($"failed after {Constants.RunnerDownloadRetryMaxAttempts} download attempts", ex.Message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -220,8 +220,8 @@ namespace GitHub.Runner.Common.Tests.Listener
|
||||
SHA256Checksum = "badhash"
|
||||
};
|
||||
|
||||
var result = await updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken);
|
||||
Assert.False(result);
|
||||
var ex = await Assert.ThrowsAsync<Exception>(() => updater.SelfUpdate(message, _jobDispatcher.Object, true, hc.RunnerShutdownToken));
|
||||
Assert.Contains("did not match expected Runner Hash", ex.Message);
|
||||
}
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GitHub.DistributedTask.Pipelines;
|
||||
using GitHub.DistributedTask.Pipelines.ContextData;
|
||||
using GitHub.DistributedTask.WebApi;
|
||||
using GitHub.Runner.Common.Util;
|
||||
using GitHub.Runner.Sdk;
|
||||
using GitHub.Runner.Worker;
|
||||
using GitHub.Runner.Worker.Handlers;
|
||||
using Moq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
using DTWebApi = GitHub.DistributedTask.WebApi;
|
||||
using Pipelines = GitHub.DistributedTask.Pipelines;
|
||||
|
||||
namespace GitHub.Runner.Common.Tests.Worker.Handlers
|
||||
{
|
||||
@@ -254,66 +250,6 @@ namespace GitHub.Runner.Common.Tests.Worker.Handlers
|
||||
Assert.Equal("##[end-action id=failing-step;outcome=failure;conclusion=success;duration_ms=500]", marker);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void PostStepMarker_UsesEvaluatedDisplayName()
|
||||
{
|
||||
// Arrange: create an ActionRunner with a RepositoryPathReference (simulating actions/cache@v4)
|
||||
// and Stage = Post. Verify that EvaluateDisplayName produces the correct display name
|
||||
// so the composite marker emits "Run actions/cache@v4" instead of the fallback "run".
|
||||
var hc = new TestHostContext(this, nameof(PostStepMarker_UsesEvaluatedDisplayName));
|
||||
var actionManifestLegacy = new ActionManifestManagerLegacy();
|
||||
actionManifestLegacy.Initialize(hc);
|
||||
hc.SetSingleton<IActionManifestManagerLegacy>(actionManifestLegacy);
|
||||
var actionManifestNew = new ActionManifestManager();
|
||||
actionManifestNew.Initialize(hc);
|
||||
hc.SetSingleton<IActionManifestManager>(actionManifestNew);
|
||||
var actionManifestManager = new ActionManifestManagerWrapper();
|
||||
actionManifestManager.Initialize(hc);
|
||||
hc.SetSingleton<IActionManifestManagerWrapper>(actionManifestManager);
|
||||
|
||||
var ec = new Mock<IExecutionContext>();
|
||||
var contextData = new DictionaryContextData();
|
||||
var githubContext = new GitHubContext();
|
||||
githubContext.Add("event", JToken.Parse("{\"foo\":\"bar\"}").ToPipelineContextData());
|
||||
contextData.Add("github", githubContext);
|
||||
#if OS_WINDOWS
|
||||
contextData["env"] = new DictionaryContextData();
|
||||
#else
|
||||
contextData["env"] = new CaseSensitiveDictionaryContextData();
|
||||
#endif
|
||||
ec.Setup(x => x.Global).Returns(new GlobalContext());
|
||||
ec.Setup(x => x.ExpressionValues).Returns(contextData);
|
||||
ec.Setup(x => x.ExpressionFunctions).Returns(new List<GitHub.DistributedTask.Expressions2.IFunctionInfo>());
|
||||
ec.Setup(x => x.Write(It.IsAny<string>(), It.IsAny<string>()));
|
||||
ec.Object.Global.Variables = new Variables(hc, new Dictionary<string, VariableValue>());
|
||||
|
||||
var actionRunner = new ActionRunner();
|
||||
actionRunner.Initialize(hc);
|
||||
actionRunner.ExecutionContext = ec.Object;
|
||||
actionRunner.Stage = ActionRunStage.Post;
|
||||
actionRunner.Action = new Pipelines.ActionStep()
|
||||
{
|
||||
Name = "cache",
|
||||
Id = Guid.NewGuid(),
|
||||
Reference = new Pipelines.RepositoryPathReference()
|
||||
{
|
||||
Name = "actions/cache",
|
||||
Ref = "v4"
|
||||
}
|
||||
};
|
||||
|
||||
// Act: call EvaluateDisplayName directly, which is what CompositeActionHandler now does
|
||||
// for embedded steps (including Post stage) instead of TryUpdateDisplayName.
|
||||
var result = actionRunner.EvaluateDisplayName(contextData, ec.Object, out bool updated);
|
||||
|
||||
// Assert: display name should be "Run actions/cache@v4", not the fallback "run"
|
||||
Assert.True(result);
|
||||
Assert.True(updated);
|
||||
Assert.Equal("Run actions/cache@v4", actionRunner.DisplayName);
|
||||
}
|
||||
|
||||
// Helper methods that call the real production code
|
||||
private static string EscapeProperty(string value) =>
|
||||
CompositeActionHandler.EscapeProperty(value);
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
|
||||
var token = new StringToken(null, null, null, "test-value");
|
||||
var contextData = new DictionaryContextData();
|
||||
@@ -63,7 +63,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
|
||||
// Call EvaluateAndCompare directly: the new evaluator cancels the token
|
||||
// and returns a different value, forcing hasMismatch = true.
|
||||
@@ -98,7 +98,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
|
||||
// Different results without cancellation — mismatch SHOULD be recorded.
|
||||
var result = wrapper.EvaluateAndCompare<string, string>(
|
||||
@@ -130,7 +130,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new BooleanToken(null, null, null, true);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
@@ -156,7 +156,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new MappingToken(null, null, null);
|
||||
token.Add(new StringToken(null, null, null, "FOO"), new StringToken(null, null, null, "bar"));
|
||||
var contextData = new DictionaryContextData();
|
||||
@@ -184,7 +184,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new BasicExpressionToken(null, null, null, "true");
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
@@ -211,7 +211,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new MappingToken(null, null, null);
|
||||
token.Add(new StringToken(null, null, null, "input1"), new StringToken(null, null, null, "val1"));
|
||||
var contextData = new DictionaryContextData();
|
||||
@@ -239,7 +239,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new NumberToken(null, null, null, 10);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
@@ -265,7 +265,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new StringToken(null, null, null, "");
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
@@ -281,140 +281,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobContainer_DockerPrefixOnly_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var token = new StringToken(null, null, null, "docker://");
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobContainer(token, contextData, functions);
|
||||
|
||||
Assert.Null(result);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobContainer_DockerPrefixOnlyMapping_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var token = new MappingToken(null, null, null);
|
||||
token.Add(new StringToken(null, null, null, "image"), new StringToken(null, null, null, "docker://"));
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobContainer(token, contextData, functions);
|
||||
|
||||
Assert.Null(result);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobContainer_EmptyImageMapping_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var token = new MappingToken(null, null, null);
|
||||
token.Add(new StringToken(null, null, null, "image"), new StringToken(null, null, null, ""));
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobContainer(token, contextData, functions);
|
||||
|
||||
Assert.Null(result);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobContainer_ValidImage_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var token = new StringToken(null, null, null, "ubuntu:latest");
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobContainer(token, contextData, functions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("ubuntu:latest", result.Image);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobContainer_DockerPrefixWithImage_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var token = new StringToken(null, null, null, "docker://ubuntu:latest");
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobContainer(token, contextData, functions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("ubuntu:latest", result.Image);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -425,7 +291,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new MappingToken(null, null, null);
|
||||
token.Add(new StringToken(null, null, null, "out1"), new StringToken(null, null, null, "val1"));
|
||||
var contextData = new DictionaryContextData();
|
||||
@@ -453,7 +319,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new StringToken(null, null, null, "https://example.com");
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
@@ -482,7 +348,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var token = new MappingToken(null, null, null);
|
||||
token.Add(new StringToken(null, null, null, "shell"), new StringToken(null, null, null, "bash"));
|
||||
var contextData = new DictionaryContextData();
|
||||
@@ -510,7 +376,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
@@ -525,213 +391,6 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobServiceContainers_EmptyImage_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
// Build a services mapping token with one service whose image is empty string
|
||||
// Similar to: services: { db: { image: '' } }
|
||||
var servicesMapping = new MappingToken(null, null, null);
|
||||
var serviceMapping = new MappingToken(null, null, null);
|
||||
serviceMapping.Add(new StringToken(null, null, null, "image"), new StringToken(null, null, null, ""));
|
||||
servicesMapping.Add(new StringToken(null, null, null, "db"), serviceMapping);
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobServiceContainers(servicesMapping, contextData, functions);
|
||||
|
||||
// Should get a list with one entry where the container is null (empty image = no container)
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result);
|
||||
Assert.Equal("db", result[0].Key);
|
||||
Assert.Null(result[0].Value);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobServiceContainers_DockerPrefixOnlyImage_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var servicesMapping = new MappingToken(null, null, null);
|
||||
var serviceMapping = new MappingToken(null, null, null);
|
||||
serviceMapping.Add(new StringToken(null, null, null, "image"), new StringToken(null, null, null, "docker://"));
|
||||
servicesMapping.Add(new StringToken(null, null, null, "db"), serviceMapping);
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobServiceContainers(servicesMapping, contextData, functions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result);
|
||||
Assert.Equal("db", result[0].Key);
|
||||
Assert.Null(result[0].Value);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobServiceContainers_ExpressionEvalsToEmpty_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
// Simulates: services: { db: { image: ${{ condition && 'img' || '' }} } }
|
||||
// where the expression evaluates to '' at runtime
|
||||
var servicesMapping = new MappingToken(null, null, null);
|
||||
var serviceMapping = new MappingToken(null, null, null);
|
||||
serviceMapping.Add(new StringToken(null, null, null, "image"), new BasicExpressionToken(null, null, null, "''"));
|
||||
servicesMapping.Add(new StringToken(null, null, null, "db"), serviceMapping);
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobServiceContainers(servicesMapping, contextData, functions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result);
|
||||
Assert.Equal("db", result[0].Key);
|
||||
Assert.Null(result[0].Value);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobServiceContainers_ValidImage_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var servicesMapping = new MappingToken(null, null, null);
|
||||
var serviceMapping = new MappingToken(null, null, null);
|
||||
serviceMapping.Add(new StringToken(null, null, null, "image"), new StringToken(null, null, null, "postgres:latest"));
|
||||
servicesMapping.Add(new StringToken(null, null, null, "db"), serviceMapping);
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobServiceContainers(servicesMapping, contextData, functions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result);
|
||||
Assert.Equal("db", result[0].Key);
|
||||
Assert.NotNull(result[0].Value);
|
||||
Assert.Equal("postgres:latest", result[0].Value.Image);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobServiceContainers_EntrypointAndCommand_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var servicesMapping = new MappingToken(null, null, null);
|
||||
var serviceMapping = new MappingToken(null, null, null);
|
||||
serviceMapping.Add(new StringToken(null, null, null, "image"), new StringToken(null, null, null, "postgres:latest"));
|
||||
serviceMapping.Add(new StringToken(null, null, null, "entrypoint"), new StringToken(null, null, null, "/bin/bash"));
|
||||
serviceMapping.Add(new StringToken(null, null, null, "command"), new StringToken(null, null, null, "-lc echo hi"));
|
||||
servicesMapping.Add(new StringToken(null, null, null, "db"), serviceMapping);
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: true);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
var result = wrapper.EvaluateJobServiceContainers(servicesMapping, contextData, functions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result);
|
||||
Assert.Equal("db", result[0].Key);
|
||||
Assert.NotNull(result[0].Value);
|
||||
Assert.Equal("postgres:latest", result[0].Value.Image);
|
||||
Assert.Equal("/bin/bash", result[0].Value.Entrypoint);
|
||||
Assert.Equal("-lc echo hi", result[0].Value.Command);
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
public void EvaluateJobServiceContainers_EntrypointAndCommand_FlagOff_BothParsersAgree()
|
||||
{
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var servicesMapping = new MappingToken(null, null, null);
|
||||
var serviceMapping = new MappingToken(null, null, null);
|
||||
serviceMapping.Add(new StringToken(null, null, null, "image"), new StringToken(null, null, null, "postgres:latest"));
|
||||
serviceMapping.Add(new StringToken(null, null, null, "entrypoint"), new StringToken(null, null, null, "/bin/bash"));
|
||||
serviceMapping.Add(new StringToken(null, null, null, "command"), new StringToken(null, null, null, "-lc echo hi"));
|
||||
servicesMapping.Add(new StringToken(null, null, null, "db"), serviceMapping);
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
Assert.Throws<GitHub.DistributedTask.ObjectTemplating.TemplateValidationException>(() =>
|
||||
wrapper.EvaluateJobServiceContainers(servicesMapping, contextData, functions));
|
||||
Assert.False(_ec.Object.Global.HasTemplateEvaluatorMismatch);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Level", "L0")]
|
||||
[Trait("Category", "Worker")]
|
||||
@@ -742,7 +401,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
var contextData = new DictionaryContextData();
|
||||
var functions = new List<LegacyExpressions.IFunctionInfo>();
|
||||
|
||||
@@ -771,7 +430,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
|
||||
// Both throw JsonReaderException with different messages — should be treated as equivalent
|
||||
var legacyEx = new Newtonsoft.Json.JsonReaderException("Error reading JToken from JsonReader. Path '', line 0, position 0.");
|
||||
@@ -802,7 +461,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
|
||||
// Legacy throws Newtonsoft JsonReaderException, new throws System.Text.Json.JsonException
|
||||
var legacyEx = new Newtonsoft.Json.JsonReaderException("Error reading JToken");
|
||||
@@ -833,7 +492,7 @@ namespace GitHub.Runner.Common.Tests.Worker
|
||||
Setup();
|
||||
_ec.Object.Global.Variables.Set(Constants.Runner.Features.CompareWorkflowParser, "true");
|
||||
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object, allowServiceContainerCommand: false);
|
||||
var wrapper = new PipelineTemplateEvaluatorWrapper(_hc, _ec.Object);
|
||||
|
||||
// Both throw non-JSON exceptions with different messages — should record mismatch
|
||||
var legacyEx = new InvalidOperationException("some error");
|
||||
|
||||
Reference in New Issue
Block a user