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);
}
}