Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
34 changes: 23 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,16 @@ 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)
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 +395,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