Backend/e-suite.WorkBench/eSuite.WorkBench/Services/CommandsService.cs
2026-01-20 21:50:10 +00:00

286 lines
11 KiB
C#

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<string> 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<FeedbackEventArgs> 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<bool> 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<RunCommandResult> 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;
}
}
}