Accountant: Inventar, where, Checkmk, Abrechnung
This commit is contained in:
1 parent
9f16148cde
commit
dd93277750
59 files changed
+7522
-113
No files matched your search
@@ -0,0 +1,165 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"sultan/pkg/docker"
|
||||
"sultan/pkg/registry"
|
||||
)
|
||||
|
||||
// osReleasePath: Quelle des Betriebssystems ohne Docker (systemd-Agent).
|
||||
var osReleasePath = "/etc/os-release"
|
||||
|
||||
// resolver ist der Teil von *net.Resolver, den detectFQDN braucht.
|
||||
type resolver interface {
|
||||
LookupCNAME(ctx context.Context, host string) (string, error)
|
||||
LookupAddr(ctx context.Context, addr string) ([]string, error)
|
||||
}
|
||||
|
||||
// detectFQDN ermittelt den vollständigen Namen des Knotens für Inventar und
|
||||
// Störfall (Spec 5.3): explizit (--fqdn) > Hostname mit Punkt > Hostname
|
||||
// über die Suchdomain aufgelöst > Reverse-Lookup der Backend-Adresse >
|
||||
// kurzer Hostname. Jede Abfrage ist kurz begrenzt; scheitert alles, bleibt
|
||||
// der Hostname.
|
||||
func detectFQDN(ctx context.Context, res resolver, explicit, hostname, backend string) string {
|
||||
if n := normalizeName(explicit); n != "" {
|
||||
return n
|
||||
}
|
||||
short := normalizeName(hostname)
|
||||
if strings.Contains(short, ".") {
|
||||
return short
|
||||
}
|
||||
lookup := func(f func(context.Context) string) string {
|
||||
lctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
return f(lctx)
|
||||
}
|
||||
if short != "" {
|
||||
if n := lookup(func(c context.Context) string {
|
||||
cname, err := res.LookupCNAME(c, short)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return normalizeName(cname)
|
||||
}); strings.Contains(n, ".") {
|
||||
return n
|
||||
}
|
||||
}
|
||||
if net.ParseIP(backend) != nil {
|
||||
if n := lookup(func(c context.Context) string {
|
||||
names, err := res.LookupAddr(c, backend)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, name := range names {
|
||||
if n := normalizeName(name); strings.Contains(n, ".") {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}); n != "" {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return short
|
||||
}
|
||||
|
||||
func normalizeName(s string) string {
|
||||
return strings.ToLower(strings.TrimSuffix(strings.TrimSpace(s), "."))
|
||||
}
|
||||
|
||||
// cgnat ist 100.64.0.0/10 – der Bereich, aus dem NetBird Adressen vergibt.
|
||||
var cgnat = &net.IPNet{IP: net.IPv4(100, 64, 0, 0).To4(), Mask: net.CIDRMask(10, 32)}
|
||||
|
||||
// netbirdIP wählt die NetBird-Adresse: IPv4 auf wt0, sonst die erste
|
||||
// IPv4-Adresse aus 100.64.0.0/10 (Interfaces alphabetisch).
|
||||
func netbirdIP(addrs map[string][]net.IP) string {
|
||||
for _, ip := range addrs["wt0"] {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
return ip4.String()
|
||||
}
|
||||
}
|
||||
names := make([]string, 0, len(addrs))
|
||||
for name := range addrs {
|
||||
names = append(names, name)
|
||||
}
|
||||
slices.Sort(names)
|
||||
for _, name := range names {
|
||||
for _, ip := range addrs[name] {
|
||||
if ip4 := ip.To4(); ip4 != nil && cgnat.Contains(ip4) {
|
||||
return ip4.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// interfaceAddrs liest die Adressen aller Interfaces.
|
||||
func interfaceAddrs() map[string][]net.IP {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
out := map[string][]net.IP{}
|
||||
for _, ifc := range ifaces {
|
||||
addrs, err := ifc.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if ipnet, ok := a.(*net.IPNet); ok {
|
||||
out[ifc.Name] = append(out[ifc.Name], ipnet.IP)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// osPrettyName liest PRETTY_NAME (ersatzweise NAME) aus einer os-release-Datei.
|
||||
func osPrettyName(path string) string {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
var name string
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
key, value, ok := strings.Cut(strings.TrimSpace(sc.Text()), "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
value = strings.Trim(value, `"'`)
|
||||
switch key {
|
||||
case "PRETTY_NAME":
|
||||
return value
|
||||
case "NAME":
|
||||
name = value
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// hostInfo sammelt die Angaben zum Knoten. Mit Docker kommt das
|
||||
// Betriebssystem aus der Docker-API (das des Hosts, auch wenn der Agent in
|
||||
// einem Container läuft); Fehler lassen Felder nur leer.
|
||||
func (a *Agent) hostInfo(ctx context.Context, client *docker.Client) *registry.HostInfo {
|
||||
h := ®istry.HostInfo{FQDN: a.fqdn, NetBirdIP: netbirdIP(interfaceAddrs())}
|
||||
if client != nil {
|
||||
info, err := client.Info(ctx)
|
||||
if err != nil {
|
||||
a.log.Debug("Docker-Info nicht lesbar", "err", err)
|
||||
}
|
||||
h.OS, h.DockerVersion = info.OperatingSystem, info.ServerVersion
|
||||
}
|
||||
if h.OS == "" {
|
||||
h.OS = osPrettyName(osReleasePath)
|
||||
}
|
||||
return h
|
||||
}
|
||||
Reference in new issue
Block a user