audit work, doctors work

This commit is contained in:
master
2026-01-12 23:39:07 +02:00
parent 9330c64349
commit b8868a5f13
80 changed files with 12659 additions and 87 deletions

View File

@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.Cli.Extensions;
using StellaOps.Doctor.Engine;
using StellaOps.Doctor.Export;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Output;
@@ -26,6 +27,7 @@ internal static class DoctorCommandGroup
// Sub-commands
doctor.Add(BuildRunCommand(services, verboseOption, cancellationToken));
doctor.Add(BuildListCommand(services, verboseOption, cancellationToken));
doctor.Add(BuildExportCommand(services, verboseOption, cancellationToken));
return doctor;
}
@@ -162,6 +164,141 @@ internal static class DoctorCommandGroup
return list;
}
private static Command BuildExportCommand(
IServiceProvider services,
Option<bool> verboseOption,
CancellationToken cancellationToken)
{
var outputOption = new Option<string>("--output", new[] { "-o" })
{
Description = "Output ZIP file path",
IsRequired = true
};
var includeLogsOption = new Option<bool>("--include-logs")
{
Description = "Include recent log files in the bundle (default: true)"
};
includeLogsOption.SetDefaultValue(true);
var logDurationOption = new Option<string?>("--log-duration")
{
Description = "Duration of logs to include (e.g., 1h, 4h, 24h). Default: 1h"
};
var noConfigOption = new Option<bool>("--no-config")
{
Description = "Exclude configuration from the bundle"
};
var export = new Command("export", "Generate diagnostic bundle for support")
{
outputOption,
includeLogsOption,
logDurationOption,
noConfigOption,
verboseOption
};
export.SetAction(async (parseResult, ct) =>
{
var output = parseResult.GetValue(outputOption)!;
var includeLogs = parseResult.GetValue(includeLogsOption);
var logDuration = parseResult.GetValue(logDurationOption);
var noConfig = parseResult.GetValue(noConfigOption);
var verbose = parseResult.GetValue(verboseOption);
await ExportDiagnosticBundleAsync(
services,
output,
includeLogs,
logDuration,
noConfig,
verbose,
cancellationToken);
});
return export;
}
private static async Task ExportDiagnosticBundleAsync(
IServiceProvider services,
string outputPath,
bool includeLogs,
string? logDuration,
bool noConfig,
bool verbose,
CancellationToken ct)
{
var generator = services.GetRequiredService<DiagnosticBundleGenerator>();
var duration = ParseDuration(logDuration) ?? TimeSpan.FromHours(1);
var options = new DiagnosticBundleOptions
{
IncludeConfig = !noConfig,
IncludeLogs = includeLogs,
LogDuration = duration
};
Console.WriteLine("Generating diagnostic bundle...");
var bundle = await generator.GenerateAsync(options, ct);
// Ensure output path has .zip extension
if (!outputPath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
{
outputPath += ".zip";
}
await generator.ExportToZipAsync(bundle, outputPath, ct);
Console.WriteLine();
Console.WriteLine($"Diagnostic bundle created: {outputPath}");
Console.WriteLine();
Console.WriteLine($"Summary:");
Console.WriteLine($" Passed: {bundle.DoctorReport.Summary.Passed}");
Console.WriteLine($" Warnings: {bundle.DoctorReport.Summary.Warnings}");
Console.WriteLine($" Failed: {bundle.DoctorReport.Summary.Failed}");
Console.WriteLine();
Console.WriteLine("Share this bundle with Stella Ops support for assistance.");
}
private static TimeSpan? ParseDuration(string? duration)
{
if (string.IsNullOrEmpty(duration))
{
return null;
}
// Parse duration strings like "1h", "4h", "30m", "24h"
if (duration.EndsWith("h", StringComparison.OrdinalIgnoreCase))
{
if (double.TryParse(duration.AsSpan(0, duration.Length - 1), NumberStyles.Float, CultureInfo.InvariantCulture, out var hours))
{
return TimeSpan.FromHours(hours);
}
}
if (duration.EndsWith("m", StringComparison.OrdinalIgnoreCase))
{
if (double.TryParse(duration.AsSpan(0, duration.Length - 1), NumberStyles.Float, CultureInfo.InvariantCulture, out var minutes))
{
return TimeSpan.FromMinutes(minutes);
}
}
if (duration.EndsWith("d", StringComparison.OrdinalIgnoreCase))
{
if (double.TryParse(duration.AsSpan(0, duration.Length - 1), NumberStyles.Float, CultureInfo.InvariantCulture, out var days))
{
return TimeSpan.FromDays(days);
}
}
return null;
}
private static async Task RunDoctorAsync(
IServiceProvider services,
string format,