Files
runner/src/Runner.Worker/Handlers/NodeScriptActionHandler.cs
Tatyana Kostromskaya 8206cf4e73 Add node20 to runner (#2732)
* .

* fix hashes

* fix linux-arm

* linux-arm64

* osx-x64

* win-x64

* clean up node12 deprecation warning

* Add node20

* Remove node12 from the BuildInVersions list

* fix externals hash
2023-08-11 11:11:28 +02:00

178 lines
8.3 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.DistributedTask.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Common.Util;
using GitHub.Runner.Sdk;
using GitHub.Runner.Worker.Container;
using GitHub.Runner.Worker.Container.ContainerHooks;
namespace GitHub.Runner.Worker.Handlers
{
[ServiceLocator(Default = typeof(NodeScriptActionHandler))]
public interface INodeScriptActionHandler : IHandler
{
NodeJSActionExecutionData Data { get; set; }
}
public sealed class NodeScriptActionHandler : Handler, INodeScriptActionHandler
{
public NodeJSActionExecutionData Data { get; set; }
public async Task RunAsync(ActionRunStage stage)
{
// Validate args.
Trace.Entering();
ArgUtil.NotNull(Data, nameof(Data));
ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext));
ArgUtil.NotNull(Inputs, nameof(Inputs));
ArgUtil.Directory(ActionDirectory, nameof(ActionDirectory));
// Update the env dictionary.
AddInputsToEnvironment();
AddPrependPathToEnvironment();
// expose context to environment
foreach (var context in ExecutionContext.ExpressionValues)
{
if (context.Value is IEnvironmentContextData runtimeContext && runtimeContext != null)
{
foreach (var env in runtimeContext.GetRuntimeEnvironmentVariables())
{
Environment[env.Key] = env.Value;
}
}
}
// Add Actions Runtime server info
var systemConnection = ExecutionContext.Global.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
Environment["ACTIONS_RUNTIME_URL"] = systemConnection.Url.AbsoluteUri;
Environment["ACTIONS_RUNTIME_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
if (systemConnection.Data.TryGetValue("CacheServerUrl", out var cacheUrl) && !string.IsNullOrEmpty(cacheUrl))
{
Environment["ACTIONS_CACHE_URL"] = cacheUrl;
}
if (systemConnection.Data.TryGetValue("GenerateIdTokenUrl", out var generateIdTokenUrl) && !string.IsNullOrEmpty(generateIdTokenUrl))
{
Environment["ACTIONS_ID_TOKEN_REQUEST_URL"] = generateIdTokenUrl;
Environment["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = systemConnection.Authorization.Parameters[EndpointAuthorizationParameters.AccessToken];
}
if (systemConnection.Data.TryGetValue("ResultsServiceUrl", out var resultsUrl) && !string.IsNullOrEmpty(resultsUrl))
{
Environment["ACTIONS_RESULTS_URL"] = resultsUrl;
}
// Resolve the target script.
string target = null;
if (stage == ActionRunStage.Main)
{
target = Data.Script;
}
else if (stage == ActionRunStage.Pre)
{
target = Data.Pre;
}
else if (stage == ActionRunStage.Post)
{
target = Data.Post;
}
// Set extra telemetry base on the current context.
if (stage == ActionRunStage.Main)
{
ExecutionContext.StepTelemetry.HasPreStep = Data.HasPre;
ExecutionContext.StepTelemetry.HasPostStep = Data.HasPost;
}
ExecutionContext.StepTelemetry.Type = Data.NodeVersion;
ArgUtil.NotNullOrEmpty(target, nameof(target));
target = Path.Combine(ActionDirectory, target);
ArgUtil.File(target, nameof(target));
// Resolve the working directory.
string workingDirectory = ExecutionContext.GetGitHubContext("workspace");
if (string.IsNullOrEmpty(workingDirectory))
{
workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work);
}
if (string.Equals(Data.NodeVersion, "node12", StringComparison.OrdinalIgnoreCase) &&
Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm64))
{
ExecutionContext.Output($"The node12 is not supported. Use node16 instead.");
Data.NodeVersion = "node16";
}
string forcedNodeVersion = System.Environment.GetEnvironmentVariable(Constants.Variables.Agent.ForcedActionsNodeVersion);
if (forcedNodeVersion == "node16" && Data.NodeVersion != "node16")
{
Data.NodeVersion = "node16";
}
var nodeRuntimeVersion = await StepHost.DetermineNodeRuntimeVersion(ExecutionContext, Data.NodeVersion);
string file = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), nodeRuntimeVersion, "bin", $"node{IOUtil.ExeExtension}");
// Format the arguments passed to node.
// 1) Wrap the script file path in double quotes.
// 2) Escape double quotes within the script file path. Double-quote is a valid
// file name character on Linux.
string arguments = StepHost.ResolvePathForStepHost(ExecutionContext, StringUtil.Format(@"""{0}""", target.Replace(@"""", @"\""")));
#if OS_WINDOWS
// It appears that node.exe outputs UTF8 when not in TTY mode.
Encoding outputEncoding = Encoding.UTF8;
#else
// Let .NET choose the default.
Encoding outputEncoding = null;
#endif
// Remove environment variable that may cause conflicts with the node within the runner.
Environment.Remove("NODE_ICU_DATA"); // https://github.com/actions/runner/issues/795
using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager))
using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager))
{
StepHost.OutputDataReceived += stdoutManager.OnDataReceived;
StepHost.ErrorDataReceived += stderrManager.OnDataReceived;
// Execute the process. Exit code 0 should always be returned.
// A non-zero exit code indicates infrastructural failure.
// Task failure should be communicated over STDOUT using ## commands.
Task<int> step = StepHost.ExecuteAsync(ExecutionContext,
workingDirectory: StepHost.ResolvePathForStepHost(ExecutionContext, workingDirectory),
fileName: StepHost.ResolvePathForStepHost(ExecutionContext, file),
arguments: arguments,
environment: Environment,
requireExitCodeZero: false,
outputEncoding: outputEncoding,
killProcessOnCancel: false,
inheritConsoleHandler: !ExecutionContext.Global.Variables.Retain_Default_Encoding,
standardInInput: null,
cancellationToken: ExecutionContext.CancellationToken);
// Wait for either the node exit or force finish through ##vso command
await System.Threading.Tasks.Task.WhenAny(step, ExecutionContext.ForceCompleted);
if (ExecutionContext.ForceCompleted.IsCompleted)
{
ExecutionContext.Debug("The task was marked as \"done\", but the process has not closed after 5 seconds. Treating the task as complete.");
}
else
{
var exitCode = await step;
ExecutionContext.Debug($"Node Action run completed with exit code {exitCode}");
if (exitCode != 0)
{
ExecutionContext.Result = TaskResult.Failed;
}
}
}
}
}
}