From 46f7da16281074930740e50855206fb51f588fe6 Mon Sep 17 00:00:00 2001 From: Ethan Chiu Date: Fri, 12 Jun 2020 15:28:22 -0400 Subject: [PATCH] Thoughts on next steps --- .../Handlers/CompositeActionHandler.cs | 153 ++++++++++++++---- 1 file changed, 120 insertions(+), 33 deletions(-) diff --git a/src/Runner.Worker/Handlers/CompositeActionHandler.cs b/src/Runner.Worker/Handlers/CompositeActionHandler.cs index 0be6ecbaa..eb1b0ff47 100644 --- a/src/Runner.Worker/Handlers/CompositeActionHandler.cs +++ b/src/Runner.Worker/Handlers/CompositeActionHandler.cs @@ -34,6 +34,13 @@ namespace GitHub.Runner.Worker.Handlers // DELETE LATER // await Task.Yield(); + + // Basically, the only difference from ScriptHandler.cs is that "contents" is not just each step under "run: " + // It might make more sense to: + // 1) Abstract the core functionality of the ScriptHandler.cs that we need for BOTH CompositeActionHandler.cs and ScriptHandler.cs + // 2) Call those functions in both handlers + // * There is already a file called ScriptHandlerHelpers.cs that might be a good location to add more functions. + // Copied from ScriptHandler.cs // Validate args. Trace.Entering(); @@ -50,25 +57,73 @@ namespace GitHub.Runner.Worker.Handlers // For now, just assume it is 1 Run step // We will adapt this in the future. + // Copied from ActionRunner.cs RunAsync() function => Maybe we don't need a handler and need to avoid this preplicatoin in the future? var runStepInputs= target[0].Inputs; + var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator(); + var inputs = templateEvaluator.EvaluateStepInputs(runStepInputs, ExecutionContext.ExpressionValues, ExecutionContext.ExpressionFunctions); + var taskManager = HostContext.GetService(); + + var userInputs = new HashSet(StringComparer.OrdinalIgnoreCase); + var runValue = ""; + foreach (KeyValuePair input in inputs) + { + userInputs.Add(input.Key); + userInputs.Add(input.Value); + if (input.Key.Equals("run")) + { + runValue = input.Value; + } + + // string message = ""; + // if (definition.Data?.Deprecated?.TryGetValue(input.Key, out message) == true) + // { + // ExecutionContext.Warning(String.Format("Input '{0}' has been deprecated with message: {1}", input.Key, message)); + // } + + // Get the run bash value that we want to run + // In the future, we would apply and validate the template => maybe using the manifest manager to recursively load the json schema. + } + + // Let's think about validating inputs later + // Validate inputs only for actions with action.yml + // var unexpectedInputs = new List(); + // foreach (var input in userInputs) + // { + // if (!validInputs.Contains(input)) + // { + // unexpectedInputs.Add(input); + // } + // } + + // if (unexpectedInputs.Count > 0) + // { + // ExecutionContext.Warning($"Unexpected input(s) '{string.Join("', '", unexpectedInputs)}', valid inputs are ['{string.Join("', '", validInputs)}']"); + // } + //TODO: + // 6/11/20 EOD thoughts + // => What functions do I need to use from ScriptHandlerHelpers.cs? + // Do I need to Reverse the string for prepending the path?? + // ^ why or why not? + // How do I process the Inputs? + // => It's a TemplateToken + // How do I incorporate Async? we call the StepHost for Async when you call ExecuteAsync() - // For now assume it's just a run step. - // runStep.TryGetValue("run", out var runDefaults); - string prependPath = string.Join(Path.PathSeparator.ToString(), runStep..Reverse()); + // Detect operating system for fileName + arguments + + + // Resolve the working directory. + string workingDirectory = ExecutionContext.GetGitHubContext("workspace"); + if (string.IsNullOrEmpty(workingDirectory)) + { + workingDirectory = HostContext.GetDirectory(WellKnownDirectory.Work); + } + // A lot of copying from ScriptHandler.cs => Should we just invoke the ScriptHandler in the future and just modify it + // to meet our needs for a composite action? - - - - // Copied from ScriptHandler.cs and ScriptHandlerHelpers.cs to handle bash commands. - string argFormat; - string shellCommand; - string shellCommandPath = null; - bool validateShellOnHost = !(StepHost is ContainerStepHost); - string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse()); string shell = null; if (!Inputs.TryGetValue("shell", out shell) || string.IsNullOrEmpty(shell)) { @@ -76,29 +131,33 @@ namespace GitHub.Runner.Worker.Handlers // for now, we won't check job.defaults if we are inside a template. if (string.IsNullOrEmpty(ExecutionContext.ScopeName) && ExecutionContext.JobDefaults.TryGetValue("run", out var runDefaults)) { - runDefaults.TryGetValue("shell", out shell); + 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.PrependPath.Reverse()); + string commandPath, argFormat, shellCommand; + if (string.IsNullOrEmpty(shell)) { #if OS_WINDOWS shellCommand = "pwsh"; - if (validateShellOnHost) + commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath); + if (string.IsNullOrEmpty(commandPath)) { - shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath); - if (string.IsNullOrEmpty(shellCommandPath)) - { - shellCommand = "powershell"; - Trace.Info($"Defaulting to {shellCommand}"); - shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath); - } + shellCommand = "powershell"; + Trace.Info($"Defaulting to {shellCommand}"); + commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath); } + ArgUtil.NotNullOrEmpty(commandPath, "Default Shell"); #else shellCommand = "sh"; - if (validateShellOnHost) - { - shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath); - } + commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath); #endif argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand); } @@ -106,11 +165,8 @@ namespace GitHub.Runner.Worker.Handlers { var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell); shellCommand = parsed.shellCommand; - if (validateShellOnHost) - { - shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath); - } - + // 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)) { @@ -118,13 +174,44 @@ namespace GitHub.Runner.Worker.Handlers } } - - - + // 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}'"); + } + // Use StepHost.ExecuteAsync() to execute code + // What does StepHost.ExecuteAsync do? + // basically just adds configuration stuff to the code for it to run and then invoke + // processInvoker.ExecuteAsync() which basically runs the process with the necessary configurations for the process. + 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.Variables.Retain_Default_Encoding, + cancellationToken: ExecutionContext.CancellationToken); + + // Error + if (exitCode != 0) + { + ExecutionContext.Error($"Process completed with exit code {exitCode}."); + ExecutionContext.Result = TaskResult.Failed; + } + } + } } } \ No newline at end of file