feat(atlas): hybrid substrate — live specs for cluster nodes, authored rest
CD / Detect unsubstituted template (push) Successful in 1s
CD / Lint / Test / Vet (push) Successful in 6s
CD / Build & Import (push) Successful in 14s
CD / Deploy via GitOps (push) Successful in 1s

MergeSubstrate overlays live node specs onto the authored machine list by name:
cluster nodes (koala) get fresh live specs, non-cluster machines (iguana/flamingo/
piguard) stay authored, new live nodes are appended. Test-first. Restores the full
homelab machine list while keeping cluster nodes truthful.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 00:47:53 +02:00
co-authored by Claude Opus 4.8
parent 74dffc1ff3
commit 39fd9b9adc
3 changed files with 59 additions and 1 deletions
+27
View File
@@ -52,6 +52,33 @@ func HostsFromNodes(nodesJSON []byte) ([]Host, error) {
return hosts, nil
}
// MergeSubstrate overlays live node specs onto the authored substrate: an
// authored host is replaced by the live node of the same name (fresh specs),
// authored-only machines (non-cluster: iguana/flamingo/piguard) are kept, and
// live-only nodes (new to the cluster) are appended.
func MergeSubstrate(authored, live []Host) []Host {
liveByName := make(map[string]Host, len(live))
for _, h := range live {
liveByName[h.Name] = h
}
seen := make(map[string]bool, len(authored))
out := make([]Host, 0, len(authored)+len(live))
for _, a := range authored {
if l, ok := liveByName[a.Name]; ok {
out = append(out, l)
} else {
out = append(out, a)
}
seen[a.Name] = true
}
for _, l := range live {
if !seen[l.Name] {
out = append(out, l)
}
}
return out
}
// memGi converts a Kubernetes memory quantity in Ki (e.g. "67108864Ki") to a
// rounded Gi string. Returns "" if unparseable.
func memGi(ki string) string {