Files
123123/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs
T

269 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using ZA.CoreService.ESBCertificateManager.Models;
namespace ZA.CoreService.ESBCertificateManager.Services;
/// <summary>
/// Fragt konfigurierte Sonic-Verbindungen nach Containern ab
/// und merged Remote-Treffer mit KnownContainers / Sample-Zielen.
/// </summary>
public sealed class SonicContainerDiscovery
{
private readonly IReadOnlyList<SonicConnection> _connections;
public SonicContainerDiscovery(IReadOnlyList<SonicConnection> connections)
{
_connections = connections;
}
public bool HasConnections => _connections.Count > 0;
public IReadOnlyList<SonicConnection> Connections => _connections;
public async Task<SonicDiscoveryResult> DiscoverAsync(
string connectionName,
IReadOnlyList<DeploymentTarget> knownTargets,
CancellationToken cancellationToken = default)
{
SonicConnection? connection = _connections
.FirstOrDefault(c => string.Equals(c.Name, connectionName, StringComparison.OrdinalIgnoreCase));
if (connection is null)
{
return SonicDiscoveryResult.Failed(connectionName,
$"Sonic-Verbindung '{connectionName}' ist nicht in AppSettings konfiguriert.");
}
using SonicManagementClient client = new(connection);
(bool reachable, string? pingError, string? resolvedPath) = await client.CheckConnectionAsync(cancellationToken);
List<string> diagnostics = [];
List<string> remoteContainers = [];
bool listOk = false;
string? listError = null;
if (!reachable)
{
// Trotzdem KnownContainers nutzen Neustart kann über SonicBin (stop/startcontainer) gehen.
diagnostics.Add($"Management-Ping: {pingError}");
if (connection.KnownContainers.Count == 0
&& !knownTargets.Any(t =>
string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(t.ContainerName)))
{
return SonicDiscoveryResult.Failed(connectionName,
$"Management-Konsole nicht erreichbar: {pingError}");
}
}
else
{
(listOk, IReadOnlyList<string> remoteNames, listError) =
await client.GetContainersAsync(cancellationToken);
foreach (string line in remoteNames)
{
if (line.StartsWith("WARN:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("INFO:", StringComparison.OrdinalIgnoreCase))
{
diagnostics.Add(line);
continue;
}
remoteContainers.Add(line);
}
if (!listOk)
{
diagnostics.Add($"Listen-Fehler: {listError}");
remoteContainers = [];
}
}
// Fallback: explizit in appsettings hinterlegte Container
List<string> mergedNames = MergeContainerNames(
remoteContainers,
connection.KnownContainers,
knownTargets
.Where(t => string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase))
.Select(t => t.ContainerName)
.Where(n => !string.IsNullOrWhiteSpace(n)));
List<DeploymentTarget> discovered = BuildTargets(connection, mergedNames, knownTargets);
string? hint = BuildHint(connection, remoteContainers.Count, connection.KnownContainers.Count, diagnostics, listOk, listError);
return new SonicDiscoveryResult
{
ConnectionName = connectionName,
DomainName = connection.DomainName,
Success = true,
ErrorMessage = hint,
DiscoveredTargets = discovered,
RawContainerNames = remoteContainers,
ResolvedPath = resolvedPath
};
}
private static List<string> MergeContainerNames(
IEnumerable<string> remote,
IEnumerable<string> knownConfigured,
IEnumerable<string> knownFromTargets)
{
List<string> result = [];
foreach (string name in remote.Concat(knownConfigured).Concat(knownFromTargets))
{
if (string.IsNullOrWhiteSpace(name))
{
continue;
}
if (!result.Any(existing => ContainerNamesMatch(existing, name, domain: null)))
{
result.Add(name.Trim());
}
}
return result;
}
private static string? BuildHint(
SonicConnection connection,
int remoteCount,
int knownConfigCount,
List<string> diagnostics,
bool listOk,
string? listError)
{
List<string> parts = [];
if (!listOk && !string.IsNullOrWhiteSpace(listError))
{
parts.Add(listError!);
}
if (remoteCount == 0)
{
parts.Add(
$"Remote hat 0 Container geliefert (Domain '{connection.DomainName}', SonicHome='{connection.SonicHome}').");
if (knownConfigCount > 0)
{
parts.Add($"Fallback: {knownConfigCount} KnownContainers aus appsettings.");
}
else
{
parts.Add("Tipp: KnownContainers in appsettings setzen oder SonicHome korrigieren.");
}
}
foreach (string d in diagnostics.Take(3))
{
parts.Add(d);
}
return parts.Count == 0 ? null : string.Join(" ", parts);
}
private static List<DeploymentTarget> BuildTargets(
SonicConnection connection,
IReadOnlyList<string> containerNames,
IReadOnlyList<DeploymentTarget> knownTargets)
{
List<DeploymentTarget> result = [];
int syntheticId = -1;
foreach (string containerName in containerNames)
{
DeploymentTarget? existing = knownTargets.FirstOrDefault(t =>
string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)
&& ContainerNamesMatch(t.ContainerName, containerName, connection.DomainName));
if (existing is not null)
{
if (!result.Any(r => r.Id == existing.Id))
{
result.Add(existing);
}
}
else
{
result.Add(new DeploymentTarget
{
Id = syntheticId--,
Name = $"{connection.Name} / {containerName}",
Environment = connection.DomainName,
IsActive = true,
TargetDirectory = string.Empty,
CertificateFileName = string.Empty,
ContainerName = containerName,
RestartType = RestartType.SonicContainer,
SonicConnectionName = connection.Name,
SortOrder = result.Count * 10
});
}
}
foreach (DeploymentTarget known in knownTargets)
{
if (!string.Equals(known.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase))
{
continue;
}
bool alreadyAdded = result.Any(r =>
ContainerNamesMatch(r.ContainerName, known.ContainerName, connection.DomainName)
|| r.Id == known.Id);
if (!alreadyAdded)
{
result.Add(known);
}
}
return result;
}
internal static bool ContainerNamesMatch(string? a, string? b, string? domain)
{
if (string.IsNullOrWhiteSpace(a) || string.IsNullOrWhiteSpace(b))
{
return false;
}
if (string.Equals(a, b, StringComparison.OrdinalIgnoreCase))
{
return true;
}
string aShort = StripDomainPrefix(a, domain);
string bShort = StripDomainPrefix(b, domain);
return string.Equals(aShort, bShort, StringComparison.OrdinalIgnoreCase);
}
private static string StripDomainPrefix(string name, string? domain)
{
if (!string.IsNullOrWhiteSpace(domain)
&& name.StartsWith(domain + ".", StringComparison.OrdinalIgnoreCase))
{
return name[(domain.Length + 1)..];
}
int dot = name.IndexOf('.');
return dot > 0 ? name[(dot + 1)..] : name;
}
}
public sealed class SonicDiscoveryResult
{
public required string ConnectionName { get; init; }
public string DomainName { get; init; } = string.Empty;
public bool Success { get; init; }
public string? ErrorMessage { get; init; }
public string? ResolvedPath { get; init; }
public IReadOnlyList<DeploymentTarget> DiscoveredTargets { get; init; } = [];
public IReadOnlyList<string> RawContainerNames { get; init; } = [];
public static SonicDiscoveryResult Failed(string connectionName, string error)
=> new() { ConnectionName = connectionName, Success = false, ErrorMessage = error };
}