SshExportOptions.cs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. using System.Text;
  2. using RackPeek.Domain.Persistence;
  3. using RackPeek.Domain.Resources;
  4. namespace RackPeek.Domain.UseCases.Hosts;
  5. public sealed record HostsExportOptions {
  6. /// <summary>
  7. /// Only include resources that have at least one of these tags.
  8. /// </summary>
  9. public IReadOnlyList<string> IncludeTags { get; init; } = [];
  10. /// <summary>
  11. /// If true, append domain suffix (e.g. .home.local)
  12. /// </summary>
  13. public string? DomainSuffix { get; init; }
  14. /// <summary>
  15. /// If true, include localhost defaults at top.
  16. /// </summary>
  17. public bool IncludeLocalhostDefaults { get; init; } = true;
  18. }
  19. public sealed record HostsExportResult(
  20. string HostsText,
  21. IReadOnlyList<string> Warnings);
  22. public static class HostsFileGenerator {
  23. public static HostsExportResult ToHostsFile(
  24. this IReadOnlyList<Resource> resources,
  25. HostsExportOptions? options = null) {
  26. options ??= new HostsExportOptions();
  27. var sb = new StringBuilder();
  28. var warnings = new List<string>();
  29. if (options.IncludeLocalhostDefaults) {
  30. sb.AppendLine("127.0.0.1 localhost");
  31. sb.AppendLine("::1 localhost");
  32. sb.AppendLine();
  33. }
  34. foreach (Resource r in resources.OrderBy(x => x.Name)) {
  35. if (options.IncludeTags.Any()) {
  36. var tags = r.Tags ?? [];
  37. if (!options.IncludeTags.Any(t =>
  38. tags.Contains(t, StringComparer.OrdinalIgnoreCase)))
  39. continue;
  40. }
  41. var address = GetAddress(r);
  42. if (string.IsNullOrWhiteSpace(address)) continue;
  43. var hostName = SanitizeHostName(r.Name);
  44. if (!string.IsNullOrWhiteSpace(options.DomainSuffix))
  45. hostName = $"{hostName}.{options.DomainSuffix.Trim('.')}";
  46. sb.AppendLine($"{address} {hostName}");
  47. }
  48. if (sb.Length == 0)
  49. warnings.Add("No host entries generated.");
  50. return new HostsExportResult(sb.ToString().TrimEnd(), warnings);
  51. }
  52. private static string? GetAddress(Resource r) {
  53. if (r.Labels.TryGetValue("ip", out var ip) && !string.IsNullOrWhiteSpace(ip))
  54. return ip;
  55. if (r.Labels.TryGetValue("hostname", out var hn) && !string.IsNullOrWhiteSpace(hn))
  56. return hn;
  57. if (r.Labels.TryGetValue("ansible_host", out var ah) && !string.IsNullOrWhiteSpace(ah))
  58. return ah;
  59. return null;
  60. }
  61. private static string SanitizeHostName(string name) {
  62. var sb = new StringBuilder();
  63. foreach (var ch in name.Trim().ToLowerInvariant())
  64. if (char.IsLetterOrDigit(ch) || ch == '-')
  65. sb.Append(ch);
  66. else if (ch == '_' || ch == ' ')
  67. sb.Append('-');
  68. return sb.Length == 0 ? "host" : sb.ToString();
  69. }
  70. }
  71. public class HostsFileExportUseCase(IResourceCollection repository) : IUseCase {
  72. public async Task<HostsExportResult?> ExecuteAsync(HostsExportOptions options) {
  73. IReadOnlyList<Resource> resources = await repository.GetAllOfTypeAsync<Resource>();
  74. return resources.ToHostsFile(options);
  75. }
  76. }