using Microsoft.VisualBasic.Logging; using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace eSuite.WorkBench.Services { public class CommandsService : ICommandsService { private static readonly List containerNames = new() { "esuite.Database.Migrator", "esuite.API", "esuite.WebUI", "esuite.messageprocessor", "esuite.scheduler" }; public CommandsService() { var p = new Process { StartInfo = { FileName = "docker", Arguments = BuildLoginCommand(), RedirectStandardError = true, RedirectStandardOutput = true, CreateNoWindow = true, } }; p.Start(); p.WaitForExit(); } public event EventHandler FeedbackMessage; protected void DoFeedbackMessage(string message) { if (FeedbackMessage != null) { var feedbackEventArgs = new FeedbackEventArgs { Message = message }; FeedbackMessage(this, feedbackEventArgs); } } private static async Task PullImageAsync(string imageName, string tag) { await RunCommandAsync("docker", $"pull {imageName}:{tag}", createWindow:true, useShellExecute:true); } public async Task StartProxyContainerAsync(string tag) { if (string.IsNullOrWhiteSpace(tag)) return; DoFeedbackMessage("Starting Proxy container"); await PullImageAsync("esuite.azurecr.io/e-suite.api", tag); await RunCommandAsync("docker", $"run --name esuite.Proxy -d -p3001:80 --env SERVER_NAME=localhost --env API_URL=http://host.docker.internal:7066 --env WEBUI_URL=http://host.docker.internal:3000 esuite.azurecr.io/e-suite.proxy:{tag}"); DoFeedbackMessage("Started API container"); } public async Task StartApiContainerAsync(string tag, string databaseName) { if (string.IsNullOrWhiteSpace(tag)) return; DoFeedbackMessage("Starting API container"); await PullImageAsync("esuite.azurecr.io/e-suite.api", tag); await RunCommandAsync("docker", $"run --name esuite.API -d -p7066:80 --env BASE_URL=http://localhost:3001/ --env SQL_SERVER=host.docker.internal --env SQL_DATABASE=esuite_{databaseName} --env SQL_USER=esuite_{databaseName}ApplicationUser --env MAIL_SERVER=host.docker.internal --env RABBITMQ_HOSTNAME=host.docker.internal esuite.azurecr.io/e-suite.api:{tag}"); DoFeedbackMessage("Started API container"); } public async Task StartWebUiContainerAsync(string tag) { if (string.IsNullOrWhiteSpace(tag)) return; DoFeedbackMessage("Starting WebUI container"); await PullImageAsync("esuite.azurecr.io/e-suite.webui", tag); await RunCommandAsync("docker", $"run --name esuite.WebUI -d -p3000:80 --env NODE_ENV=production --env API_URL=http://localhost:3001/api esuite.azurecr.io/e-suite.webui:{tag}"); DoFeedbackMessage("Started WebUI container"); } public async Task StartDatabaseMigratorContainerAsync(string tag, string databaseName) { if (string.IsNullOrWhiteSpace(tag)) return; DoFeedbackMessage("Starting database migrator container"); await PullImageAsync("esuite.azurecr.io/esuite.database.migrator", tag); await RunCommandAsync("docker", $"run --name esuite.Database.Migrator --rm --env SQL_SERVER=host.docker.internal --env SQL_DATABASE=esuite_{databaseName} --env SQL_USER={Properties.Settings.Default.SqlServerAdminLogin} --env SQL_PASSWORD={Properties.Settings.Default.SqlServerAdminPassword} --env SQL_APPLICATION_USER=esuite_{databaseName}ApplicationUser esuite.azurecr.io/e-suite.database.migrator:{tag}"); DoFeedbackMessage("Started database migrator container"); } public async Task StartRabbitMQContainerAsync() { DoFeedbackMessage("Starting RabbitMQ"); await PullImageAsync("rabbitmq", "management"); await RunCommandAsync("docker", $"run --name RabbitMQ -d -p8080:15672 -p5672:5672 --restart always rabbitmq:management"); DoFeedbackMessage("Started RabbitMQ"); } public async Task StartSchedulerContainerAsync(string tag, string databaseName) { if (string.IsNullOrWhiteSpace(tag)) return; DoFeedbackMessage("Starting e-suite.scheduler"); await PullImageAsync("esuite.azurecr.io/e-suite.scheduler", tag); await RunCommandAsync("docker", $"run --name esuite.scheduler -d --env SQL_SERVER=host.docker.internal --env SQL_DATABASE=esuite_{databaseName} --env SQL_USER=esuite_{databaseName}ApplicationUser --env RABBITMQ_HOSTNAME=host.docker.internal esuite.azurecr.io/e-suite.scheduler:{tag}"); DoFeedbackMessage("Started e-suite.scheduler"); } public async Task StartMessageProcessorContainerAsync(string tag, string databaseName) { if (string.IsNullOrWhiteSpace(tag)) return; DoFeedbackMessage("Starting esuite.messageprocessor"); await PullImageAsync("esuite.azurecr.io/e-suite.messageprocessor", tag); await RunCommandAsync("docker", $"run --name esuite.messageprocessor -d --env SQL_SERVER=host.docker.internal --env SQL_DATABASE=esuite_{databaseName} --env SQL_USER=esuite_{databaseName}ApplicationUser --env RABBITMQ_HOSTNAME=host.docker.internal esuite.azurecr.io/e-suite.messageprocessor:{tag}"); DoFeedbackMessage("Started esuite.messageprocessor"); } public async Task IsAnyContainerRunningAsync() { var runningContainers = (await RunCommandAsync("docker", "container ls -a --format \"{{.Names}}\"")).StdOut; return containerNames.Any(x => runningContainers.Contains(x)); } public bool IsAnyContainerRunning() { var startInfo = new ProcessStartInfo { FileName = "docker", Arguments = "container ls -a --format \"{{.Names}}\"", CreateNoWindow = true, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, }; var process = Process.Start(startInfo); var outputBuilder = new StringBuilder(); process.OutputDataReceived += (sender, e) => { if (e.Data == null) { return; } outputBuilder.AppendLine(e.Data); }; process.BeginOutputReadLine(); process.WaitForExit(); var runningContainers = outputBuilder.ToString(); return containerNames.Any(x => runningContainers.Contains(x)); } public async Task ConnectToContainerRegistry() { var loginCommand = BuildLoginCommand(); await RunCommandAsync("docker", loginCommand); } private static string BuildLoginCommand() { return $"login -u {ConfigurationManager.AppSettings["AzureContainerRegistryUserName"]} -p {ConfigurationManager.AppSettings["AzureContainerRegistryPassword"]} esuite.azurecr.io"; } public async Task LaunchSwagger() { await RunCommandAsync("http://localhost:3001/swagger", useShellExecute: true, launchAndForget: true); } public async Task LaunchWebUi() { await RunCommandAsync("http://localhost:3001/", useShellExecute: true, launchAndForget: true); } public async Task LaunchHealthZ() { await RunCommandAsync("http://localhost:3001/healthz", useShellExecute: true, launchAndForget: true); } public async Task StopContainersAsync() { //Note RabbitMQ is left running, this is to make it easy for developers not to have to run the command. await RunCommandAsync("docker", "stop -t 2 esuite.API esuite.WebUI esuite.scheduler esuite.messageprocessor esuite.Proxy RabbitMQ"); await RunCommandAsync("docker", "rm esuite.API esuite.WebUI esuite.scheduler esuite.messageprocessor esuite.Proxy RabbitMQ"); } private static async Task RunCommandAsync(string command, string arguments = null, TimeSpan? timeout = null, bool createWindow = false, bool useShellExecute = false, bool launchAndForget = false) { if (timeout == null) { timeout = TimeSpan.FromSeconds(300); } var startInfo = new ProcessStartInfo { Arguments = arguments ?? string.Empty, CreateNoWindow = !createWindow, FileName = command, RedirectStandardError = !useShellExecute, RedirectStandardOutput = !useShellExecute, UseShellExecute = useShellExecute }; var process = Process.Start(startInfo); var errorBuilder = new StringBuilder(); var outputBuilder = new StringBuilder(); process.ErrorDataReceived += (sender, e) => { if (e.Data == null) { return; } lock (errorBuilder) { errorBuilder.AppendLine(e.Data); } }; process.OutputDataReceived += (sender, e) => { if (e.Data == null) { return; } lock (outputBuilder) { outputBuilder.AppendLine(e.Data); } }; if (!useShellExecute) { process.BeginOutputReadLine(); process.BeginErrorReadLine(); } if (!launchAndForget) { await Task.Run(() => process.WaitForExit((int)timeout.Value.TotalMilliseconds)); return new RunCommandResult { ExitCode = process.ExitCode, StdErr = errorBuilder.ToString(), StdOut = outputBuilder.ToString() }; } return new RunCommandResult { ExitCode = 0, StdErr = string.Empty, StdOut = string.Empty }; } private class RunCommandResult { public int ExitCode { get; set; } public string StdErr { get; set; } public string StdOut { get; set; } public bool Success => ExitCode == 0; } } }