diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index 85b10e16523..122ea9ce3ef 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -219,6 +219,7 @@ public static class Actions public static readonly string AllowUnsupportedStopCommandTokens = "ACTIONS_ALLOW_UNSECURE_STOPCOMMAND_TOKENS"; public static readonly string RunnerDebug = "ACTIONS_RUNNER_DEBUG"; public static readonly string StepDebug = "ACTIONS_STEP_DEBUG"; + public static readonly string AllowActionsUseUnsecureNodeVersion = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION"; } public static class Agent diff --git a/src/Runner.Worker/Handlers/HandlerFactory.cs b/src/Runner.Worker/Handlers/HandlerFactory.cs index 2b3b9814200..7636902b23d 100644 --- a/src/Runner.Worker/Handlers/HandlerFactory.cs +++ b/src/Runner.Worker/Handlers/HandlerFactory.cs @@ -55,7 +55,23 @@ public IHandler Create( else if (data.ExecutionType == ActionExecutionType.NodeJS) { handler = HostContext.CreateService(); - (handler as INodeScriptActionHandler).Data = data as NodeJSActionExecutionData; + var nodeData = data as NodeJSActionExecutionData; + + // With node12 EoL in 04/2022, we want to be able to uniformly upgrade all JS actions to node16 from the server + if (string.Equals(nodeData.NodeVersion, "node12", StringComparison.InvariantCultureIgnoreCase) && + (executionContext.Global.Variables.GetBoolean("DistributedTask.ForceGithubJavascriptActionsToNode16") ?? false)) + { + // The user can opt out of this behaviour by setting this variable to true, either setting 'env' in their workflow or as an environment variable on their machine + executionContext.Global.EnvironmentVariables.TryGetValue(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, out var workflowOptOut); + var isWorkflowOptOutSet = !string.IsNullOrEmpty(workflowOptOut); + var isLocalOptOut = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion)); + bool isOptOut = isWorkflowOptOutSet ? StringUtil.ConvertToBoolean(workflowOptOut) : isLocalOptOut; + if (!isOptOut) + { + nodeData.NodeVersion = "node16"; + } + } + (handler as INodeScriptActionHandler).Data = nodeData; } else if (data.ExecutionType == ActionExecutionType.Script) { diff --git a/src/Test/L0/Worker/HandlerFactoryL0.cs b/src/Test/L0/Worker/HandlerFactoryL0.cs new file mode 100644 index 00000000000..f046d9803c8 --- /dev/null +++ b/src/Test/L0/Worker/HandlerFactoryL0.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Moq; +using Xunit; +using GitHub.Runner.Worker; +using GitHub.Runner.Worker.Handlers; +using GitHub.DistributedTask.Pipelines; +using GitHub.DistributedTask.WebApi; + +namespace GitHub.Runner.Common.Tests.Worker +{ + public sealed class HandlerFactoryL0 + { + private Mock _ec; + private TestHostContext CreateTestContext([CallerMemberName] string testName = "") + { + var hostContext = new TestHostContext(this, testName); + _ec = new Mock(); + _ec.SetupAllProperties(); + _ec.Object.Initialize(hostContext); + var handler = new Mock(); + handler.SetupAllProperties(); + hostContext.EnqueueInstance(handler.Object); + //hostContext.EnqueueInstance(new ActionCommandManager() as IActionCommandManager); + + return hostContext; + } + + [Theory] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + [InlineData("node12", "", "", "", "node12")] + [InlineData("node12", "true", "", "", "node16")] + [InlineData("node12", "true", "", "true", "node12")] + [InlineData("node12", "true", "true", "", "node12")] + [InlineData("node12", "true", "true", "true", "node12")] + [InlineData("node12", "true", "false", "true", "node16")] // workflow overrides env + [InlineData("node16", "", "", "", "node16")] + [InlineData("node16", "true", "", "", "node16")] + [InlineData("node16", "true", "", "true", "node16")] + [InlineData("node16", "true", "true", "", "node16")] + [InlineData("node16", "true", "true", "true", "node16")] + [InlineData("node16", "true", "false", "true", "node16")] + public void IsNodeVersionUpgraded(string inputVersion, string serverFeatureFlag, string workflowOptOut, string machineOptOut, string expectedVersion) + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var hf = new HandlerFactory(); + hf.Initialize(hc); + + // Server Feature Flag + var variables = new Dictionary(); + if (!string.IsNullOrEmpty(serverFeatureFlag)) + { + variables["DistributedTask.ForceGithubJavascriptActionsToNode16"] = serverFeatureFlag; + } + Variables serverVariables = new Variables(hc, variables); + + // Workflow opt-out + var workflowVariables = new Dictionary(); + if (!string.IsNullOrEmpty(workflowOptOut)) + { + workflowVariables[Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion] = workflowOptOut; + } + + // Machine opt-out + if (!string.IsNullOrEmpty(machineOptOut)) + { + Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, machineOptOut); + } + + _ec.Setup(x => x.Global).Returns(new GlobalContext() + { + Variables = serverVariables, + EnvironmentVariables = workflowVariables + }); + + + // Act. + var data = new NodeJSActionExecutionData(); + data.NodeVersion = inputVersion; + var handler = hf.Create( + _ec.Object, + new ScriptReference(), + new Mock().Object, + data, + new Dictionary(), + new Dictionary(), + new Variables(hc, new Dictionary()), "", new List() + ) as INodeScriptActionHandler; + + // Assert. + Assert.Equal(expectedVersion, handler.Data.NodeVersion); + Environment.SetEnvironmentVariable(Constants.Variables.Actions.AllowActionsUseUnsecureNodeVersion, null); + } + } + } +}