generated from mathias/template-go-web
feat(atlas): Phase B — substrate from the live cluster
The substrate machines are now rendered from the live k3s nodes (arch, cpu, memory, GPU, k3s version), read in-cluster via the mounted ServiceAccount with a stdlib HTTP client (no client-go) and a 30s cache; falls back to the authored substrate whenever the cluster is unreachable. Placement per the homelab decision "in-cluster only for in-workload ops" — a running web app rendering live data fits. HostsFromNodes parser built test-first. RBAC (read-only node-reader SA) shipped in infra k3s/apps/cad-atlas/. Verified: go build/vet/lint(0)/test green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-1
@@ -4,9 +4,12 @@ import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
cadatlas "git.d-ma.be/mathias/cad-atlas"
|
||||
"git.d-ma.be/mathias/cad-atlas/internal/atlas"
|
||||
"git.d-ma.be/mathias/cad-atlas/internal/cluster"
|
||||
)
|
||||
|
||||
// atlasHTML is the Phase-A/B static shell. It fetches /api/atlas.json at load
|
||||
@@ -16,7 +19,8 @@ import (
|
||||
var atlasHTML []byte
|
||||
|
||||
// NewHandler serves the CAD Atlas: the shell at "/", and the sourced data at
|
||||
// "/api/atlas.json" (authored data + CI stage generated from the real cd.yml).
|
||||
// "/api/atlas.json" (authored data + CI stage generated from the real cd.yml +
|
||||
// substrate overlaid from the live cluster when reachable).
|
||||
func NewHandler() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -33,8 +37,42 @@ func NewHandler() http.Handler {
|
||||
http.Error(w, "atlas build failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if live := liveSubstrate(); len(live) > 0 {
|
||||
a.Substrate = live
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_ = json.NewEncoder(w).Encode(a)
|
||||
})
|
||||
return mux
|
||||
}
|
||||
|
||||
// substrate cache: query the cluster at most once per TTL; fall back to the
|
||||
// authored substrate (returns nil) whenever the cluster is unreachable.
|
||||
var (
|
||||
subMu sync.Mutex
|
||||
subCache []atlas.Host
|
||||
subAt time.Time
|
||||
)
|
||||
|
||||
const substrateTTL = 30 * time.Second
|
||||
|
||||
func liveSubstrate() []atlas.Host {
|
||||
subMu.Lock()
|
||||
defer subMu.Unlock()
|
||||
if !subAt.IsZero() && time.Since(subAt) < substrateTTL {
|
||||
return subCache
|
||||
}
|
||||
subAt = time.Now()
|
||||
raw, err := cluster.Nodes()
|
||||
if err != nil {
|
||||
subCache = nil
|
||||
return nil
|
||||
}
|
||||
hosts, err := atlas.HostsFromNodes(raw)
|
||||
if err != nil {
|
||||
subCache = nil
|
||||
return nil
|
||||
}
|
||||
subCache = hosts
|
||||
return hosts
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user