package atlas import ( "encoding/json" "fmt" "math" "strconv" "strings" ) // HostsFromNodes parses a Kubernetes `/api/v1/nodes` list response into // substrate Host entries, so the atlas machines reflect the live cluster. func HostsFromNodes(nodesJSON []byte) ([]Host, error) { var list struct { Items []struct { Metadata struct { Name string `json:"name"` } `json:"metadata"` Status struct { Capacity map[string]string `json:"capacity"` NodeInfo struct { Architecture string `json:"architecture"` KubeletVersion string `json:"kubeletVersion"` } `json:"nodeInfo"` } `json:"status"` } `json:"items"` } if err := json.Unmarshal(nodesJSON, &list); err != nil { return nil, fmt.Errorf("parse nodes: %w", err) } hosts := make([]Host, 0, len(list.Items)) for _, it := range list.Items { var parts []string if a := it.Status.NodeInfo.Architecture; a != "" { parts = append(parts, a) } if c := it.Status.Capacity["cpu"]; c != "" { parts = append(parts, c+" cpu") } if gi := memGi(it.Status.Capacity["memory"]); gi != "" { parts = append(parts, gi+"Gi") } if g := it.Status.Capacity["nvidia.com/gpu"]; g != "" && g != "0" { parts = append(parts, g+"× GPU") } if v := k3sVersion(it.Status.NodeInfo.KubeletVersion); v != "" { parts = append(parts, v) } hosts = append(hosts, Host{Name: it.Metadata.Name, Spec: strings.Join(parts, " · ")}) } return hosts, nil } // memGi converts a Kubernetes memory quantity in Ki (e.g. "67108864Ki") to a // rounded Gi string. Returns "" if unparseable. func memGi(ki string) string { n, err := strconv.ParseFloat(strings.TrimSuffix(ki, "Ki"), 64) if err != nil { return "" } return strconv.Itoa(int(math.Round(n / 1048576))) } // k3sVersion trims a kubeletVersion's build metadata, labelling k3s builds. // "v1.31.4+k3s1" → "k3s v1.31.4"; "v1.31.4" → "v1.31.4". func k3sVersion(kubelet string) string { if kubelet == "" { return "" } if i := strings.Index(kubelet, "+"); i >= 0 { ver, suffix := kubelet[:i], kubelet[i+1:] if strings.Contains(suffix, "k3s") { return "k3s " + ver } return ver } return kubelet }