diff --git a/src/Agent.Sdk/Knob/AgentKnobs.cs b/src/Agent.Sdk/Knob/AgentKnobs.cs index 250a9db015..6517bf1540 100644 --- a/src/Agent.Sdk/Knob/AgentKnobs.cs +++ b/src/Agent.Sdk/Knob/AgentKnobs.cs @@ -152,6 +152,13 @@ public class AgentKnobs new EnvironmentKnobSource("AGENT_USE_NODE16"), new BuiltInDefaultKnobSource("false")); + public static readonly Knob UseNode20 = new Knob( + nameof(UseNode20), + "Forces the agent to use Node 20 handler for all Node-based tasks", + new RuntimeKnobSource("AGENT_USE_NODE20"), + new EnvironmentKnobSource("AGENT_USE_NODE20"), + new BuiltInDefaultKnobSource("false")); + // Agent logging public static readonly Knob AgentPerflog = new Knob( nameof(AgentPerflog), diff --git a/src/Agent.Worker/Handlers/NodeHandler.cs b/src/Agent.Worker/Handlers/NodeHandler.cs index 08584f33eb..5545227913 100644 --- a/src/Agent.Worker/Handlers/NodeHandler.cs +++ b/src/Agent.Worker/Handlers/NodeHandler.cs @@ -18,7 +18,7 @@ namespace Microsoft.VisualStudio.Services.Agent.Worker.Handlers [ServiceLocator(Default = typeof(NodeHandler))] public interface INodeHandler : IHandler { - // Data can be of these three types: NodeHandlerData, Node10HandlerData and Node16HandlerData + // Data can be of these four types: NodeHandlerData, Node10HandlerData, Node16HandlerData and Node20HandlerData BaseNodeHandlerData Data { get; set; } } @@ -56,10 +56,11 @@ public sealed class NodeHandler : Handler, INodeHandler private const string nodeFolder = "node"; private const string node10Folder = "node10"; private const string node16Folder = "node16"; + private const string node20Folder = "node20"; private const string nodeLTS = node16Folder; private const string useNodeKnobLtsKey = "LTS"; private const string useNodeKnobUpgradeKey = "UPGRADE"; - private string[] possibleNodeFolders = { nodeFolder, node10Folder, node16Folder }; + private string[] possibleNodeFolders = { nodeFolder, node10Folder, node16Folder, node20Folder }; private static Regex _vstsTaskLibVersionNeedsFix = new Regex("^[0-2]\\.[0-9]+", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static string[] _extensionsNode6 ={ "if (process.versions.node && process.versions.node.match(/^5\\./)) {", @@ -215,10 +216,12 @@ public async Task RunAsync() public string GetNodeLocation() { - bool useNode16 = AgentKnobs.UseNode16.GetValue(ExecutionContext).AsBoolean(); bool useNode10 = AgentKnobs.UseNode10.GetValue(ExecutionContext).AsBoolean(); + bool useNode16 = AgentKnobs.UseNode16.GetValue(ExecutionContext).AsBoolean(); + bool useNode20 = AgentKnobs.UseNode20.GetValue(ExecutionContext).AsBoolean(); bool taskHasNode10Data = Data is Node10HandlerData; bool taskHasNode16Data = Data is Node16HandlerData; + bool taskHasNode20Data = Data is Node20HandlerData; string useNodeKnob = AgentKnobs.UseNode.GetValue(ExecutionContext).AsString(); string nodeFolder = NodeHandler.nodeFolder; @@ -227,6 +230,11 @@ public string GetNodeLocation() Trace.Info($"Detected RedHat 6, using node 10 as execution handler, instead node16"); nodeFolder = NodeHandler.node10Folder; } + else if (taskHasNode20Data) + { + Trace.Info($"Task.json has node20 handler data: {taskHasNode20Data}"); + nodeFolder = NodeHandler.node20Folder; + } else if (taskHasNode16Data) { Trace.Info($"Task.json has node16 handler data: {taskHasNode16Data}"); @@ -238,6 +246,12 @@ public string GetNodeLocation() nodeFolder = NodeHandler.node10Folder; } + if (useNode20) + { + Trace.Info($"Found UseNode20 knob, using node20 for node tasks {useNode20}"); + nodeFolder = NodeHandler.node20Folder; + } + if (useNode16) { Trace.Info($"Found UseNode16 knob, using node16 for node tasks {useNode16}"); diff --git a/src/Agent.Worker/TaskManager.cs b/src/Agent.Worker/TaskManager.cs index c8739a9a35..cb1a966587 100644 --- a/src/Agent.Worker/TaskManager.cs +++ b/src/Agent.Worker/TaskManager.cs @@ -381,6 +381,7 @@ public sealed class ExecutionData private NodeHandlerData _node; private Node10HandlerData _node10; private Node16HandlerData _node16; + private Node20HandlerData _node20; private PowerShellHandlerData _powerShell; private PowerShell3HandlerData _powerShell3; private PowerShellExeHandlerData _powerShellExe; @@ -450,6 +451,20 @@ public Node16HandlerData Node16 } } + public Node20HandlerData Node20 + { + get + { + return _node20; + } + + set + { + _node20 = value; + Add(value); + } + } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public PowerShellHandlerData PowerShell { @@ -624,21 +639,25 @@ public string WorkingDirectory public sealed class NodeHandlerData : BaseNodeHandlerData { - public override int Priority => 3; + public override int Priority => 4; } public sealed class Node10HandlerData : BaseNodeHandlerData { - public override int Priority => 2; + public override int Priority => 3; } public sealed class Node16HandlerData : BaseNodeHandlerData + { + public override int Priority => 2; + } + public sealed class Node20HandlerData : BaseNodeHandlerData { public override int Priority => 1; } public sealed class PowerShell3HandlerData : HandlerData { - public override int Priority => 4; + public override int Priority => 5; } public sealed class PowerShellHandlerData : HandlerData @@ -656,7 +675,7 @@ public string ArgumentFormat } } - public override int Priority => 5; + public override int Priority => 6; public string WorkingDirectory { @@ -687,7 +706,7 @@ public string ArgumentFormat } } - public override int Priority => 6; + public override int Priority => 7; public string WorkingDirectory { @@ -744,7 +763,7 @@ public string InlineScript } } - public override int Priority => 6; + public override int Priority => 7; public string ScriptType { @@ -801,7 +820,7 @@ public string ModifyEnvironment } } - public override int Priority => 7; + public override int Priority => 8; public string WorkingDirectory { diff --git a/src/Misc/externals.sh b/src/Misc/externals.sh index daa381f82a..087576401f 100644 --- a/src/Misc/externals.sh +++ b/src/Misc/externals.sh @@ -11,6 +11,7 @@ NODE_URL=https://nodejs.org/dist NODE_VERSION="6.17.1" NODE10_VERSION="10.24.1" NODE16_VERSION="16.20.0" +NODE20_VERSION="20.3.1" MINGIT_VERSION="2.39.1" LFS_VERSION="3.3.0" @@ -160,6 +161,8 @@ if [[ "$PACKAGERUNTIME" == "win-x64" ]]; then acquireExternalTool "$NODE_URL/v${NODE10_VERSION}/win-x64/node.lib" node10/bin acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/win-x64/node.exe" node16/bin acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/win-x64/node.lib" node16/bin + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/win-x64/node.exe" node20/bin + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/win-x64/node.lib" node20/bin acquireExternalTool "https://dist.nuget.org/win-x86-commandline/v3.4.4/nuget.exe" nuget fi @@ -178,6 +181,8 @@ if [[ "$PACKAGERUNTIME" == "win-x86" ]]; then acquireExternalTool "$NODE_URL/v${NODE10_VERSION}/win-x86/node.lib" node10/bin acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/win-x86/node.exe" node16/bin acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/win-x86/node.lib" node16/bin + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/win-x86/node.exe" node20/bin + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/win-x86/node.lib" node20/bin acquireExternalTool "https://dist.nuget.org/win-x86-commandline/v3.4.4/nuget.exe" nuget fi @@ -188,6 +193,7 @@ if [[ "$PACKAGERUNTIME" == "osx-x64" ]]; then fi acquireExternalTool "$NODE_URL/v${NODE10_VERSION}/node-v${NODE10_VERSION}-darwin-x64.tar.gz" node10 fix_nested_dir acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-x64.tar.gz" node16 fix_nested_dir + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-x64.tar.gz" node20 fix_nested_dir fi @@ -197,6 +203,7 @@ if [[ "$PACKAGERUNTIME" == "osx-arm64" ]]; then fi acquireExternalTool "$NODE_URL/v${NODE10_VERSION}/node-v${NODE10_VERSION}-darwin-x64.tar.gz" node10 fix_nested_dir acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-darwin-arm64.tar.gz" node16 fix_nested_dir + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-darwin-arm64.tar.gz" node20 fix_nested_dir fi # Download the external tools common across OSX and Linux PACKAGERUNTIMEs. @@ -211,6 +218,7 @@ if [[ "$PACKAGERUNTIME" == "linux-x64" || "$PACKAGERUNTIME" == "rhel.7.2-x64" ]] fi acquireExternalTool "$NODE_URL/v${NODE10_VERSION}/node-v${NODE10_VERSION}-linux-x64.tar.gz" node10 fix_nested_dir acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-x64.tar.gz" node16 fix_nested_dir + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-x64.tar.gz" node20 fix_nested_dir fi if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then @@ -219,6 +227,7 @@ if [[ "$PACKAGERUNTIME" == "linux-arm" ]]; then fi acquireExternalTool "$NODE_URL/v${NODE10_VERSION}/node-v${NODE10_VERSION}-linux-armv7l.tar.gz" node10 fix_nested_dir acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-armv7l.tar.gz" node16 fix_nested_dir + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-armv7l.tar.gz" node20 fix_nested_dir fi if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then @@ -227,6 +236,7 @@ if [[ "$PACKAGERUNTIME" == "linux-arm64" ]]; then fi acquireExternalTool "$NODE_URL/v${NODE10_VERSION}/node-v${NODE10_VERSION}-linux-arm64.tar.gz" node10 fix_nested_dir acquireExternalTool "$NODE_URL/v${NODE16_VERSION}/node-v${NODE16_VERSION}-linux-arm64.tar.gz" node16 fix_nested_dir + acquireExternalTool "$NODE_URL/v${NODE20_VERSION}/node-v${NODE20_VERSION}-linux-arm64.tar.gz" node20 fix_nested_dir fi if [[ "$PACKAGERUNTIME" != "win-x64" && "$PACKAGERUNTIME" != "win-x86" ]]; then @@ -244,6 +254,11 @@ if [[ "$PACKAGERUNTIME" != "win-x64" && "$PACKAGERUNTIME" != "win-x86" ]]; then rm "$LAYOUT_DIR/externals/node16/bin/npm" rm "$LAYOUT_DIR/externals/node16/bin/npx" rm "$LAYOUT_DIR/externals/node16/bin/corepack" + + rm -rf "$LAYOUT_DIR/externals/node20/lib" + rm "$LAYOUT_DIR/externals/node20/bin/npm" + rm "$LAYOUT_DIR/externals/node20/bin/npx" + rm "$LAYOUT_DIR/externals/node20/bin/corepack" fi if [[ "$PACKAGERUNTIME" != "win-x64" && "$PACKAGERUNTIME" != "win-x86" ]]; then diff --git a/src/Test/L0/NodeHandlerL0.cs b/src/Test/L0/NodeHandlerL0.cs index 6b2243c22d..af90beee91 100644 --- a/src/Test/L0/NodeHandlerL0.cs +++ b/src/Test/L0/NodeHandlerL0.cs @@ -56,6 +56,7 @@ public void UseNodeForNodeHandlerEnvVarNotSet() [Theory] [InlineData("node10")] [InlineData("node16")] + [InlineData("node20")] [Trait("Level", "L0")] [Trait("Category", "Common")] public void UseNewNodeForNewNodeHandler(string nodeVersion) @@ -69,7 +70,13 @@ public void UseNewNodeForNewNodeHandler(string nodeVersion) nodeHandler.Initialize(thc); nodeHandler.ExecutionContext = CreateTestExecutionContext(thc); - nodeHandler.Data = nodeVersion == "node16" ? (BaseNodeHandlerData)new Node16HandlerData() : (BaseNodeHandlerData)new Node10HandlerData(); + nodeHandler.Data = nodeVersion switch + { + "node10" => new Node10HandlerData(), + "node16" => new Node16HandlerData(), + "node20" => new Node20HandlerData(), + _ => throw new Exception("Invalid node version"), + }; string actualLocation = nodeHandler.GetNodeLocation(); // We should fall back to node10 for node16 tasks, since RHEL 6 is not capable with Node16. diff --git a/src/Test/L0/Worker/TaskManagerL0.cs b/src/Test/L0/Worker/TaskManagerL0.cs index 3cf8c75cd6..ed4d57874b 100644 --- a/src/Test/L0/Worker/TaskManagerL0.cs +++ b/src/Test/L0/Worker/TaskManagerL0.cs @@ -458,6 +458,10 @@ public void LoadsDefinition() ""target"": ""Some Node16 target"", ""extraNodeArg"": ""Extra node16 arg value"" }, + ""Node20"": { + ""target"": ""Some Node20 target"", + ""extraNodeArg"": ""Extra node20 arg value"" + }, ""Process"": { ""target"": ""Some process target"", ""argumentFormat"": ""Some process argument format"", @@ -509,12 +513,12 @@ public void LoadsDefinition() if (TestUtil.IsWindows()) { // Process handler should only be deserialized on Windows. - Assert.Equal(4, definition.Data.Execution.All.Count); + Assert.Equal(5, definition.Data.Execution.All.Count); } else { // Only the Node handlers should be deserialized on non-Windows. - Assert.Equal(3, definition.Data.Execution.All.Count); + Assert.Equal(4, definition.Data.Execution.All.Count); } // Node handler should always be deserialized. @@ -532,11 +536,16 @@ public void LoadsDefinition() Assert.Equal(definition.Data.Execution.Node16, definition.Data.Execution.All[2]); Assert.Equal("Some Node16 target", definition.Data.Execution.Node16.Target); + // Node20 handler should always be deserialized. + Assert.NotNull(definition.Data.Execution.Node20); // execution.Node20 + Assert.Equal(definition.Data.Execution.Node20, definition.Data.Execution.All[3]); + Assert.Equal("Some Node20 target", definition.Data.Execution.Node20.Target); + if (TestUtil.IsWindows()) { // Process handler should only be deserialized on Windows. Assert.NotNull(definition.Data.Execution.Process); // execution.Process - Assert.Equal(definition.Data.Execution.Process, definition.Data.Execution.All[3]); + Assert.Equal(definition.Data.Execution.Process, definition.Data.Execution.All[4]); Assert.Equal("Some process argument format", definition.Data.Execution.Process.ArgumentFormat); Assert.NotNull(definition.Data.Execution.Process.Platforms); Assert.Equal(1, definition.Data.Execution.Process.Platforms.Length);