diff --git a/src/Runner.Common/ExtensionManager.cs b/src/Runner.Common/ExtensionManager.cs index c53fc3647..9b7171c73 100644 --- a/src/Runner.Common/ExtensionManager.cs +++ b/src/Runner.Common/ExtensionManager.cs @@ -56,6 +56,10 @@ namespace GitHub.Runner.Common Add(extensions, "GitHub.Runner.Worker.EndGroupCommandExtension, Runner.Worker"); Add(extensions, "GitHub.Runner.Worker.EchoCommandExtension, Runner.Worker"); break; + case "GitHub.Runner.Worker.IFileCommandExtension": + Add(extensions, "GitHub.Runner.Worker.AddPathFileCommand, Runner.Worker"); + Add(extensions, "GitHub.Runner.Worker.SetEnvFileCommand, Runner.Worker"); + break; default: // This should never happen. throw new NotSupportedException($"Unexpected extension type: '{typeof(T).FullName}'"); diff --git a/src/Runner.Worker/ActionRunner.cs b/src/Runner.Worker/ActionRunner.cs index 8a12b9561..f946573e4 100644 --- a/src/Runner.Worker/ActionRunner.cs +++ b/src/Runner.Worker/ActionRunner.cs @@ -145,6 +145,12 @@ namespace GitHub.Runner.Worker stepHost = containerStepHost; } + // Setup File Command Manager + var fileCommandManager = HostContext.CreateService(); + // Container Action Handler will handle the conversion for Container Actions + var container = handlerData.ExecutionType == ActionExecutionType.Container ? null : ExecutionContext.Global.Container; + fileCommandManager.InitializeFiles(ExecutionContext, container); + // Load the inputs. ExecutionContext.Debug("Loading inputs"); var templateEvaluator = ExecutionContext.ToPipelineTemplateEvaluator(); @@ -239,6 +245,8 @@ namespace GitHub.Runner.Worker // Run the task. await handler.RunAsync(Stage); + fileCommandManager.TryProcessFiles(ExecutionContext, ExecutionContext.Global.Container); + } public bool TryEvaluateDisplayName(DictionaryContextData contextData, IExecutionContext context) diff --git a/src/Runner.Worker/FileCommandManager.cs b/src/Runner.Worker/FileCommandManager.cs new file mode 100644 index 000000000..5fdcb2377 --- /dev/null +++ b/src/Runner.Worker/FileCommandManager.cs @@ -0,0 +1,140 @@ +using GitHub.Runner.Worker.Container; +using System; +using System.Text; +using System.IO; +using GitHub.Runner.Common; +using GitHub.Runner.Sdk; +using System.Collections; +using System.Collections.Generic; + +namespace GitHub.Runner.Worker +{ + [ServiceLocator(Default = typeof(FileCommandManager))] + public interface IFileCommandManager : IRunnerService + { + void InitializeFiles(IExecutionContext context, ContainerInfo container); + void TryProcessFiles(IExecutionContext context, ContainerInfo container); + + } + + public sealed class FileCommandManager : RunnerService, IFileCommandManager + { + private const string _folderName = "_runner_file_commands"; + private List _commandExtensions; + private string _fileSuffix = String.Empty; + private string _fileCommandDirectory; + private Tracing _trace; + + public override void Initialize(IHostContext hostContext) + { + base.Initialize(hostContext); + _trace = HostContext.GetTrace(nameof(FileCommandManager)); + + _fileCommandDirectory = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Temp), _folderName); + if (!Directory.Exists(_fileCommandDirectory)) + { + Directory.CreateDirectory(_fileCommandDirectory); + } + + var extensionManager = hostContext.GetService(); + _commandExtensions = extensionManager.GetExtensions() ?? new List(); + + } + + public void InitializeFiles(IExecutionContext context, ContainerInfo container) + { + var oldSuffix = _fileSuffix; + _fileSuffix = Guid.NewGuid().ToString(); + foreach (var fileCommand in _commandExtensions) + { + var oldPath = Path.Combine(_fileCommandDirectory, fileCommand.FileName + oldSuffix); + if (oldSuffix != String.Empty && File.Exists(oldPath)) + { + TryDeleteFile(oldPath); + } + + var newPath = Path.Combine(_fileCommandDirectory, fileCommand.FileName + _fileSuffix); + TryDeleteFile(newPath); + File.Create(newPath).Dispose(); + + var pathToSet = container != null ? container.TranslateToContainerPath(newPath) : newPath; + context.SetGitHubContext(fileCommand.ContextName, pathToSet); + } + } + + public void TryProcessFiles(IExecutionContext context, ContainerInfo container) + { + foreach (var fileCommand in _commandExtensions) + { + fileCommand.ProcessCommand(context, Path.Combine(_fileCommandDirectory, fileCommand.FileName + _fileSuffix),container); + } + } + + private bool TryDeleteFile(string path) + { + if (!File.Exists(path)) + { + return true; + } + try + { + File.Delete(path); + } + catch (Exception e) + { + _trace.Warning($"Unable to delete file {path} for reason: {e.ToString()}"); + return false; + } + return true; + } + } + + public interface IFileCommandExtension : IExtension + { + string ContextName { get; } + string FileName { get; } + + void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container); + } + + public sealed class AddPathFileCommand : RunnerService, IFileCommandExtension + { + public string ContextName => "path"; + public string FileName => "add_path_"; + + public Type ExtensionType => typeof(IFileCommandExtension); + + public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container) + { + if (File.Exists(filePath)) + { + var lines = File.ReadAllLines(filePath, Encoding.UTF8); + foreach(var line in lines) + { + if (line == string.Empty) + { + continue; + } + context.Global.PrependPath.RemoveAll(x => string.Equals(x, line, StringComparison.CurrentCulture)); + context.Global.PrependPath.Add(line); + } + } + } + } + + public sealed class SetEnvFileCommand : RunnerService, IFileCommandExtension + { + public string ContextName => "env"; + public string FileName => "set_env_"; + + public Type ExtensionType => typeof(IFileCommandExtension); + + public void ProcessCommand(IExecutionContext context, string filePath, ContainerInfo container) + { + if (File.Exists(filePath)) + { + // TODO Process this file + } + } + } +} diff --git a/src/Runner.Worker/GitHubContext.cs b/src/Runner.Worker/GitHubContext.cs index 1ecca19f0..0e4b6d472 100644 --- a/src/Runner.Worker/GitHubContext.cs +++ b/src/Runner.Worker/GitHubContext.cs @@ -13,11 +13,13 @@ namespace GitHub.Runner.Worker "actor", "api_url", "base_ref", + "env", "event_name", "event_path", "graphql_url", "head_ref", "job", + "path", "ref", "repository", "repository_owner", diff --git a/src/Runner.Worker/Handlers/ContainerActionHandler.cs b/src/Runner.Worker/Handlers/ContainerActionHandler.cs index 6e8624510..b25383459 100644 --- a/src/Runner.Worker/Handlers/ContainerActionHandler.cs +++ b/src/Runner.Worker/Handlers/ContainerActionHandler.cs @@ -161,16 +161,21 @@ namespace GitHub.Runner.Worker.Handlers 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"; diff --git a/src/Test/L0/ServiceInterfacesL0.cs b/src/Test/L0/ServiceInterfacesL0.cs index 63c01b17d..b3535e40a 100644 --- a/src/Test/L0/ServiceInterfacesL0.cs +++ b/src/Test/L0/ServiceInterfacesL0.cs @@ -60,6 +60,7 @@ namespace GitHub.Runner.Common.Tests { typeof(IActionCommandExtension), typeof(IExecutionContext), + typeof(IFileCommandExtension), typeof(IHandler), typeof(IJobExtension), typeof(IStep), diff --git a/src/Test/L0/Worker/ActionRunnerL0.cs b/src/Test/L0/Worker/ActionRunnerL0.cs index c5c6426e4..371a6253b 100644 --- a/src/Test/L0/Worker/ActionRunnerL0.cs +++ b/src/Test/L0/Worker/ActionRunnerL0.cs @@ -32,6 +32,8 @@ namespace GitHub.Runner.Common.Tests.Worker private TestHostContext _hc; private ActionRunner _actionRunner; private IActionManifestManager _actionManifestManager; + private Mock _fileCommandManager; + private DictionaryContextData _context = new DictionaryContextData(); [Fact] @@ -362,6 +364,7 @@ namespace GitHub.Runner.Common.Tests.Worker _handlerFactory = new Mock(); _defaultStepHost = new Mock(); _actionManifestManager = new ActionManifestManager(); + _fileCommandManager = new Mock(); _actionManifestManager.Initialize(_hc); var githubContext = new GitHubContext(); @@ -394,6 +397,8 @@ namespace GitHub.Runner.Common.Tests.Worker _hc.EnqueueInstance(_defaultStepHost.Object); + _hc.EnqueueInstance(_fileCommandManager.Object); + // Instance to test. _actionRunner = new ActionRunner(); _actionRunner.Initialize(_hc);