audit work, doctors work
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user