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
8 changes: 8 additions & 0 deletions src/Agent.Sdk/Knob/AgentKnobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,14 @@ public class AgentKnobs
new EnvironmentKnobSource("AGENT_USE_NODE24_IN_UNSUPPORTED_SYSTEM"),
new BuiltInDefaultKnobSource("false"));

public static readonly Knob UseNode24withHandlerData = new Knob(
nameof(UseNode24withHandlerData),
"Forces the agent to use Node 24 handler if the task has handler data for it",
new PipelineFeatureSource("UseNode24withHandlerData"),
new RuntimeKnobSource("AGENT_USE_NODE24_WITH_HANDLER_DATA"),
new EnvironmentKnobSource("AGENT_USE_NODE24_WITH_HANDLER_DATA"),
new BuiltInDefaultKnobSource("false"));

public static readonly Knob FetchByCommitForFullClone = new Knob(
nameof(FetchByCommitForFullClone),
"If true, allow fetch by commit when doing a full clone (depth=0).",
Expand Down
35 changes: 24 additions & 11 deletions src/Agent.Worker/Handlers/NodeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ public string[] GetFilteredPossibleNodeFolders(string nodeFolderName, string[] p
public sealed class NodeHandler : Handler, INodeHandler
{
private readonly INodeHandlerHelper nodeHandlerHelper;
private const string node10Folder = "node10";
private const string Node10Folder = "node10";
internal const string NodeFolder = "node";
internal static readonly string Node16Folder = "node16";
internal static readonly string Node20_1Folder = "node20_1";
internal static readonly string Node24Folder = "node24";
private static readonly string nodeLTS = Node16Folder;
private const string useNodeKnobLtsKey = "LTS";
private const string useNodeKnobUpgradeKey = "UPGRADE";
private string[] possibleNodeFolders = { NodeFolder, node10Folder, Node16Folder, Node20_1Folder, Node24Folder };
private string[] possibleNodeFolders = { NodeFolder, Node10Folder, Node16Folder, Node20_1Folder, Node24Folder };
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\\./)) {",
Expand Down Expand Up @@ -368,14 +368,17 @@ public string GetNodeLocation(bool node20ResultsInGlibCError, bool node24Results
bool useNode20_1 = AgentKnobs.UseNode20_1.GetValue(ExecutionContext).AsBoolean();
bool UseNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(ExecutionContext).AsBoolean();
bool useNode24 = AgentKnobs.UseNode24.GetValue(ExecutionContext).AsBoolean();
bool UseNode24withHandlerData = AgentKnobs.UseNode24withHandlerData.GetValue(ExecutionContext).AsBoolean();
bool taskHasNode6Data = Data is NodeHandlerData;
bool taskHasNode10Data = Data is Node10HandlerData;
bool taskHasNode16Data = Data is Node16HandlerData;
bool taskHasNode20_1Data = Data is Node20_1HandlerData;
bool taskHasNode24Data = Data is Node24HandlerData;
string useNodeKnob = AgentKnobs.UseNode.GetValue(ExecutionContext).AsString();

string nodeFolder = NodeHandler.NodeFolder;
if (taskHasNode24Data && useNode24)
//using Node20_1 as default node version
string nodeFolder = NodeHandler.Node20_1Folder;
if (taskHasNode24Data && UseNode24withHandlerData)
{
Trace.Info($"Task.json has node24 handler data: {taskHasNode24Data}");
nodeFolder = GetNodeFolderWithFallback(NodeHandler.Node24Folder, node20ResultsInGlibCError, node24ResultsInGlibCError, inContainer);
Expand All @@ -393,27 +396,37 @@ public string GetNodeLocation(bool node20ResultsInGlibCError, bool node24Results
else if (taskHasNode10Data)
{
Trace.Info($"Task.json has node10 handler data: {taskHasNode10Data}");
nodeFolder = NodeHandler.node10Folder;
nodeFolder = NodeHandler.Node10Folder;
}
else if (taskHasNode6Data)
{
Trace.Info($"Task.json has node6 handler data: {taskHasNode6Data}");
nodeFolder = NodeHandler.NodeFolder;
}
else if (PlatformUtil.RunningOnAlpine)
{
Trace.Info($"Detected Alpine, using node10 instead of node (6)");
nodeFolder = NodeHandler.node10Folder;
nodeFolder = NodeHandler.Node10Folder;
}

if (useNode20_1)
if (useNode24)
{
Trace.Info($"Found UseNode24 knob, using node24 for node tasks: {useNode24}");
nodeFolder = GetNodeFolderWithFallback(NodeHandler.Node24Folder, node20ResultsInGlibCError, node24ResultsInGlibCError, inContainer);
}
else if (useNode20_1)
{
Trace.Info($"Found UseNode20_1 knob, using node20_1 for node tasks {useNode20_1} node20ResultsInGlibCError = {node20ResultsInGlibCError}");
nodeFolder = GetNodeFolderWithFallback(NodeHandler.Node20_1Folder, node20ResultsInGlibCError, node24ResultsInGlibCError, inContainer);
}

else if (useNode10)
{
Trace.Info($"Found UseNode10 knob, use node10 for node tasks: {useNode10}");
nodeFolder = NodeHandler.node10Folder;
nodeFolder = NodeHandler.Node10Folder;
}
if (nodeFolder == NodeHandler.NodeFolder &&
AgentKnobs.AgentDeprecatedNodeWarnings.GetValue(ExecutionContext).AsBoolean() == true)
// Warn for deprecated node versions
if ((nodeFolder == NodeHandler.NodeFolder || nodeFolder == NodeHandler.Node10Folder || nodeFolder == NodeHandler.Node16Folder) &&
AgentKnobs.AgentDeprecatedNodeWarnings.GetValue(ExecutionContext).AsBoolean())
{
ExecutionContext.Warning(StringUtil.Loc("DeprecatedRunner", Task.Name.ToString()));
}
Expand Down
97 changes: 95 additions & 2 deletions src/Test/L0/NodeHandlerL0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public void UseNodeForNodeHandlerEnvVarNotSet()
}

[Theory]
[InlineData("node")]
[InlineData("node10")]
[InlineData("node16")]
[InlineData("node20_1")]
Expand All @@ -74,7 +75,7 @@ public void UseNewNodeForNewNodeHandler(string nodeVersion)
// For node24, set the required knob
if (nodeVersion == "node24")
{
Environment.SetEnvironmentVariable("AGENT_USE_NODE24", "true");
Environment.SetEnvironmentVariable("AGENT_USE_NODE24_WITH_HANDLER_DATA", "true");
}

try
Expand All @@ -91,6 +92,7 @@ public void UseNewNodeForNewNodeHandler(string nodeVersion)
nodeHandler.ExecutionContext = CreateTestExecutionContext(thc);
nodeHandler.Data = nodeVersion switch
{
"node" => new NodeHandlerData(),
"node10" => new Node10HandlerData(),
"node16" => new Node16HandlerData(),
"node20_1" => new Node20_1HandlerData(),
Expand All @@ -110,11 +112,101 @@ public void UseNewNodeForNewNodeHandler(string nodeVersion)
{
if (nodeVersion == "node24")
{
Environment.SetEnvironmentVariable("AGENT_USE_NODE24", null);
Environment.SetEnvironmentVariable("AGENT_USE_NODE24_WITH_HANDLER_DATA", null);
}
}
}

//test the AGENT_USE_NODE24_WITH_HANDLER_DATA knob
[Theory]
[InlineData("node")]
[InlineData("node10")]
[InlineData("node16")]
[InlineData("node20_1")]
[InlineData("node24")]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void ForceUseNode24Knob(string nodeVersion)
{
ResetNodeKnobs();

Environment.SetEnvironmentVariable("AGENT_USE_NODE24", "true");

try
{
// Use a unique test name per data row to avoid sharing the same trace file across parallel runs
using (TestHostContext thc = CreateTestHostContext($"{nameof(ForceUseNode24Knob)}_{nodeVersion}"))
{
thc.SetSingleton(new WorkerCommandManager() as IWorkerCommandManager);
thc.SetSingleton(new ExtensionManager() as IExtensionManager);

NodeHandler nodeHandler = new NodeHandler(nodeHandlerHalper.Object);

nodeHandler.Initialize(thc);
nodeHandler.ExecutionContext = CreateTestExecutionContext(thc);
nodeHandler.Data = nodeVersion switch
{
"node" => new NodeHandlerData(),
"node10" => new Node10HandlerData(),
"node16" => new Node16HandlerData(),
"node20_1" => new Node20_1HandlerData(),
"node24" => new Node24HandlerData(),
_ => throw new Exception("Invalid node version"),
};

string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false);
string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals),
"node24",
"bin",
$"node{IOUtil.ExeExtension}");
Assert.Equal(expectedLocation, actualLocation);
}
}
finally
{
Environment.SetEnvironmentVariable("AGENT_USE_NODE24", null);
}
}

//tests that Node24 is NOT used when handler data exists but knob is false
[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void DoNotUseNode24WhenHandlerDataKnobIsFalse()
{
ResetNodeKnobs();

Environment.SetEnvironmentVariable("AGENT_USE_NODE24_WITH_HANDLER_DATA", "false");

try
{
using (TestHostContext thc = CreateTestHostContext())
{
thc.SetSingleton(new WorkerCommandManager() as IWorkerCommandManager);
thc.SetSingleton(new ExtensionManager() as IExtensionManager);

NodeHandler nodeHandler = new NodeHandler(nodeHandlerHalper.Object);

nodeHandler.Initialize(thc);
nodeHandler.ExecutionContext = CreateTestExecutionContext(thc);
// Task has Node24HandlerData but knob is false
nodeHandler.Data = new Node24HandlerData();

string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false);
// Should fall back to Node20_1 (the default)
string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals),
"node20_1",
"bin",
$"node{IOUtil.ExeExtension}");
Assert.Equal(expectedLocation, actualLocation);
}
}
finally
{
Environment.SetEnvironmentVariable("AGENT_USE_NODE24_WITH_HANDLER_DATA", null);
}
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
Expand Down Expand Up @@ -491,6 +583,7 @@ private void ResetNodeKnobs()
Environment.SetEnvironmentVariable("AGENT_USE_NODE20_IN_UNSUPPORTED_SYSTEM", null);
Environment.SetEnvironmentVariable("AGENT_USE_NODE24", null);
Environment.SetEnvironmentVariable("AGENT_USE_NODE24_IN_UNSUPPORTED_SYSTEM", null);
Environment.SetEnvironmentVariable("AGENT_USE_NODE24_WITH_HANDLER_DATA", null);
}
}
}
Loading