GenerateAnsibleInventoryCommand.cs 3.5 KB

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