using System.Text; using RackPeek.Domain.Persistence; using RackPeek.Domain.Resources; using RackPeek.Domain.Resources.SystemResources; namespace RackPeek.Domain.UseCases.Hosts; public sealed record HostsExportOptions { /// /// Only include resources that have at least one of these tags. /// public IReadOnlyList IncludeTags { get; init; } = []; /// /// If true, append domain suffix (e.g. .home.local) /// public string? DomainSuffix { get; init; } /// /// If true, include localhost defaults at top. /// public bool IncludeLocalhostDefaults { get; init; } = true; } public sealed record HostsExportResult( string HostsText, IReadOnlyList Warnings); public static class HostsFileGenerator { public static HostsExportResult ToHostsFile( this IReadOnlyList resources, HostsExportOptions? options = null) { options ??= new HostsExportOptions(); var sb = new StringBuilder(); var warnings = new List(); if (options.IncludeLocalhostDefaults) { sb.AppendLine("127.0.0.1 localhost"); sb.AppendLine("::1 localhost"); sb.AppendLine(); } foreach (Resource r in resources.OrderBy(x => x.Name)) { if (options.IncludeTags.Any()) { var tags = r.Tags ?? []; if (!options.IncludeTags.Any(t => tags.Contains(t, StringComparer.OrdinalIgnoreCase))) continue; } var address = GetAddress(r); if (string.IsNullOrWhiteSpace(address)) continue; var hostName = SanitizeHostName(r.Name); if (!string.IsNullOrWhiteSpace(options.DomainSuffix)) hostName = $"{hostName}.{options.DomainSuffix.Trim('.')}"; sb.AppendLine($"{address} {hostName}"); } if (sb.Length == 0) warnings.Add("No host entries generated."); return new HostsExportResult(sb.ToString().TrimEnd(), warnings); } private static string? GetAddress(Resource r) { if (r is SystemResource { Ip: not null } system && !string.IsNullOrWhiteSpace(system!.Ip)) { return system.Ip; } if (r.Labels.TryGetValue("ip", out var ip) && !string.IsNullOrWhiteSpace(ip)) return ip; if (r.Labels.TryGetValue("hostname", out var hn) && !string.IsNullOrWhiteSpace(hn)) return hn; if (r.Labels.TryGetValue("ansible_host", out var ah) && !string.IsNullOrWhiteSpace(ah)) return ah; return null; } private static string SanitizeHostName(string name) { var sb = new StringBuilder(); foreach (var ch in name.Trim().ToLowerInvariant()) if (char.IsLetterOrDigit(ch) || ch == '-') sb.Append(ch); else if (ch == '_' || ch == ' ') sb.Append('-'); return sb.Length == 0 ? "host" : sb.ToString(); } } public class HostsFileExportUseCase(IResourceCollection repository) : IUseCase { public async Task ExecuteAsync(HostsExportOptions options) { IReadOnlyList resources = await repository.GetAllOfTypeAsync(); return resources.ToHostsFile(options); } }