GenerateAnsibleInventoryCommand.cs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. using System.ComponentModel;
  2. using Microsoft.Extensions.DependencyInjection;
  3. using RackPeek.Domain.UseCases.Ansible;
  4. using Spectre.Console;
  5. using Spectre.Console.Cli;
  6. namespace Shared.Rcl.Commands.Exporters;
  7. public sealed class GenerateAnsibleInventorySettings : CommandSettings {
  8. [CommandOption("--group-tags")]
  9. [Description("Comma-separated list of tags to group by (e.g. prod,staging)")]
  10. public string? GroupTags { get; init; }
  11. [CommandOption("--group-labels")]
  12. [Description("Comma-separated list of label keys to group by (e.g. env,site)")]
  13. public string? GroupLabels { get; init; }
  14. [CommandOption("--global-var")]
  15. [Description("Global variable (repeatable). Format: key=value")]
  16. public string[] GlobalVars { get; init; } = [];
  17. [CommandOption("--format")]
  18. [Description("Inventory format: ini (default) or yaml")]
  19. [DefaultValue("ini")]
  20. public string Format { get; init; } = "ini";
  21. [CommandOption("-o|--output")]
  22. [Description("Write inventory to file instead of stdout")]
  23. public string? OutputPath { get; init; }
  24. }
  25. public sealed class GenerateAnsibleInventoryCommand(IServiceProvider provider)
  26. : AsyncCommand<GenerateAnsibleInventorySettings> {
  27. public override async Task<int> ExecuteAsync(
  28. CommandContext context,
  29. GenerateAnsibleInventorySettings settings,
  30. CancellationToken cancellationToken) {
  31. using IServiceScope scope = provider.CreateScope();
  32. AnsibleInventoryGeneratorUseCase useCase = scope.ServiceProvider
  33. .GetRequiredService<AnsibleInventoryGeneratorUseCase>();
  34. if (!TryParseFormat(settings.Format, out InventoryFormat format)) {
  35. AnsiConsole.MarkupLine(
  36. $"[red]Invalid format:[/] {Markup.Escape(settings.Format)}. Use 'ini' or 'yaml'.");
  37. return -1;
  38. }
  39. var options = new InventoryOptions {
  40. Format = format,
  41. GroupByTags = ParseCsv(settings.GroupTags),
  42. GroupByLabelKeys = ParseCsv(settings.GroupLabels),
  43. GlobalVars = ParseGlobalVars(settings.GlobalVars)
  44. };
  45. InventoryResult? result = await useCase.ExecuteAsync(options);
  46. if (result is null) {
  47. AnsiConsole.MarkupLine("[red]Inventory generation returned null.[/]");
  48. return -1;
  49. }
  50. if (result.Warnings.Any()) {
  51. AnsiConsole.MarkupLine("[yellow]Warnings:[/]");
  52. foreach (var warning in result.Warnings)
  53. AnsiConsole.MarkupLine($"[yellow]- {Markup.Escape(warning)}[/]");
  54. AnsiConsole.WriteLine();
  55. }
  56. if (!string.IsNullOrWhiteSpace(settings.OutputPath)) {
  57. await File.WriteAllTextAsync(
  58. settings.OutputPath,
  59. result.InventoryText,
  60. cancellationToken);
  61. AnsiConsole.MarkupLine(
  62. $"[green]Inventory written to:[/] {Markup.Escape(settings.OutputPath)}");
  63. }
  64. else {
  65. AnsiConsole.MarkupLine("[green]Generated Inventory:[/]");
  66. AnsiConsole.WriteLine();
  67. AnsiConsole.Write(result.InventoryText);
  68. }
  69. return 0;
  70. }
  71. // ------------------------
  72. private static bool TryParseFormat(string raw, out InventoryFormat format) {
  73. format = raw.Trim().ToLowerInvariant() switch {
  74. "ini" => InventoryFormat.Ini,
  75. "yaml" => InventoryFormat.Yaml,
  76. "yml" => InventoryFormat.Yaml,
  77. _ => default
  78. };
  79. return raw.Equals("ini", StringComparison.OrdinalIgnoreCase)
  80. || raw.Equals("yaml", StringComparison.OrdinalIgnoreCase)
  81. || raw.Equals("yml", StringComparison.OrdinalIgnoreCase);
  82. }
  83. private static IReadOnlyList<string> ParseCsv(string? raw) {
  84. if (string.IsNullOrWhiteSpace(raw))
  85. return [];
  86. return raw.Split(',', StringSplitOptions.RemoveEmptyEntries)
  87. .Select(x => x.Trim())
  88. .Where(x => !string.IsNullOrWhiteSpace(x))
  89. .ToArray();
  90. }
  91. private static IDictionary<string, string> ParseGlobalVars(string[] vars) {
  92. var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  93. foreach (var entry in vars ?? []) {
  94. var parts = entry.Split('=', 2);
  95. if (parts.Length != 2)
  96. continue;
  97. var key = parts[0].Trim();
  98. var value = parts[1].Trim();
  99. if (!string.IsNullOrWhiteSpace(key))
  100. dict[key] = value;
  101. }
  102. return dict;
  103. }
  104. }