metis/pkg/service/node_annotations.go

86 lines
2.3 KiB
Go

package service
import (
"fmt"
"strings"
"time"
"metis/pkg/facts"
)
func (a *App) syncScratchAnnotations(record SnapshotRecord) error {
scratch := record.Snapshot.USBScratch
if scratch == nil {
return nil
}
annotations := scratchHealthAnnotations(scratch, record.CollectedAt)
if len(annotations) == 0 {
return nil
}
return patchNodeAnnotations(record.Node, annotations)
}
func scratchHealthAnnotations(scratch *facts.USBScratch, observedAt time.Time) map[string]string {
status, detail := scratchStatusDetail(scratch)
selector := ""
if scratch.UUID != "" {
selector = "UUID=" + scratch.UUID
} else if scratch.Label != "" {
selector = "LABEL=" + scratch.Label
}
managedPaths := make([]string, 0, len(scratch.BindTargets))
for _, target := range scratch.BindTargets {
if strings.TrimSpace(target.Path) != "" {
managedPaths = append(managedPaths, target.Path)
}
}
annotations := map[string]string{}
for _, family := range []string{"usb-scratch", "astraios"} {
prefix := "maintenance.bstein.dev/" + family
annotations[prefix+"-status"] = status
annotations[prefix+"-detail"] = detail
annotations[prefix+"-mountpoint"] = scratch.Mountpoint
annotations[prefix+"-managed-paths"] = strings.Join(managedPaths, "_")
annotations[prefix+"-last-observed"] = observedAt.UTC().Format(time.RFC3339)
if selector != "" {
annotations[prefix+"-selector"] = selector
}
}
return annotations
}
func scratchStatusDetail(scratch *facts.USBScratch) (string, string) {
if scratch == nil {
return "missing", "no-scratch-snapshot"
}
failures := []string{}
if !scratch.MountHealthy {
failures = append(failures, "mount-unhealthy")
}
if scratch.UUID != "" && !scratch.UUIDHealthy {
failures = append(failures, "uuid-mismatch")
}
if scratch.Label != "" && !scratch.LabelHealthy {
failures = append(failures, "label-mismatch")
}
if !scratch.BindHealthy {
failures = append(failures, "bind-mount-incomplete")
}
if len(failures) == 0 {
return "ok", "healthy"
}
return "error", strings.Join(failures, ",")
}
func annotationSyncEvent(node string, err error) Event {
return Event{
Time: time.Now().UTC(),
Kind: "sentinel.annotation",
Summary: fmt.Sprintf("Could not sync scratch annotations for %s", node),
Details: map[string]any{
"node": node,
"error": err.Error(),
},
}
}