Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Runner.Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ public static class Features
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";
Expand Down
2 changes: 2 additions & 0 deletions src/Runner.Worker/Container/ContainerInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public ContainerInfo(IHostContext hostContext, Pipelines.JobContainer 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;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sets existing properties, which DockerCommandManager already uses for docker create

this.ContainerEntryPointArgs = container.Command;
Comment thread
ericsciple marked this conversation as resolved.
_environmentVariables = container.Environment;
this.IsJobContainer = isJobContainer;
this.ContainerNetworkAlias = networkAlias;
Expand Down
10 changes: 7 additions & 3 deletions src/Runner.Worker/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1328,9 +1328,9 @@ public void ApplyContinueOnError(TemplateToken continueOnErrorToken)
UpdateGlobalStepsContext();
}

internal IPipelineTemplateEvaluator ToPipelineTemplateEvaluatorInternal(ObjectTemplating.ITraceWriter traceWriter = null)
internal IPipelineTemplateEvaluator ToPipelineTemplateEvaluatorInternal(bool allowServiceContainerCommand, ObjectTemplating.ITraceWriter traceWriter = null)
{
return new PipelineTemplateEvaluatorWrapper(HostContext, this, traceWriter);
return new PipelineTemplateEvaluatorWrapper(HostContext, this, allowServiceContainerCommand, traceWriter);
}

private static void NoOp()
Expand Down Expand Up @@ -1418,10 +1418,13 @@ public static IEnumerable<KeyValuePair<string, object>> ToExpressionState(this I

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(traceWriter);
return (context as ExecutionContext).ToPipelineTemplateEvaluatorInternal(allowServiceContainerCommand, traceWriter);
}

// Legacy
Expand All @@ -1433,6 +1436,7 @@ public static IPipelineTemplateEvaluator ToPipelineTemplateEvaluator(this IExecu
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,
};
}

Expand Down
18 changes: 17 additions & 1 deletion src/Runner.Worker/PipelineTemplateEvaluatorWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal sealed class PipelineTemplateEvaluatorWrapper : IPipelineTemplateEvalua
public PipelineTemplateEvaluatorWrapper(
IHostContext hostContext,
IExecutionContext context,
bool allowServiceContainerCommand,
ObjectTemplating.ITraceWriter traceWriter = null)
{
ArgUtil.NotNull(hostContext, nameof(hostContext));
Expand All @@ -40,11 +41,14 @@ public PipelineTemplateEvaluatorWrapper(
_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();
_newEvaluator = new WorkflowTemplateEvaluator(newTraceWriter, context.Global.FileTable, features: null)
var features = WorkflowFeatures.GetDefaults();
features.AllowServiceContainerCommand = allowServiceContainerCommand;
_newEvaluator = new WorkflowTemplateEvaluator(newTraceWriter, context.Global.FileTable, features)
{
MaxErrorMessageLength = int.MaxValue, // Don't truncate error messages otherwise we might not scrub secrets correctly
};
Expand Down Expand Up @@ -401,6 +405,18 @@ private bool CompareJobContainer(
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}')");
Comment thread
ericsciple marked this conversation as resolved.
return false;
}

if (!CompareDictionaries(legacyResult.Environment, newResult.Environment, "Environment"))
{
return false;
Expand Down
18 changes: 18 additions & 0 deletions src/Sdk/DTPipelines/Pipelines/JobContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ public String Options
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public sealed class PipelineTemplateConstants
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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ internal static ContainerRegistryCredentials ConvertToContainerCredentials(Templ
internal static JobContainer ConvertToJobContainer(
TemplateContext context,
TemplateToken value,
bool allowExpressions = false)
bool allowExpressions = false,
bool allowServiceContainerCommand = false)
{
var result = new JobContainer();
if (allowExpressions && value.Traverse().Any(x => x is ExpressionToken))
Expand Down Expand Up @@ -280,6 +281,22 @@ internal static JobContainer ConvertToJobContainer(
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);
Expand Down Expand Up @@ -326,7 +343,8 @@ internal static JobContainer ConvertToJobContainer(
internal static List<KeyValuePair<String, JobContainer>> ConvertToJobServiceContainers(
TemplateContext context,
TemplateToken services,
bool allowExpressions = false)
bool allowExpressions = false,
bool allowServiceContainerCommand = false)
{
var result = new List<KeyValuePair<String, JobContainer>>();

Expand All @@ -340,7 +358,7 @@ internal static List<KeyValuePair<String, JobContainer>> ConvertToJobServiceCont
foreach (var servicePair in servicesMapping)
{
var networkAlias = servicePair.Key.AssertString("services key").Value;
var container = ConvertToJobContainer(context, servicePair.Value);
var container = ConvertToJobContainer(context, servicePair.Value, allowExpressions, allowServiceContainerCommand);
result.Add(new KeyValuePair<String, JobContainer>(networkAlias, container));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public PipelineTemplateEvaluator(

public Int32 MaxResultSize { get; set; } = 10 * 1024 * 1024; // 10 mb

public bool AllowServiceContainerCommand { get; set; }

public Boolean EvaluateStepContinueOnError(
TemplateToken token,
DictionaryContextData contextData,
Expand Down Expand Up @@ -357,7 +359,7 @@ public IList<KeyValuePair<String, JobContainer>> EvaluateJobServiceContainers(
{
token = TemplateEvaluator.Evaluate(context, PipelineTemplateConstants.Services, token, 0, null, omitHeader: true);
context.Errors.Check();
result = PipelineTemplateConverter.ConvertToJobServiceContainers(context, token);
result = PipelineTemplateConverter.ConvertToJobServiceContainers(context, token, allowServiceContainerCommand: AllowServiceContainerCommand);
}
catch (Exception ex) when (!(ex is TemplateValidationException))
{
Expand Down
17 changes: 16 additions & 1 deletion src/Sdk/DTPipelines/workflow-v1.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,21 @@
}
},

"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",
Expand All @@ -454,7 +469,7 @@
],
"one-of": [
"string",
"container-mapping"
"service-container-mapping"
]
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ internal static class WorkflowTemplateConstants
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";
Expand Down
16 changes: 16 additions & 0 deletions src/Sdk/WorkflowParser/Conversion/WorkflowTemplateConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,22 @@ internal static JobContainer ConvertToJobContainer(
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);
Expand Down
18 changes: 18 additions & 0 deletions src/Sdk/WorkflowParser/JobContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ public String Options
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>
Expand Down
8 changes: 8 additions & 0 deletions src/Sdk/WorkflowParser/WorkflowFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ public class WorkflowFeatures
[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>
Expand All @@ -60,6 +67,7 @@ public static WorkflowFeatures GetDefaults()
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
};
}

Expand Down
Loading
Loading