using RackPeek.Domain.Api;
using RackPeek.Domain.Discovery;
using RackPeek.Domain.Resources;
using Spectre.Console;
namespace Shared.Rcl.Commands.Discovery;
///
/// The one place a discovery result leaves the process: printed as YAML, or sent to
/// a server. Shared so every collector behaves identically.
///
public static class DiscoveryOutput {
public static async Task EmitAsync(
IReadOnlyList resources,
DiscoverSettings settings,
CancellationToken cancellationToken) {
if (resources.Count == 0) {
AnsiConsole.MarkupLine("[yellow]Nothing discovered.[/]");
return 0;
}
var yaml = DiscoveryDocument.ToYaml(resources);
if (!settings.ShouldUpload) {
// Raw write, not through Spectre's renderer: this is meant to be redirected
// to a file, and the renderer word-wraps lines longer than the console
// width — which splits a long single-token scalar and corrupts the YAML.
// The active console's own writer keeps the web console emulator working.
await AnsiConsole.Console.Profile.Out.Writer.WriteLineAsync(yaml);
return 0;
}
return await UploadAsync(yaml, settings, cancellationToken);
}
private static async Task UploadAsync(
string yaml,
DiscoverSettings settings,
CancellationToken cancellationToken) {
var server = settings.ResolvedServer;
var apiKey = settings.ResolvedApiKey;
if (string.IsNullOrWhiteSpace(server) || string.IsNullOrWhiteSpace(apiKey)) {
// Validate() enforces both; this is the guard for a caller that skipped it.
AnsiConsole.MarkupLine(
"[red]No server or API key. Pass --server and --api-key, or set RPK_SERVER and RPK_API_KEY.[/]");
return 1;
}
try {
using var publisher = new DiscoveryPublisher(server, apiKey);
ImportYamlResponse response = await publisher.PublishAsync(yaml, settings.DryRun, cancellationToken);
Report(response, settings.DryRun, server);
return 0;
}
catch (Exception ex) when (
ex is InvalidOperationException or HttpRequestException or UriFormatException
// HttpClient reports its own timeout as a cancellation.
|| (ex is TaskCanceledException && !cancellationToken.IsCancellationRequested)) {
AnsiConsole.MarkupLine($"[red]{Markup.Escape(ex.Message)}[/]");
return 1;
}
}
private static void Report(ImportYamlResponse response, bool dryRun, string server) {
List(response.Added, "added", "green");
List(response.Updated, "updated", "yellow");
List(response.Replaced, "replaced", "yellow");
var total = response.Added.Count + response.Updated.Count + response.Replaced.Count;
if (total == 0) {
AnsiConsole.MarkupLine($"[grey]No changes — {Markup.Escape(server)} is already up to date.[/]");
return;
}
AnsiConsole.MarkupLine(dryRun
? $"[grey]Dry run — nothing was written to {Markup.Escape(server)}.[/]"
: $"[grey]{total} resource(s) written to {Markup.Escape(server)}.[/]");
}
private static void List(IReadOnlyList names, string label, string colour) {
foreach (var name in names)
AnsiConsole.MarkupLine($"[{colour}]{label}[/] {Markup.Escape(name)}");
}
}