GenerateAnsibleInventoryCommand.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  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.Ansible;
  7. public sealed class GenerateAnsibleInventorySettings : CommandSettings
  8. {
  9. [CommandOption("--group-tags")]
  10. [Description("Comma-separated list of tags to group by (e.g. prod,staging)")]
  11. public string? GroupTags { get; init; }
  12. [CommandOption("--group-labels")]
  13. [Description("Comma-separated list of label keys to group by (e.g. env,site)")]
  14. public string? GroupLabels { get; init; }
  15. [CommandOption("--global-var")]
  16. [Description("Global variable (repeatable). Format: key=value")]
  17. public string[] GlobalVars { get; init; } = [];
  18. [CommandOption("-o|--output")]
  19. [Description("Write inventory to file instead of stdout")]
  20. public string? OutputPath { get; init; }
  21. }
  22. public sealed class GenerateAnsibleInventoryCommand(IServiceProvider provider)
  23. : AsyncCommand<GenerateAnsibleInventorySettings>
  24. {
  25. public override async Task<int> ExecuteAsync(
  26. CommandContext context,
  27. GenerateAnsibleInventorySettings settings,
  28. CancellationToken cancellationToken)
  29. {
  30. using var scope = provider.CreateScope();
  31. var useCase = scope.ServiceProvider
  32. .GetRequiredService<AnsibleInventoryGeneratorUseCase>();
  33. var options = new InventoryOptions
  34. {
  35. GroupByTags = ParseCsv(settings.GroupTags),
  36. GroupByLabelKeys = ParseCsv(settings.GroupLabels),
  37. GlobalVars = ParseGlobalVars(settings.GlobalVars)
  38. };
  39. var result = await useCase.ExecuteAsync(options);
  40. if (result is null)
  41. {
  42. AnsiConsole.MarkupLine("[red]Inventory generation returned null.[/]");
  43. return -1;
  44. }
  45. if (result.Warnings.Any())
  46. {
  47. AnsiConsole.MarkupLine("[yellow]Warnings:[/]");
  48. foreach (var warning in result.Warnings)
  49. AnsiConsole.MarkupLine($"[yellow]- {Markup.Escape(warning)}[/]");
  50. AnsiConsole.WriteLine();
  51. }
  52. if (!string.IsNullOrWhiteSpace(settings.OutputPath))
  53. {
  54. await File.WriteAllTextAsync(settings.OutputPath, result.InventoryText, cancellationToken);
  55. AnsiConsole.MarkupLine(
  56. $"[green]Inventory written to:[/] {Markup.Escape(settings.OutputPath)}");
  57. }
  58. else
  59. {
  60. AnsiConsole.MarkupLine("[green]Generated Inventory:[/]");
  61. AnsiConsole.WriteLine();
  62. AnsiConsole.Write(result.InventoryText);
  63. }
  64. return 0;
  65. }
  66. // ------------------------
  67. private static IReadOnlyList<string> ParseCsv(string? raw)
  68. {
  69. if (string.IsNullOrWhiteSpace(raw))
  70. return [];
  71. return raw.Split(',', StringSplitOptions.RemoveEmptyEntries)
  72. .Select(x => x.Trim())
  73. .Where(x => !string.IsNullOrWhiteSpace(x))
  74. .ToArray();
  75. }
  76. private static IDictionary<string, string> ParseGlobalVars(string[] vars)
  77. {
  78. var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  79. foreach (var entry in vars ?? [])
  80. {
  81. var parts = entry.Split('=', 2);
  82. if (parts.Length != 2) continue;
  83. var key = parts[0].Trim();
  84. var value = parts[1].Trim();
  85. if (!string.IsNullOrWhiteSpace(key))
  86. dict[key] = value;
  87. }
  88. return dict;
  89. }
  90. }