Accountant: Inventar, where, Checkmk, Abrechnung

This commit is contained in:
br committed 2026-10-18 17:08:58 +02:00
1 parent 9f16148cde
commit dd93277750
59 files changed
+7522 -113

No files matched your search

+58
View File
@@ -14,11 +14,13 @@ import (
"context"
"errors"
"fmt"
"maps"
"net/http"
"slices"
"strconv"
"strings"
"sync"
"time"
"sultan/pkg/license"
"sultan/pkg/policy"
@@ -44,6 +46,28 @@ type Workload struct {
Name string `json:"name"`
Host string `json:"host"`
Labels map[string]string `json:"labels"`
// Meta: Zusatzdaten für Inventar und Störfall (Spec 5.3); nil bei
// sultan.d-Diensten und älteren Agents. Ändert das Routing nie.
Meta *WorkloadMeta `json:"meta,omitempty"`
}
// WorkloadMeta beschreibt, wo und wie ein Container läuft – automatisch
// ermittelt, ohne zusätzliche Labels.
type WorkloadMeta struct {
ComposeProject string `json:"compose_project,omitempty"` // com.docker.compose.project
ComposeService string `json:"compose_service,omitempty"` // com.docker.compose.service
ComposeDir string `json:"compose_dir,omitempty"` // com.docker.compose.project.working_dir
State string `json:"state,omitempty"` // running, exited, …
StartedAt time.Time `json:"started_at,omitzero"` // letzter Start
Health string `json:"health,omitempty"` // healthy, unhealthy, starting; leer = kein Healthcheck
}
// HostInfo beschreibt den Knoten (Spec 5.3); nil bei älteren Agents.
type HostInfo struct {
FQDN string `json:"fqdn,omitempty"`
OS string `json:"os,omitempty"`
DockerVersion string `json:"docker_version,omitempty"`
NetBirdIP string `json:"netbird_ip,omitempty"`
}
// NodeState ist der vollständige Zustand eines Knotens (Payload von
@@ -52,6 +76,29 @@ type NodeState struct {
Workloads []Workload `json:"workloads"`
// DownServices: Services (Originalnamen), deren Healthcheck fehlschlägt.
DownServices []string `json:"down_services,omitempty"`
// Host: Angaben zum Knoten; ändern das Routing nie.
Host *HostInfo `json:"host_info,omitempty"`
}
// clone liefert eine tiefe Kopie.
func (s NodeState) clone() NodeState {
out := NodeState{DownServices: slices.Clone(s.DownServices)}
if s.Workloads != nil {
out.Workloads = make([]Workload, len(s.Workloads))
}
for i, w := range s.Workloads {
w.Labels = maps.Clone(w.Labels)
if w.Meta != nil {
m := *w.Meta
w.Meta = &m
}
out.Workloads[i] = w
}
if s.Host != nil {
h := *s.Host
out.Host = &h
}
return out
}
// NodeContext ist, was die Policy über einen Knoten wissen muss.
@@ -179,6 +226,17 @@ func (r *Registry) Nodes() []string {
return r.nodeIDs()
}
// State liefert eine Kopie des zuletzt gemeldeten Zustands eines Knotens.
func (r *Registry) State(nodeID string) (NodeState, bool) {
r.mu.Lock()
defer r.mu.Unlock()
entry, ok := r.nodes[nodeID]
if !ok {
return NodeState{}, false
}
return entry.state.clone(), true
}
// Handler liefert die Konfiguration für Traefiks HTTP-Provider aus
// (?format=json|yaml, Default JSON).
func (r *Registry) Handler() http.Handler {