SshExportOptions.cs 3.3 KB

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