#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously (method has async logic on only certain platforms) public async Task RunAsync(ActionRunStage stage) #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { // Validate args. Trace.Entering(); ArgUtil.NotNull(Data, nameof(Data)); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); #if OS_WINDOWS || OS_OSX throw new NotSupportedException($"Container action is only supported on Linux"); #else // Update the env dictionary. AddInputsToEnvironment(); var dockerManager = HostContext.GetService <IDockerCommandManager>(); // container image haven't built/pull if (Data.Image.StartsWith("docker://", StringComparison.OrdinalIgnoreCase)) { Data.Image = Data.Image.Substring("docker://".Length); } else if (Data.Image.EndsWith("Dockerfile") || Data.Image.EndsWith("dockerfile")) { // ensure docker file exist var dockerFile = Path.Combine(ActionDirectory, Data.Image); ArgUtil.File(dockerFile, nameof(Data.Image)); ExecutionContext.Output($"##[group]Building docker image"); ExecutionContext.Output($"Dockerfile for action: '{dockerFile}'."); var imageName = $"{dockerManager.DockerInstanceLabel}:{ExecutionContext.Id.ToString("N")}"; var buildExitCode = await dockerManager.DockerBuild( ExecutionContext, ExecutionContext.GetGitHubContext("workspace"), dockerFile, Directory.GetParent(dockerFile).FullName, imageName); ExecutionContext.Output("##[endgroup]"); if (buildExitCode != 0) { throw new InvalidOperationException($"Docker build failed with exit code {buildExitCode}"); } Data.Image = imageName; } string type = Action.Type == Pipelines.ActionSourceType.Repository ? "Dockerfile" : "DockerHub"; // Add Telemetry to JobContext to send with JobCompleteMessage if (stage == ActionRunStage.Main) { var telemetry = new ActionsStepTelemetry { Ref = GetActionRef(), HasPreStep = Data.HasPre, HasPostStep = Data.HasPost, IsEmbedded = ExecutionContext.IsEmbedded, Type = type }; ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry); } // run container var container = new ContainerInfo(HostContext) { ContainerImage = Data.Image, ContainerName = ExecutionContext.Id.ToString("N"), ContainerDisplayName = $"{Pipelines.Validation.NameValidation.Sanitize(Data.Image)}_{Guid.NewGuid().ToString("N").Substring(0, 6)}", }; if (stage == ActionRunStage.Main) { if (!string.IsNullOrEmpty(Data.EntryPoint)) { // use entrypoint from action.yml container.ContainerEntryPoint = Data.EntryPoint; } else { // use entrypoint input, this is for action v1 which doesn't have action.yml container.ContainerEntryPoint = Inputs.GetValueOrDefault("entryPoint"); } } else if (stage == ActionRunStage.Pre) { container.ContainerEntryPoint = Data.Pre; } else if (stage == ActionRunStage.Post) { container.ContainerEntryPoint = Data.Post; } // create inputs context for template evaluation var inputsContext = new DictionaryContextData(); if (this.Inputs != null) { foreach (var input in Inputs) { inputsContext.Add(input.Key, new StringContextData(input.Value)); } } var extraExpressionValues = new Dictionary <string, PipelineContextData>(StringComparer.OrdinalIgnoreCase); extraExpressionValues["inputs"] = inputsContext; var manifestManager = HostContext.GetService <IActionManifestManager>(); if (Data.Arguments != null) { container.ContainerEntryPointArgs = ""; var evaluatedArgs = manifestManager.EvaluateContainerArguments(ExecutionContext, Data.Arguments, extraExpressionValues); foreach (var arg in evaluatedArgs) { if (!string.IsNullOrEmpty(arg)) { container.ContainerEntryPointArgs = container.ContainerEntryPointArgs + $" \"{arg.Replace("\"", "\\\"")}\""; } else { container.ContainerEntryPointArgs = container.ContainerEntryPointArgs + " \"\""; } } } else { container.ContainerEntryPointArgs = Inputs.GetValueOrDefault("args"); } if (Data.Environment != null) { var evaluatedEnv = manifestManager.EvaluateContainerEnvironment(ExecutionContext, Data.Environment, extraExpressionValues); foreach (var env in evaluatedEnv) { if (!this.Environment.ContainsKey(env.Key)) { this.Environment[env.Key] = env.Value; } } } if (ExecutionContext.JobContext.Container.TryGetValue("network", out var networkContextData) && networkContextData is StringContextData networkStringData) { container.ContainerNetwork = networkStringData.ToString(); } var defaultWorkingDirectory = ExecutionContext.GetGitHubContext("workspace"); var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); ArgUtil.NotNullOrEmpty(defaultWorkingDirectory, nameof(defaultWorkingDirectory)); ArgUtil.NotNullOrEmpty(tempDirectory, nameof(tempDirectory)); var tempHomeDirectory = Path.Combine(tempDirectory, "_github_home"); Directory.CreateDirectory(tempHomeDirectory); this.Environment["HOME"] = tempHomeDirectory; var tempFileCommandDirectory = Path.Combine(tempDirectory, "_runner_file_commands"); ArgUtil.Directory(tempFileCommandDirectory, nameof(tempFileCommandDirectory)); var tempWorkflowDirectory = Path.Combine(tempDirectory, "_github_workflow"); ArgUtil.Directory(tempWorkflowDirectory, nameof(tempWorkflowDirectory)); container.MountVolumes.Add(new MountVolume("/var/run/docker.sock", "/var/run/docker.sock")); container.MountVolumes.Add(new MountVolume(tempHomeDirectory, "/github/home")); container.MountVolumes.Add(new MountVolume(tempWorkflowDirectory, "/github/workflow")); container.MountVolumes.Add(new MountVolume(tempFileCommandDirectory, "/github/file_commands")); container.MountVolumes.Add(new MountVolume(defaultWorkingDirectory, "/github/workspace")); container.AddPathTranslateMapping(tempHomeDirectory, "/github/home"); container.AddPathTranslateMapping(tempWorkflowDirectory, "/github/workflow"); container.AddPathTranslateMapping(tempFileCommandDirectory, "/github/file_commands"); container.AddPathTranslateMapping(defaultWorkingDirectory, "/github/workspace"); container.ContainerWorkDirectory = "/github/workspace"; // 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]; } foreach (var variable in this.Environment) { container.ContainerEnvironmentVariables[variable.Key] = container.TranslateToContainerPath(variable.Value); } using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager, container)) using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager, container)) { var runExitCode = await dockerManager.DockerRun(ExecutionContext, container, stdoutManager.OnDataReceived, stderrManager.OnDataReceived); ExecutionContext.Debug($"Docker Action run completed with exit code {runExitCode}"); if (runExitCode != 0) { ExecutionContext.Result = TaskResult.Failed; } } #endif }
public async Task RunAsync(ActionRunStage stage) { // Validate args Trace.Entering(); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); List <Pipelines.ActionStep> steps; if (stage == ActionRunStage.Pre) { ArgUtil.NotNull(Data.PreSteps, nameof(Data.PreSteps)); steps = Data.PreSteps; } else if (stage == ActionRunStage.Post) { ArgUtil.NotNull(Data.PostSteps, nameof(Data.PostSteps)); steps = new List <Pipelines.ActionStep>(); // Only register post steps for steps that actually ran foreach (var step in Data.PostSteps.ToList()) { if (ExecutionContext.Root.EmbeddedStepsWithPostRegistered.ContainsKey(step.Id)) { step.Condition = ExecutionContext.Root.EmbeddedStepsWithPostRegistered[step.Id]; steps.Add(step); } else { Trace.Info($"Skipping executing post step id: {step.Id}, name: ${step.DisplayName}"); } } } else { ArgUtil.NotNull(Data.Steps, nameof(Data.Steps)); steps = Data.Steps; } // Add Telemetry to JobContext to send with JobCompleteMessage if (stage == ActionRunStage.Main) { var hasRunsStep = false; var hasUsesStep = false; foreach (var step in steps) { if (step.Reference.Type == Pipelines.ActionSourceType.Script) { hasRunsStep = true; } else { hasUsesStep = true; } } var pathReference = Action as Pipelines.RepositoryPathReference; var telemetry = new ActionsStepTelemetry { Ref = GetActionRef(), HasPreStep = Data.HasPre, HasPostStep = Data.HasPost, IsEmbedded = ExecutionContext.IsEmbedded, Type = "composite", HasRunsStep = hasRunsStep, HasUsesStep = hasUsesStep, StepCount = steps.Count }; ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry); } try { // Inputs of the composite step var inputsData = new DictionaryContextData(); foreach (var i in Inputs) { inputsData[i.Key] = new StringContextData(i.Value); } // Temporary hack until after 3.2. After 3.2 the server will never send an empty // context name. Generated context names start with "__" var childScopeName = ExecutionContext.GetFullyQualifiedContextName(); if (string.IsNullOrEmpty(childScopeName)) { childScopeName = $"__{Guid.NewGuid()}"; } // Create embedded steps var embeddedSteps = new List <IStep>(); // If we need to setup containers beforehand, do it // only relevant for local composite actions that need to JIT download/setup containers if (LocalActionContainerSetupSteps != null && LocalActionContainerSetupSteps.Count > 0) { foreach (var step in LocalActionContainerSetupSteps) { ArgUtil.NotNull(step, step.DisplayName); var stepId = $"__{Guid.NewGuid()}"; step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepId, Guid.NewGuid(), stage); embeddedSteps.Add(step); } } foreach (Pipelines.ActionStep stepData in steps) { // Compute child sibling scope names for post steps // We need to use the main's scope to keep step context correct, makes inputs flow correctly string siblingScopeName = null; if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName) && stage == ActionRunStage.Post) { siblingScopeName = $"{ExecutionContext.SiblingScopeName}.{stepData.ContextName}"; } var step = HostContext.CreateService <IActionRunner>(); step.Action = stepData; step.Stage = stage; step.Condition = stepData.Condition; ExecutionContext.Root.EmbeddedIntraActionState.TryGetValue(step.Action.Id, out var intraActionState); step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, stage, intraActionState: intraActionState, siblingScopeName: siblingScopeName); step.ExecutionContext.ExpressionValues["inputs"] = inputsData; if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName)) { step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(ExecutionContext.SiblingScopeName); } else { step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName); } // Shallow copy github context var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext; ArgUtil.NotNull(gitHubContext, nameof(gitHubContext)); gitHubContext = gitHubContext.ShallowCopy(); step.ExecutionContext.ExpressionValues["github"] = gitHubContext; // Set GITHUB_ACTION_PATH step.ExecutionContext.SetGitHubContext("action_path", ActionDirectory); embeddedSteps.Add(step); } // Run embedded steps await RunStepsAsync(embeddedSteps, stage); // Set outputs ExecutionContext.ExpressionValues["inputs"] = inputsData; ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName); ProcessOutputs(); } catch (Exception ex) { // Composite StepRunner should never throw exception out. Trace.Error($"Caught exception from composite steps {nameof(CompositeActionHandler)}: {ex}"); ExecutionContext.Error(ex); ExecutionContext.Result = TaskResult.Failed; } }
public async Task RunAsync(ActionRunStage stage) { if (stage == ActionRunStage.Post) { throw new NotSupportedException("Script action should not have 'Post' job action."); } // Validate args Trace.Entering(); ArgUtil.NotNull(ExecutionContext, nameof(ExecutionContext)); ArgUtil.NotNull(Inputs, nameof(Inputs)); var githubContext = ExecutionContext.ExpressionValues["github"] as GitHubContext; ArgUtil.NotNull(githubContext, nameof(githubContext)); // Add Telemetry to JobContext to send with JobCompleteMessage if (stage == ActionRunStage.Main) { var telemetry = new ActionsStepTelemetry { IsEmbedded = ExecutionContext.IsEmbedded, Type = "run", }; ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry); } var tempDirectory = HostContext.GetDirectory(WellKnownDirectory.Temp); Inputs.TryGetValue("script", out var contents); contents = contents ?? string.Empty; string workingDirectory = null; if (!Inputs.TryGetValue("workingDirectory", out workingDirectory)) { if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.Global.JobDefaults.TryGetValue("run", out var runDefaults)) { if (runDefaults.TryGetValue("working-directory", out workingDirectory)) { ExecutionContext.Debug("Overwrite 'working-directory' base on job defaults."); } } } var workspaceDir = githubContext["workspace"] as StringContextData; workingDirectory = Path.Combine(workspaceDir, workingDirectory ?? string.Empty); string shell = null; if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell)) { if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.Global.JobDefaults.TryGetValue("run", out var runDefaults)) { if (runDefaults.TryGetValue("shell", out shell)) { ExecutionContext.Debug("Overwrite 'shell' base on job defaults."); } } } var isContainerStepHost = StepHost is ContainerStepHost; string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.Global.PrependPath.Reverse <string>()); string commandPath, argFormat, shellCommand; // Set up default command and arguments if (string.IsNullOrEmpty(shell)) { #if OS_WINDOWS shellCommand = "pwsh"; commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath); if (string.IsNullOrEmpty(commandPath)) { shellCommand = "powershell"; Trace.Info($"Defaulting to {shellCommand}"); commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath); } ArgUtil.NotNullOrEmpty(commandPath, "Default Shell"); #else shellCommand = "sh"; commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath); #endif argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand); } else { var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell); shellCommand = parsed.shellCommand; // For non-ContainerStepHost, the command must be located on the host by Which commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath); argFormat = $"{parsed.shellArgs}".TrimStart(); if (string.IsNullOrEmpty(argFormat)) { argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand); } } // No arg format was given, shell must be a built-in if (string.IsNullOrEmpty(argFormat) || !argFormat.Contains("{0}")) { throw new ArgumentException("Invalid shell option. Shell must be a valid built-in (bash, sh, cmd, powershell, pwsh) or a format string containing '{0}'"); } // We do not not the full path until we know what shell is being used, so that we can determine the file extension var scriptFilePath = Path.Combine(tempDirectory, $"{Guid.NewGuid()}{ScriptHandlerHelpers.GetScriptFileExtension(shellCommand)}"); var resolvedScriptPath = $"{StepHost.ResolvePathForStepHost(scriptFilePath).Replace("\"", "\\\"")}"; // Format arg string with script path var arguments = string.Format(argFormat, resolvedScriptPath); // Fix up and write the script contents = ScriptHandlerHelpers.FixUpScriptContents(shellCommand, contents); #if OS_WINDOWS // Normalize Windows line endings contents = contents.Replace("\r\n", "\n").Replace("\n", "\r\n"); var encoding = ExecutionContext.Global.Variables.Retain_Default_Encoding && Console.InputEncoding.CodePage != 65001 ? Console.InputEncoding : new UTF8Encoding(false); #else // Don't add a BOM. It causes the script to fail on some operating systems (e.g. on Ubuntu 14). var encoding = new UTF8Encoding(false); #endif // Script is written to local path (ie host) but executed relative to the StepHost, which may be a container File.WriteAllText(scriptFilePath, contents, encoding); // Prepend PATH 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; } } } // dump out the command var fileName = isContainerStepHost ? shellCommand : commandPath; #if OS_OSX if (Environment.ContainsKey("DYLD_INSERT_LIBRARIES")) // We don't check `isContainerStepHost` because we don't support container on macOS { // launch `node macOSRunInvoker.js shell args` instead of `shell args` to avoid macOS SIP remove `DYLD_INSERT_LIBRARIES` when launch process string node12 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}"); string macOSRunInvoker = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "macos-run-invoker.js"); arguments = $"\"{macOSRunInvoker.Replace("\"", "\\\"")}\" \"{fileName.Replace("\"", "\\\"")}\" {arguments}"; fileName = node12; } #endif var systemConnection = ExecutionContext.Global.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase)); 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]; } ExecutionContext.Debug($"{fileName} {arguments}"); using (var stdoutManager = new OutputManager(ExecutionContext, ActionCommandManager)) using (var stderrManager = new OutputManager(ExecutionContext, ActionCommandManager)) { StepHost.OutputDataReceived += stdoutManager.OnDataReceived; StepHost.ErrorDataReceived += stderrManager.OnDataReceived; // Execute int exitCode = await StepHost.ExecuteAsync(workingDirectory : StepHost.ResolvePathForStepHost(workingDirectory), fileName : fileName, arguments : arguments, environment : Environment, requireExitCodeZero : false, outputEncoding : null, killProcessOnCancel : false, inheritConsoleHandler : !ExecutionContext.Global.Variables.Retain_Default_Encoding, cancellationToken : ExecutionContext.CancellationToken); // Error if (exitCode != 0) { ExecutionContext.Error($"Process completed with exit code {exitCode}."); ExecutionContext.Result = TaskResult.Failed; } } }
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]; } // 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; } // Add Telemetry to JobContext to send with JobCompleteMessage if (stage == ActionRunStage.Main) { var telemetry = new ActionsStepTelemetry { Ref = GetActionRef(), HasPreStep = Data.HasPre, HasPostStep = Data.HasPost, IsEmbedded = ExecutionContext.IsEmbedded, Type = Data.NodeVersion }; ExecutionContext.Root.ActionsStepsTelemetry.Add(telemetry); } 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, 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(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(workingDirectory: StepHost.ResolvePathForStepHost(workingDirectory), fileName: StepHost.ResolvePathForStepHost(file), arguments: arguments, environment: Environment, requireExitCodeZero: false, outputEncoding: outputEncoding, killProcessOnCancel: false, inheritConsoleHandler: !ExecutionContext.Global.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; ExecutionContext.Debug($"Node Action run completed with exit code {exitCode}"); if (exitCode != 0) { ExecutionContext.Result = TaskResult.Failed; } } } }