From e0a0bfbfe7036463886c8954458035f3a70b6d17 Mon Sep 17 00:00:00 2001 From: Ethan Dennis Date: Wed, 23 Mar 2022 16:43:08 -0700 Subject: [PATCH] Execute valet from process --- .gitignore | 3 ++ Valet/App.cs | 6 +-- Valet/Interfaces/IProcessService.cs | 6 +++ Valet/Program.cs | 46 ++++++++++------ Valet/Services/DockerService.cs | 84 +++++++++++++---------------- Valet/Services/ProcessService.cs | 71 ++++++++++++++++++++++++ 6 files changed, 149 insertions(+), 67 deletions(-) create mode 100644 Valet/Interfaces/IProcessService.cs create mode 100644 Valet/Services/ProcessService.cs diff --git a/.gitignore b/.gitignore index a255f99..96eb773 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ *.user *.userosscache *.sln.docstates +.DS_STORE +output/ +dist/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/Valet/App.cs b/Valet/App.cs index 1367dd2..45577da 100644 --- a/Valet/App.cs +++ b/Valet/App.cs @@ -41,9 +41,7 @@ public class App public async Task ExecuteValetAsync(string[] args) { - Console.WriteLine(string.Join(' ', args)); - - await _dockerService.ExecuteCommandAsync($"{ValetContainerRegistry}/{ValetImage}:latest", args); - return 1; + var result = await _dockerService.ExecuteCommandAsync($"{ValetContainerRegistry}/{ValetImage}:latest", args); + return result ? 0 : 1; } } \ No newline at end of file diff --git a/Valet/Interfaces/IProcessService.cs b/Valet/Interfaces/IProcessService.cs new file mode 100644 index 0000000..c08396a --- /dev/null +++ b/Valet/Interfaces/IProcessService.cs @@ -0,0 +1,6 @@ +namespace Valet.Interfaces; + +public interface IProcessService +{ + Task RunAsync(string filename, string arguments, string? cwd = null, IEnumerable<(string, string)>? environmentVariables = null); +} \ No newline at end of file diff --git a/Valet/Program.cs b/Valet/Program.cs index 6751fb6..b39d9dc 100644 --- a/Valet/Program.cs +++ b/Valet/Program.cs @@ -1,27 +1,43 @@ // See https://aka.ms/new-console-template for more information using CommandLine; +using CommandLine.Text; using Valet; using Valet.Models; using Valet.Services; +var processService = new ProcessService(); + var app = new App( - new DockerService(), + new DockerService(processService), new AuthenticationService() ); + +var parser = new Parser(with => with.HelpWriter = null); +var parserResult = parser.ParseArguments(args); + // TODO: Utilize help menu from Valet itself -await Parser.Default.ParseArguments(args) - .MapResult( - (UpdateOptions opts) => - { - return app.UpdateValetAsync(opts.Username, opts.Password); - }, - (ExecuteOptions opts) => - { - return Task.FromResult(1); - }, - _ => - { - return app.ExecuteValetAsync(args); - }); \ No newline at end of file +await parserResult.WithNotParsedAsync(errs => + { + return app.ExecuteValetAsync(args); + }); + +await parserResult.WithParsedAsync(options => app.UpdateValetAsync(options.Username, options.Password)); + + + // (UpdateOptions opts) => app.UpdateValetAsync(opts.Username, opts.Password), + // (ExecuteOptions opts) => Task.FromResult(1), + // _ => + // { + // var helpText = new HelpText + // { + // Heading = "Tool tool usage", + // AdditionalNewLineAfterOption = false, + // AddDashesToOption = true + // }; + // + // Console.Error.WriteLine(helpText); + // + // return app.ExecuteValetAsync(args); + // }); \ No newline at end of file diff --git a/Valet/Services/DockerService.cs b/Valet/Services/DockerService.cs index 5221409..a56f6a5 100644 --- a/Valet/Services/DockerService.cs +++ b/Valet/Services/DockerService.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; +using System.Text; using Docker.DotNet; using Docker.DotNet.Models; using Valet.Interfaces; @@ -8,8 +10,10 @@ namespace Valet.Services; public class DockerService : IDockerService { private readonly DockerClient _client; + private readonly IProcessService _processService; - private readonly string[] _valetEnvVars = { + private readonly string[] _valetEnvVars = + { "GH_ACCESS_TOKEN", "GH_INSTANCE_URL", "GITHUB_ACCESS_TOKEN", "GITHUB_INSTANCE_URL", "JENKINSFILE_ACCESS_TOKEN", "JENKINS_USERNAME", "JENKINS_ACCESS_TOKEN", "JENKINS_INSTANCE_URL", "TRAVIS_CI_ACCESS_TOKEN", "TRAVIS_CI_INSTANCE_URL", "TRAVIS_CI_SOURCE_GITHUB_ACCESS_TOKEN", "TRAVIS_CI_SOURCE_GITHUB_INSTANCE_URL", "TRAVIS_CI_ORGANIZATION", @@ -18,9 +22,11 @@ public class DockerService : IDockerService "AZURE_DEVOPS_ACCESS_TOKEN", "AZURE_DEVOPS_PROJECT", "AZURE_DEVOPS_ORGANIZATION", "AZURE_DEVOPS_INSTANCE_URL", "YAML_VERBOSITY", "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "OCTOKIT_PROXY", "OCTOKIT_SSL_VERIFY_MODE", }; - - public DockerService() + + public DockerService(IProcessService processService) { + _processService = processService; + // TODO: Raise error if docker daemon not started _client = new DockerClientConfiguration() .CreateClient(); @@ -47,65 +53,47 @@ public class DockerService : IDockerService public async Task ExecuteCommandAsync(string image, params string[] arguments) { - // MSYS_NO_PATHCONV=1 docker run --rm $dockerArgs --env INSTALLATION_ID="$INSTALLATION_ID" $DOCKER_OPTIONS -v "$VALET_LOCAL_FOLDER"":/data" "$VALET_IMAGE":"$VALET_IMAGE_VERSION" "$@" - var container = await _client.Containers.CreateContainerAsync( - new CreateContainerParameters - { - Image = image, - HostConfig = new HostConfig - { - Binds = new[] { $"{Directory.GetCurrentDirectory()}:/data" }, - AutoRemove = true, - }, - Env = GetEnvironmentVariables().ToArray() - } - ).ConfigureAwait(false); + var valetArguments = new List(); + valetArguments.Add("run --rm"); + valetArguments.AddRange(GetEnvironmentVariableArguments()); + valetArguments.Add($"-v \"{Directory.GetCurrentDirectory()}\":/data"); + valetArguments.Add(image); + valetArguments.AddRange(arguments); - await _client.Containers.StartContainerAsync( - container.ID, - new ContainerStartParameters() + Debug.WriteLine(string.Join(' ', valetArguments)); + + var result = await _processService.RunAsync( + "docker", + string.Join(' ', valetArguments), + Directory.GetCurrentDirectory(), + new[] { ("MSYS_NO_PATHCONV", "1") } ); - - - return true; + + return result; } - private IEnumerable GetEnvironmentVariables() + private IEnumerable GetEnvironmentVariableArguments() { if (File.Exists(".env.local")) { - foreach (var line in File.ReadAllLines(".env.local")) - { - var parts = line.Split('=', StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length != 2) - continue; - - yield return $"{parts[0]}={parts[1]}"; - } + yield return "--env-file .env.local"; } + var installationId = Environment.GetEnvironmentVariable("INSTALLATION_ID") ?? "get_from_client"; + yield return $"--env INSTALLATION_ID={installationId}"; + foreach (var env in _valetEnvVars) { var value = Environment.GetEnvironmentVariable(env); - if (!string.IsNullOrWhiteSpace(value)) - { - var key = env; - // TODO: This can probably be cleaner - if (key.StartsWith("GH_")) - key = key.Replace("GH_", "GITHUB_"); - - yield return $"{key}={value}"; - } - } + if (string.IsNullOrWhiteSpace(value)) continue; - var installationId = Environment.GetEnvironmentVariable("INSTALLATION_ID"); - if (installationId == null) - { - installationId = "get_from_client"; - } + var key = env; + // TODO: This can probably be cleaner + if (key.StartsWith("GH_")) + key = key.Replace("GH_", "GITHUB_"); - yield return $"INSTALLATION_ID={installationId}"; + yield return $"--env {key}={value}"; + } } } \ No newline at end of file diff --git a/Valet/Services/ProcessService.cs b/Valet/Services/ProcessService.cs new file mode 100644 index 0000000..8c4e3a4 --- /dev/null +++ b/Valet/Services/ProcessService.cs @@ -0,0 +1,71 @@ +using System.Collections.Specialized; +using Valet.Interfaces; + +namespace Valet.Services; + +using System; +using System.Diagnostics; +using System.Threading.Tasks; + +public class ProcessService : IProcessService +{ + public Task RunAsync(string filename, string arguments, string? cwd = null, IEnumerable<(string, string)>? environmentVariables = null) + { + var tcs = new TaskCompletionSource(); + var startInfo = new ProcessStartInfo + { + FileName = filename, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + WorkingDirectory = cwd + }; + + if (environmentVariables != null) + { + foreach (var (key, value) in environmentVariables) + { + startInfo.EnvironmentVariables.Add(key, value); + } + } + + var process = new Process + { + StartInfo = startInfo, + EnableRaisingEvents = true + }; + + void OnProcessExited(object? sender, EventArgs args) + { + process.Exited -= OnProcessExited; + process.OutputDataReceived -= OnOutputDataReceived; + + if (process.ExitCode == 0) + { + tcs.TrySetResult(true); + } + else + { + // TODO: Verify works + var error = process.StandardError.ReadToEnd(); + tcs.TrySetException(new Exception(error)); + } + + process.Dispose(); + } + + void OnOutputDataReceived(object sender, DataReceivedEventArgs e) + { + Console.WriteLine(e.Data); + } + + process.OutputDataReceived += OnOutputDataReceived; + process.Exited += OnProcessExited; + + process.Start(); + process.BeginOutputReadLine(); + + return tcs.Task; + } +} \ No newline at end of file