mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
135 lines
6.0 KiB
C#
135 lines
6.0 KiB
C#
using System.IO;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using GitHub.Runner.Common;
|
|
using GitHub.Runner.Sdk;
|
|
using GitHub.DistributedTask.WebApi;
|
|
using Pipelines = GitHub.DistributedTask.Pipelines;
|
|
using System;
|
|
using System.Linq;
|
|
|
|
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.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;
|
|
}
|
|
|
|
// Resolve the target script.
|
|
string target = null;
|
|
if (stage == ActionRunStage.Main)
|
|
{
|
|
target = Data.Script;
|
|
}
|
|
else if (stage == ActionRunStage.Post)
|
|
{
|
|
target = Data.Cleanup;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
var nodeRuntimeVersion = await StepHost.DetermineNodeRuntimeVersion(ExecutionContext);
|
|
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(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
|
|
|
|
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(workingDirectory: StepHost.ResolvePathForStepHost(workingDirectory),
|
|
fileName: StepHost.ResolvePathForStepHost(file),
|
|
arguments: arguments,
|
|
environment: Environment,
|
|
requireExitCodeZero: false,
|
|
outputEncoding: outputEncoding,
|
|
killProcessOnCancel: false,
|
|
inheritConsoleHandler: !ExecutionContext.Variables.Retain_Default_Encoding,
|
|
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;
|
|
if (exitCode != 0)
|
|
{
|
|
ExecutionContext.Error($"Node run failed with exit code {exitCode}");
|
|
ExecutionContext.Result = TaskResult.Failed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|