diff --git a/src/Misc/containerEngineHandlers/podmanHandler/src/podmanHandler.ts b/src/Misc/containerEngineHandlers/podmanHandler/src/podmanHandler.ts index 0833be68d..58979cbd9 100644 --- a/src/Misc/containerEngineHandlers/podmanHandler/src/podmanHandler.ts +++ b/src/Misc/containerEngineHandlers/podmanHandler/src/podmanHandler.ts @@ -107,9 +107,37 @@ async function run(): Promise { await exec.exec('podman', ['rm', '-f', jobContainerId]) await exec.exec('podman', ['network', 'rm', '-f', network]) + } else if (command === 'Exec') { + const execInput = inputJson.execInput + core.debug(JSON.stringify(execInput)) + + // podman exec -i --workdir /__w/canary/canary + // -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY + // -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER + // -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR + // -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME + // -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL + // -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY + // -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_DEBUG + // -e RUNNER_OS -e RUNNER_NAME -e RUNNER_TOOL_CACHE + // -e RUNNER_TEMP -e RUNNER_WORKSPACE + // eccdf520697a035599d6e8c8dc801f004fdd3797cdce88f590aba3669a88d9bc sh -e /__w/_temp/d3b30383-719c-4e76-a16f-8f85443352be.sh + + const execArgs = ['exec'] + execArgs.push('-i') + execArgs.push(`--wordir=${execInput.workingDirectory}`) + for (const envKey of execInput.environmentKeys) { + execArgs.push(`-e=${envKey}`) + } + execArgs.push(execInput.jobContainer.containerId) + execArgs.push(execInput.fileName) + execArgs.push(execInput.arguments) + + core.debug(JSON.stringify(execArgs)) + + await exec.exec('podman', execArgs) } - // else if (command === 'Exec') { - // } + await exec.exec('podman', ['network', 'ls']) await exec.exec('podman', ['ps', '-a']) } diff --git a/src/Misc/layoutbin/podmanHandler/index.js b/src/Misc/layoutbin/podmanHandler/index.js index 741c25ae1..7ea73be8c 100644 --- a/src/Misc/layoutbin/podmanHandler/index.js +++ b/src/Misc/layoutbin/podmanHandler/index.js @@ -1130,8 +1130,32 @@ function run() { yield exec.exec('podman', ['rm', '-f', jobContainerId]); yield exec.exec('podman', ['network', 'rm', '-f', network]); } - // else if (command === 'Exec') { - // } + else if (command === 'Exec') { + const execInput = inputJson.execInput; + core.debug(JSON.stringify(execInput)); + // podman exec -i --workdir /__w/canary/canary + // -e GITHUB_JOB -e GITHUB_REF -e GITHUB_SHA -e GITHUB_REPOSITORY + // -e GITHUB_REPOSITORY_OWNER -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER + // -e GITHUB_RETENTION_DAYS -e GITHUB_RUN_ATTEMPT -e GITHUB_ACTOR + // -e GITHUB_WORKFLOW -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GITHUB_EVENT_NAME + // -e GITHUB_SERVER_URL -e GITHUB_API_URL -e GITHUB_GRAPHQL_URL + // -e GITHUB_WORKSPACE -e GITHUB_ACTION -e GITHUB_EVENT_PATH -e GITHUB_ACTION_REPOSITORY + // -e GITHUB_ACTION_REF -e GITHUB_PATH -e GITHUB_ENV -e RUNNER_DEBUG + // -e RUNNER_OS -e RUNNER_NAME -e RUNNER_TOOL_CACHE + // -e RUNNER_TEMP -e RUNNER_WORKSPACE + // eccdf520697a035599d6e8c8dc801f004fdd3797cdce88f590aba3669a88d9bc sh -e /__w/_temp/d3b30383-719c-4e76-a16f-8f85443352be.sh + const execArgs = ['exec']; + execArgs.push("-i"); + execArgs.push(`--wordir=${execInput.workingDirectory}`); + for (const envKey of execInput.environmentKeys) { + execArgs.push(`-e=${envKey}`); + } + execArgs.push(execInput.jobContainer.containerId); + execArgs.push(execInput.fileName); + execArgs.push(execInput.arguments); + core.debug(JSON.stringify(execArgs)); + yield exec.exec('podman', execArgs); + } yield exec.exec('podman', ['network', 'ls']); yield exec.exec('podman', ['ps', '-a']); }); diff --git a/src/Runner.Worker/ContainerOperationProvider.cs b/src/Runner.Worker/ContainerOperationProvider.cs index 3069213d1..677f35deb 100644 --- a/src/Runner.Worker/ContainerOperationProvider.cs +++ b/src/Runner.Worker/ContainerOperationProvider.cs @@ -29,12 +29,34 @@ namespace GitHub.Runner.Worker public ContainersCreationInput CreationInput { get; set; } [DataMember] - public object JobContainerExecInput { get; set; } + public JobContainerExecInput ExecInput { get; set; } [DataMember] public ContainersRemoveInput RemoveInput { get; set; } } + [DataContract] + public class JobContainerExecInput + { + [DataMember] + public ContainerInfo JobContainer { get; set; } + + [DataMember] + public string WorkingDirectory { get; set; } + + + [DataMember] + public string FileName { get; set; } + + + [DataMember] + public string Arguments { get; set; } + + + [DataMember] + public List EnvironmentKeys { get; set; } + } + [DataContract] public class ContainersCreationInput { @@ -192,6 +214,7 @@ namespace GitHub.Runner.Worker { executionContext.JobContext.Container["network"] = new StringContextData(podmanOutput.CreationOutput.Network); executionContext.JobContext.Container["id"] = new StringContextData(podmanOutput.CreationOutput.JobContainerId); + executionContext.Global.Container.ContainerId = podmanOutput.CreationOutput.JobContainerId; } } else diff --git a/src/Runner.Worker/Handlers/StepHost.cs b/src/Runner.Worker/Handlers/StepHost.cs index 0907eaed2..867e518f1 100644 --- a/src/Runner.Worker/Handlers/StepHost.cs +++ b/src/Runner.Worker/Handlers/StepHost.cs @@ -21,6 +21,8 @@ namespace GitHub.Runner.Worker.Handlers event EventHandler OutputDataReceived; event EventHandler ErrorDataReceived; + IExecutionContext ExecutionContext { get; set; } + string ResolvePathForStepHost(string path); Task DetermineNodeRuntimeVersion(IExecutionContext executionContext); @@ -53,6 +55,8 @@ namespace GitHub.Runner.Worker.Handlers public event EventHandler OutputDataReceived; public event EventHandler ErrorDataReceived; + public IExecutionContext ExecutionContext { get; set; } + public string ResolvePathForStepHost(string path) { return path; @@ -99,6 +103,8 @@ namespace GitHub.Runner.Worker.Handlers public event EventHandler OutputDataReceived; public event EventHandler ErrorDataReceived; + public IExecutionContext ExecutionContext { get; set; } + public string ResolvePathForStepHost(string path) { // make sure container exist. @@ -174,69 +180,137 @@ namespace GitHub.Runner.Worker.Handlers ArgUtil.NotNull(Container, nameof(Container)); ArgUtil.NotNullOrEmpty(Container.ContainerId, nameof(Container.ContainerId)); - var dockerManager = HostContext.GetService(); - string dockerClientPath = dockerManager.DockerPath; - - // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] - IList dockerCommandArgs = new List(); - dockerCommandArgs.Add($"exec"); - - // [OPTIONS] - dockerCommandArgs.Add($"-i"); - dockerCommandArgs.Add($"--workdir {workingDirectory}"); - foreach (var env in environment) + var podManHandler = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "podmanHandler", "index.js"); + if (File.Exists(podManHandler)) { - // e.g. -e MY_SECRET maps the value into the exec'ed process without exposing - // the value directly in the command - dockerCommandArgs.Add($"-e {env.Key}"); + var podmanInput = new ContainerEngineHandlerInput() + { + Command = "Exec", + ExecInput = new JobContainerExecInput() + { + JobContainer = this.Container, + WorkingDirectory = workingDirectory, + FileName = fileName, + Arguments = arguments, + EnvironmentKeys = environment.Keys.ToList() + } + }; + + // make sure all env are using container path + foreach (var envKey in environment.Keys.ToList()) + { + environment[envKey] = this.Container.TranslateToContainerPath(environment[envKey]); + } + + // ContainerEngineHandlerOutput podmanOutput = null; + using (var processInvoker = HostContext.CreateService()) + { + var redirectStandardIn = Channel.CreateUnbounded(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true }); + redirectStandardIn.Writer.TryWrite(JsonUtility.ToString(podmanInput)); + + // processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message) + // { + // ExecutionContext.Output(message.Data); + // }; + + // processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs message) + // { + // executionContext.Output(message.Data); + // if (podmanOutput == null && message.Data.IndexOf("___CONTAINER_ENGINE_HANDLER_OUTPUT___") >= 0) + // { + // try + // { + // podmanOutput = JsonUtility.FromString(message.Data.Replace("___CONTAINER_ENGINE_HANDLER_OUTPUT___", "")); + // } + // catch (Exception ex) + // { + // executionContext.Error(ex); + // } + // } + // }; + processInvoker.OutputDataReceived += OutputDataReceived; + processInvoker.ErrorDataReceived += ErrorDataReceived; + + // 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. + return await processInvoker.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work), + fileName: Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), "node12", "bin", $"node{IOUtil.ExeExtension}"), + arguments: podManHandler, + environment: environment, + requireExitCodeZero: requireExitCodeZero, + outputEncoding: Encoding.UTF8, + killProcessOnCancel: killProcessOnCancel, + redirectStandardIn: redirectStandardIn, + cancellationToken: cancellationToken); + } } - if (!string.IsNullOrEmpty(PrependPath)) + else { - // Prepend tool paths to container's PATH - var fullPath = !string.IsNullOrEmpty(Container.ContainerRuntimePath) ? $"{PrependPath}:{Container.ContainerRuntimePath}" : PrependPath; - dockerCommandArgs.Add($"-e PATH=\"{fullPath}\""); - } + var dockerManager = HostContext.GetService(); + string dockerClientPath = dockerManager.DockerPath; - // CONTAINER - dockerCommandArgs.Add($"{Container.ContainerId}"); + // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] + IList dockerCommandArgs = new List(); + dockerCommandArgs.Add($"exec"); - // COMMAND - dockerCommandArgs.Add(fileName); + // [OPTIONS] + dockerCommandArgs.Add($"-i"); + dockerCommandArgs.Add($"--workdir {workingDirectory}"); + foreach (var env in environment) + { + // e.g. -e MY_SECRET maps the value into the exec'ed process without exposing + // the value directly in the command + dockerCommandArgs.Add($"-e {env.Key}"); + } + if (!string.IsNullOrEmpty(PrependPath)) + { + // Prepend tool paths to container's PATH + var fullPath = !string.IsNullOrEmpty(Container.ContainerRuntimePath) ? $"{PrependPath}:{Container.ContainerRuntimePath}" : PrependPath; + dockerCommandArgs.Add($"-e PATH=\"{fullPath}\""); + } - // [ARG...] - dockerCommandArgs.Add(arguments); + // CONTAINER + dockerCommandArgs.Add($"{Container.ContainerId}"); - string dockerCommandArgstring = string.Join(" ", dockerCommandArgs); + // COMMAND + dockerCommandArgs.Add(fileName); - // make sure all env are using container path - foreach (var envKey in environment.Keys.ToList()) - { - environment[envKey] = this.Container.TranslateToContainerPath(environment[envKey]); - } + // [ARG...] + dockerCommandArgs.Add(arguments); - using (var processInvoker = HostContext.CreateService()) - { - processInvoker.OutputDataReceived += OutputDataReceived; - processInvoker.ErrorDataReceived += ErrorDataReceived; + string dockerCommandArgstring = string.Join(" ", dockerCommandArgs); + + // make sure all env are using container path + foreach (var envKey in environment.Keys.ToList()) + { + environment[envKey] = this.Container.TranslateToContainerPath(environment[envKey]); + } + + using (var processInvoker = HostContext.CreateService()) + { + processInvoker.OutputDataReceived += OutputDataReceived; + processInvoker.ErrorDataReceived += ErrorDataReceived; #if OS_WINDOWS // It appears that node.exe outputs UTF8 when not in TTY mode. outputEncoding = Encoding.UTF8; #else - // Let .NET choose the default. - outputEncoding = null; + // Let .NET choose the default. + outputEncoding = null; #endif - return await processInvoker.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work), - fileName: dockerClientPath, - arguments: dockerCommandArgstring, - environment: environment, - requireExitCodeZero: requireExitCodeZero, - outputEncoding: outputEncoding, - killProcessOnCancel: killProcessOnCancel, - redirectStandardIn: null, - inheritConsoleHandler: inheritConsoleHandler, - cancellationToken: cancellationToken); + return await processInvoker.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work), + fileName: dockerClientPath, + arguments: dockerCommandArgstring, + environment: environment, + requireExitCodeZero: requireExitCodeZero, + outputEncoding: outputEncoding, + killProcessOnCancel: killProcessOnCancel, + redirectStandardIn: null, + inheritConsoleHandler: inheritConsoleHandler, + cancellationToken: cancellationToken); + } } } }