b9f0f6dd5f
Co-authored-by: Copilot <copilot@github.com>
77 lines
3.1 KiB
C#
77 lines
3.1 KiB
C#
using ControlPlane.Core.Config;
|
|
using ControlPlane.Core.Interfaces;
|
|
using ControlPlane.Core.Models;
|
|
using ControlPlane.Worker.Services;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace ControlPlane.Worker.Steps;
|
|
|
|
/// <summary>
|
|
/// Final saga step — launches the clarity-server Docker container with the fully
|
|
/// enriched SagaContext (connection strings, Keycloak realm, etc. all known).
|
|
/// Runs LAST so all env vars are available at container start.
|
|
/// </summary>
|
|
public class LaunchStep(
|
|
ILogger<LaunchStep> logger,
|
|
IConfiguration config,
|
|
IOptions<ClarityInfraOptions> infraOptions,
|
|
ClarityContainerService containers) : ISagaStep
|
|
{
|
|
public string StepName => "Container Launch";
|
|
|
|
public async Task ExecuteAsync(SagaContext context, CancellationToken cancellationToken)
|
|
{
|
|
var job = context.Job;
|
|
|
|
logger.LogInformation("[{JobId}] Launching container {Env}-app-clarity-{Site}",
|
|
job.Id, job.Environment, job.SiteCode);
|
|
|
|
var containerName = await containers.StartTenantContainerAsync(
|
|
environment: job.Environment,
|
|
siteCode: job.SiteCode,
|
|
subdomain: job.Subdomain,
|
|
keycloakRealm: $"clarity-{job.Subdomain.ToLowerInvariant()}",
|
|
postgresConnectionString: context.TenantConnectionString,
|
|
vaultToken: context.VaultToken ?? ReadVaultToken(config),
|
|
jobId: job.Id,
|
|
cancellationToken: cancellationToken);
|
|
|
|
context.ContainerName = containerName;
|
|
context.TenantApiBaseUrl = infraOptions.Value.TenantPublicUrl(job.Subdomain);
|
|
|
|
logger.LogInformation("[{JobId}] Container {Name} live at {Url}",
|
|
job.Id, containerName, context.TenantApiBaseUrl);
|
|
|
|
context.Job.CompletedSteps |= CompletedSteps.InfrastructureProvisioned;
|
|
}
|
|
|
|
public async Task CompensateAsync(SagaContext context, CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(context.ContainerName)) return;
|
|
|
|
logger.LogWarning("[{JobId}] Compensating: removing container {Name}", context.Job.Id, context.ContainerName);
|
|
await containers.StopAndRemoveAsync(context.ContainerName, cancellationToken);
|
|
await containers.RemoveNginxConfigAsync(context.Job.Subdomain, cancellationToken);
|
|
}
|
|
|
|
// Reads the Vault root token from the persisted init.json on the Vault volume.
|
|
// Falls back to config["Vault:Token"] then "root" for local dev.
|
|
private static string? ReadVaultToken(IConfiguration config)
|
|
{
|
|
var keysFile = config["Vault:KeysFile"];
|
|
if (!string.IsNullOrWhiteSpace(keysFile) && File.Exists(keysFile))
|
|
{
|
|
try
|
|
{
|
|
var json = File.ReadAllText(keysFile);
|
|
using var doc = System.Text.Json.JsonDocument.Parse(json);
|
|
if (doc.RootElement.TryGetProperty("root_token", out var tok))
|
|
return tok.GetString();
|
|
}
|
|
catch { /* fall through */ }
|
|
}
|
|
|
|
return config["Vault:Token"] ?? "root";
|
|
}
|
|
}
|