304 lines
8.5 KiB
C#
304 lines
8.5 KiB
C#
using System.Diagnostics;
|
|
using System.Text;
|
|
using ZA.CoreService.ESBCertificateManager.Models;
|
|
|
|
namespace ZA.CoreService.ESBCertificateManager.Services;
|
|
|
|
public sealed class SonicContainerScanner
|
|
{
|
|
private readonly RuntimeSettings _runtimeSettings;
|
|
|
|
public SonicContainerScanner(RuntimeSettings runtimeSettings)
|
|
{
|
|
_runtimeSettings = runtimeSettings
|
|
?? throw new ArgumentNullException(nameof(runtimeSettings));
|
|
}
|
|
|
|
public async Task<ContainerScanResult> ScanAsync(
|
|
SonicSystemOption system,
|
|
string userName,
|
|
string secret,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(system);
|
|
|
|
if (string.IsNullOrWhiteSpace(userName))
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
"Der Sonic-Benutzername fehlt.");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(secret))
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
"Das Sonic-Kennwort fehlt.");
|
|
}
|
|
|
|
string javaPath =
|
|
PathResolver.ResolvePath(
|
|
_runtimeSettings.JavaExecutablePath);
|
|
|
|
string libraryPath =
|
|
PathResolver.ResolvePath(
|
|
_runtimeSettings.SonicClientLibraryPath);
|
|
|
|
string? toolsPath = ResolveToolsDirectory();
|
|
|
|
if (!File.Exists(javaPath))
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
$"Java wurde nicht gefunden: {javaPath}");
|
|
}
|
|
|
|
if (!Directory.Exists(libraryPath))
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
$"Der Sonic-JAR-Ordner wurde nicht gefunden: {libraryPath}");
|
|
}
|
|
|
|
if (toolsPath is null)
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
"SonicMfContainerTool.class wurde nicht gefunden.");
|
|
}
|
|
|
|
string[] jars =
|
|
Directory.GetFiles(
|
|
libraryPath,
|
|
"*.jar",
|
|
SearchOption.TopDirectoryOnly);
|
|
|
|
if (jars.Length == 0)
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
"Im Sonic-Client-Ordner wurden keine JAR-Dateien gefunden.");
|
|
}
|
|
|
|
string classpath =
|
|
toolsPath
|
|
+ Path.PathSeparator
|
|
+ string.Join(
|
|
Path.PathSeparator,
|
|
jars.OrderBy(
|
|
Path.GetFileName,
|
|
StringComparer.OrdinalIgnoreCase));
|
|
|
|
ProcessStartInfo startInfo = new()
|
|
{
|
|
FileName = javaPath,
|
|
WorkingDirectory = toolsPath,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
StandardOutputEncoding = Encoding.UTF8,
|
|
StandardErrorEncoding = Encoding.UTF8,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
startInfo.ArgumentList.Add("-cp");
|
|
startInfo.ArgumentList.Add(classpath);
|
|
startInfo.ArgumentList.Add("SonicMfContainerTool");
|
|
startInfo.ArgumentList.Add("list");
|
|
startInfo.ArgumentList.Add("--domain");
|
|
startInfo.ArgumentList.Add(system.DomainName);
|
|
startInfo.ArgumentList.Add("--url");
|
|
startInfo.ArgumentList.Add(system.ConnectionUrl);
|
|
startInfo.ArgumentList.Add("--user");
|
|
startInfo.ArgumentList.Add(userName.Trim());
|
|
startInfo.ArgumentList.Add("--timeout");
|
|
startInfo.ArgumentList.Add("45");
|
|
|
|
startInfo.Environment["ESB_SONIC_PASSWORD"] = secret;
|
|
|
|
using Process process = new()
|
|
{
|
|
StartInfo = startInfo
|
|
};
|
|
|
|
try
|
|
{
|
|
if (!process.Start())
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
"Der Java-Prozess konnte nicht gestartet werden.");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
$"Java konnte nicht gestartet werden: {ex.Message}");
|
|
}
|
|
|
|
Task<string> standardOutputTask =
|
|
process.StandardOutput.ReadToEndAsync();
|
|
|
|
Task<string> standardErrorTask =
|
|
process.StandardError.ReadToEndAsync();
|
|
|
|
using CancellationTokenSource timeoutSource =
|
|
CancellationTokenSource.CreateLinkedTokenSource(
|
|
cancellationToken);
|
|
|
|
timeoutSource.CancelAfter(TimeSpan.FromSeconds(50));
|
|
|
|
try
|
|
{
|
|
await process.WaitForExitAsync(timeoutSource.Token);
|
|
}
|
|
catch (OperationCanceledException)
|
|
when (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
TryKill(process);
|
|
|
|
return ContainerScanResult.Failed(
|
|
"Der Container-Scan hat das Zeitlimit überschritten.");
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
TryKill(process);
|
|
throw;
|
|
}
|
|
|
|
string standardOutput =
|
|
(await standardOutputTask).Trim();
|
|
|
|
string standardError =
|
|
(await standardErrorTask).Trim();
|
|
|
|
string combined =
|
|
string.Join(
|
|
Environment.NewLine,
|
|
new[] { standardOutput, standardError }
|
|
.Where(value => !string.IsNullOrWhiteSpace(value)));
|
|
|
|
bool success =
|
|
process.ExitCode == 0
|
|
&& combined.Contains(
|
|
"OK:ContainerListEnd",
|
|
StringComparison.OrdinalIgnoreCase)
|
|
&& !combined.Contains(
|
|
"ERROR:",
|
|
StringComparison.OrdinalIgnoreCase);
|
|
|
|
if (!success)
|
|
{
|
|
return ContainerScanResult.Failed(
|
|
ExtractError(combined)
|
|
?? $"Container-Scan fehlgeschlagen, ExitCode={process.ExitCode}.");
|
|
}
|
|
|
|
List<string> containers = [];
|
|
|
|
foreach (string line in standardOutput.Split(
|
|
['\r', '\n'],
|
|
StringSplitOptions.RemoveEmptyEntries))
|
|
{
|
|
const string prefix = "CONTAINER=";
|
|
|
|
if (!line.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string name = line[prefix.Length..].Trim();
|
|
|
|
if (name.Length > 0
|
|
&& !containers.Contains(name, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
containers.Add(name);
|
|
}
|
|
}
|
|
|
|
containers.Sort(StringComparer.OrdinalIgnoreCase);
|
|
|
|
return ContainerScanResult.Successful(containers);
|
|
}
|
|
|
|
private static string? ResolveToolsDirectory()
|
|
{
|
|
string baseDirectory = AppContext.BaseDirectory;
|
|
|
|
string[] candidates =
|
|
[
|
|
Path.Combine(baseDirectory, "Tools"),
|
|
Path.Combine(baseDirectory, "..", "..", "..", "Tools"),
|
|
Path.Combine(
|
|
Directory.GetCurrentDirectory(),
|
|
"Tools")
|
|
];
|
|
|
|
foreach (string candidate in candidates)
|
|
{
|
|
string fullPath = Path.GetFullPath(candidate);
|
|
string classFile = Path.Combine(
|
|
fullPath,
|
|
"SonicMfContainerTool.class");
|
|
|
|
if (File.Exists(classFile))
|
|
{
|
|
return fullPath;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string? ExtractError(string output)
|
|
{
|
|
foreach (string line in output.Split(
|
|
['\r', '\n'],
|
|
StringSplitOptions.RemoveEmptyEntries))
|
|
{
|
|
if (line.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return line["ERROR:".Length..].Trim();
|
|
}
|
|
}
|
|
|
|
return string.IsNullOrWhiteSpace(output) ? null : output;
|
|
}
|
|
|
|
private static void TryKill(Process process)
|
|
{
|
|
try
|
|
{
|
|
if (!process.HasExited)
|
|
{
|
|
process.Kill(entireProcessTree: true);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed class ContainerScanResult
|
|
{
|
|
public bool Success { get; init; }
|
|
|
|
public string Message { get; init; } = string.Empty;
|
|
|
|
public IReadOnlyList<string> Containers { get; init; } = [];
|
|
|
|
public static ContainerScanResult Successful(
|
|
IReadOnlyList<string> containers)
|
|
{
|
|
return new ContainerScanResult
|
|
{
|
|
Success = true,
|
|
Message = $"{containers.Count} Container gefunden.",
|
|
Containers = containers
|
|
};
|
|
}
|
|
|
|
public static ContainerScanResult Failed(string message)
|
|
{
|
|
return new ContainerScanResult
|
|
{
|
|
Success = false,
|
|
Message = message
|
|
};
|
|
}
|
|
}
|