diff --git a/ZA.CoreService.ESBCertificateManager/Form1.cs b/ZA.CoreService.ESBCertificateManager/Form1.cs index 54195ad..8b29338 100644 --- a/ZA.CoreService.ESBCertificateManager/Form1.cs +++ b/ZA.CoreService.ESBCertificateManager/Form1.cs @@ -1829,23 +1829,41 @@ namespace ZA.CoreService.ESBCertificateManager return; } - // Grid mit entdeckten Containern befüllen + // Grid mit entdeckten + konfigurierten Containern befüllen _loadedTargets = result.DiscoveredTargets; BindTargetsToGrid(result.DiscoveredTargets); - int configured = result.DiscoveredTargets.Count(t => !string.IsNullOrWhiteSpace(t.TargetDirectory)); - int discovered = result.RawContainerNames.Count; + int total = result.DiscoveredTargets.Count; + int remote = result.RawContainerNames.Count; + string domain = string.IsNullOrWhiteSpace(result.DomainName) + ? selectedConnection + : result.DomainName; - lblSonicStatus.Text = discovered > 0 - ? $"{discovered} Container gefunden, {configured} konfiguriert" - : $"Verbindung ok – keine Container in Domain '{selectedConnection}'"; - lblSonicStatus.ForeColor = discovered > 0 ? GreenColor : GoldColor; + if (total == 0) + { + lblSonicStatus.Text = $"Verb. ok – 0 Container in Domain '{domain}'"; + lblSonicStatus.ForeColor = GoldColor; + SetStatus( + result.ErrorMessage + ?? $"Sonic Domain '{domain}': keine Container. KnownContainers/SonicHome in appsettings prüfen.", + isError: true); + } + else if (remote == 0) + { + lblSonicStatus.Text = $"Verb. ok – Domain '{domain}': 0 remote, {total} aus Config"; + lblSonicStatus.ForeColor = GoldColor; + SetStatus( + $"Domain '{domain}': Remote leer – {total} Ziel(e) aus appsettings/KnownContainers. " + + (result.ErrorMessage ?? string.Empty), + isError: false); + } + else + { + lblSonicStatus.Text = $"Domain '{domain}': {remote} remote, {total} gesamt"; + lblSonicStatus.ForeColor = GreenColor; + SetStatus($"Sonic Domain '{domain}': {total} Ziel(e) geladen ({remote} remote).", isError: false); + } - SetStatus( - $"Sonic {selectedConnection}: {discovered} Container geladen.", - isError: false); - - // Nicht-konfigurierte Ziele visuell markieren MarkUnconfiguredRows(); } catch (OperationCanceledException) diff --git a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs index 227da4b..8dc20b0 100644 --- a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs +++ b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs @@ -39,6 +39,12 @@ public sealed class SonicConnection /// public string SonicHome { get; init; } = @"C:\Sonic\MQ10.0"; + /// + /// Bekannte Container-Namen dieser Domain (Fallback, wenn Remote-Discovery nichts findet). + /// z.B. [ "ESB", "DomainManager" ] oder vollqualifiziert [ "proalpha-test.ESB" ]. + /// + public List KnownContainers { get; init; } = []; + // --------------------------------------------------------------- // HTTP REST API (ManagementMode = HttpApi) // --------------------------------------------------------------- @@ -127,21 +133,63 @@ public sealed class SonicConnection """ $ErrorActionPreference = 'Continue' $sonicHome = '{sonicHome}' - $names = @() + $domain = '{domain}' + $names = New-Object System.Collections.Generic.List[string] - # Windows-Dienste mit Sonic/ESB im Namen - $names += @(Get-Service -ErrorAction SilentlyContinue | - Where-Object { $_.DisplayName -match 'Sonic|ESB|MQ' -or $_.Name -match 'Sonic|ESB|MQ' } | - Select-Object -ExpandProperty Name) - - # Container-Cache-Ordner unter SonicHome (Domain.Container.cache) - if (Test-Path -LiteralPath $sonicHome) { - $names += @(Get-ChildItem -LiteralPath $sonicHome -Directory -ErrorAction SilentlyContinue | - Where-Object { $_.Name -like '*.cache' } | - ForEach-Object { $_.Name -replace '\.cache$', '' }) + function Add-Name([string]$n) { + if ([string]::IsNullOrWhiteSpace($n)) { return } + $n = $n.Trim() + if (-not $names.Contains($n)) { [void]$names.Add($n) } } - $names | Where-Object { $_ } | Sort-Object -Unique + if (-not (Test-Path -LiteralPath $sonicHome)) { + Write-Output "WARN:SonicHomeNichtGefunden:$sonicHome" + } + else { + Write-Output "INFO:SonicHomeOk:$sonicHome" + + # *.cache Ordner (Domain.Container.cache) – 1–2 Ebenen + Get-ChildItem -LiteralPath $sonicHome -Directory -ErrorAction SilentlyContinue | + ForEach-Object { + if ($_.Name -like '*.cache') { + Add-Name ($_.Name -replace '\.cache$', '') + } + Get-ChildItem -LiteralPath $_.FullName -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like '*.cache' } | + ForEach-Object { Add-Name ($_.Name -replace '\.cache$', '') } + } + + # container.xml / Containers-Verzeichnisse + Get-ChildItem -LiteralPath $sonicHome -Recurse -Filter 'container.xml' -File -ErrorAction SilentlyContinue | + Select-Object -First 40 | + ForEach-Object { + $parent = Split-Path $_.DirectoryName -Leaf + Add-Name $parent + } + } + + # Windows-Dienste + Get-Service -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -match 'Sonic|ESB|MQ|Aurea' -or $_.Name -match 'Sonic|ESB|MQ|Aurea' } | + ForEach-Object { + Add-Name $_.Name + Add-Name $_.DisplayName + } + + # Prozess-Hinweise (laufende Container) + Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | + Where-Object { $_.CommandLine -match 'sonic|mf\.framework|container\.xml' } | + ForEach-Object { + if ($_.CommandLine -match '([A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+)\.cache') { + Add-Name $Matches[1] + } + } + + if ($names.Count -eq 0 -and -not [string]::IsNullOrWhiteSpace($domain)) { + Write-Output "WARN:KeineContainerGefunden Domain=$domain SonicHome=$sonicHome" + } + + $names | Sort-Object -Unique """; } diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs index a2e8077..edcdb81 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicContainerDiscovery.cs @@ -3,8 +3,8 @@ using ZA.CoreService.ESBCertificateManager.Models; namespace ZA.CoreService.ESBCertificateManager.Services; /// -/// Fragt alle konfigurierten Sonic-Management-Verbindungen nach ihren Containern -/// und gleicht die gefundenen Container mit den bereits konfigurierten Deployment-Zielen ab. +/// Fragt konfigurierte Sonic-Verbindungen nach Containern ab +/// und merged Remote-Treffer mit KnownContainers / Sample-Zielen. /// public sealed class SonicContainerDiscovery { @@ -18,10 +18,6 @@ public sealed class SonicContainerDiscovery public bool HasConnections => _connections.Count > 0; public IReadOnlyList Connections => _connections; - /// - /// Verbindet sich mit der angegebenen Sonic-Instanz, liest die Container-Liste - /// und reichert sie mit konfigurierten Ziel-Metadaten an. - /// public async Task DiscoverAsync( string connectionName, IReadOnlyList knownTargets, @@ -45,28 +41,116 @@ public sealed class SonicContainerDiscovery $"Management-Konsole nicht erreichbar: {pingError}"); } - (bool listOk, IReadOnlyList containerNames, string? listError) = + (bool listOk, IReadOnlyList remoteNames, string? listError) = await client.GetContainersAsync(cancellationToken); + List diagnostics = []; + List remoteContainers = []; + + 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) { - // Fallback: CheckConnection erfolgreich, aber /containers nicht gefunden – - // dies kann passieren wenn die API-Pfade abweichen. Container-Namen sind dann leer. - containerNames = []; + diagnostics.Add($"Listen-Fehler: {listError}"); + remoteContainers = []; } - List discovered = BuildTargets(connection, containerNames, knownTargets); + // Fallback: explizit in appsettings hinterlegte Container + List 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 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 = listOk ? null : $"Container-Liste konnte nicht geladen werden: {listError}", + ErrorMessage = hint, DiscoveredTargets = discovered, - RawContainerNames = containerNames + RawContainerNames = remoteContainers, + ResolvedPath = resolvedPath }; } + private static List MergeContainerNames( + IEnumerable remote, + IEnumerable knownConfigured, + IEnumerable knownFromTargets) + { + List 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 diagnostics, + bool listOk, + string? listError) + { + List 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 BuildTargets( SonicConnection connection, IReadOnlyList containerNames, @@ -77,19 +161,19 @@ public sealed class SonicContainerDiscovery foreach (string containerName in containerNames) { - // Bekanntes, voll-konfiguriertes Ziel suchen (nach ContainerName + SonicConnectionName) DeploymentTarget? existing = knownTargets.FirstOrDefault(t => - string.Equals(t.ContainerName, containerName, StringComparison.OrdinalIgnoreCase) && - string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)); + string.Equals(t.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase) + && ContainerNamesMatch(t.ContainerName, containerName, connection.DomainName)); if (existing is not null) { - result.Add(existing); + if (!result.Any(r => r.Id == existing.Id)) + { + result.Add(existing); + } } else { - // Minimales Ziel aus der Discovery erzeugen. - // TargetDirectory/CertificateFileName sind leer → Preflight-Validator zeigt Warnung. result.Add(new DeploymentTarget { Id = syntheticId--, @@ -106,15 +190,16 @@ public sealed class SonicContainerDiscovery } } - // Konfigurierte Ziele dieser Verbindung, die NICHT in der Discovery aufgetaucht sind, - // trotzdem anzeigen (könnten offline / gestoppt sein). foreach (DeploymentTarget known in knownTargets) { if (!string.Equals(known.SonicConnectionName, connection.Name, StringComparison.OrdinalIgnoreCase)) + { continue; + } bool alreadyAdded = result.Any(r => - string.Equals(r.ContainerName, known.ContainerName, StringComparison.OrdinalIgnoreCase)); + ContainerNamesMatch(r.ContainerName, known.ContainerName, connection.DomainName) + || r.Id == known.Id); if (!alreadyAdded) { @@ -124,13 +209,44 @@ public sealed class SonicContainerDiscovery 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 DiscoveredTargets { get; init; } = []; public IReadOnlyList RawContainerNames { get; init; } = []; diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs index 717b34b..23e0f2d 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs @@ -185,9 +185,11 @@ public sealed class SonicManagementClient : IDisposable List names = (output ?? string.Empty) .Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - .Where(l => l.Length > 0 && !l.StartsWith("WARNING", StringComparison.OrdinalIgnoreCase)) + .Where(l => l.Length > 0) + .Where(l => !l.StartsWith("WARNING:", StringComparison.OrdinalIgnoreCase)) .ToList(); + // INFO:/WARN:-Zeilen aus dem Script bewusst durchreichen (Diagnose in Discovery) return (true, names, null); } diff --git a/ZA.CoreService.ESBCertificateManager/appsettings.json b/ZA.CoreService.ESBCertificateManager/appsettings.json index 71e2704..f3c1560 100644 --- a/ZA.CoreService.ESBCertificateManager/appsettings.json +++ b/ZA.CoreService.ESBCertificateManager/appsettings.json @@ -14,6 +14,10 @@ "Password": "Administrator", "ManagementMode": "WinRm", "SonicHome": "C:\\Sonic\\MQ10.0", + "KnownContainers": [ + "ESB", + "DomainManager" + ], "WinRmPort": 5985, "TimeoutSeconds": 90, "PostRestartDelaySeconds": 15,