89 lines
2.6 KiB
Go
89 lines
2.6 KiB
Go
package sentinel
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
// Snapshot captures host-level facts.
|
|
type Snapshot struct {
|
|
Hostname string `json:"hostname,omitempty"`
|
|
Kernel string `json:"kernel,omitempty"`
|
|
OSImage string `json:"os_image,omitempty"`
|
|
K3sVersion string `json:"k3s_version,omitempty"`
|
|
Containerd string `json:"containerd,omitempty"`
|
|
PackageSample map[string]string `json:"package_sample,omitempty"` // small subset to detect drift
|
|
DropInsSample map[string]string `json:"dropins_sample,omitempty"` // path->content hash/sample
|
|
Notes string `json:"notes,omitempty"`
|
|
}
|
|
|
|
// Collect gathers a minimal set of facts; intended to run inside a DaemonSet pod with host mounts.
|
|
func Collect() *Snapshot {
|
|
return &Snapshot{
|
|
Hostname: runAndTrim("hostname"),
|
|
Kernel: runAndTrim("uname", "-r"),
|
|
OSImage: osRelease(),
|
|
K3sVersion: runAndTrim("k3s", "version"),
|
|
Containerd: runAndTrim("containerd", "--version"),
|
|
PackageSample: pkgSample(),
|
|
}
|
|
}
|
|
|
|
func runAndTrim(cmd string, args ...string) string {
|
|
out, err := commandOutput(cmd, args...)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
|
|
func osRelease() string {
|
|
out, err := commandOutput("cat", "/etc/os-release")
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
if strings.HasPrefix(line, "PRETTY_NAME=") {
|
|
return strings.Trim(line[len("PRETTY_NAME="):], "\"")
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// pkgSample grabs a tiny subset of package versions to detect drift without collecting everything.
|
|
func pkgSample() map[string]string {
|
|
names := []string{"containerd", "k3s", "nvidia-container-toolkit", "linux-image-raspi"}
|
|
result := map[string]string{}
|
|
for _, n := range names {
|
|
v := pkgVersion(n)
|
|
if v != "" {
|
|
result[n] = v
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func pkgVersion(name string) string {
|
|
// Try dpkg-query first.
|
|
out, err := commandOutput("dpkg-query", "-W", "-f", "${Version}", name)
|
|
if err == nil && len(out) > 0 {
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
// Fallback rpm.
|
|
out, err = commandOutput("rpm", "-q", "--qf", "%{VERSION}-%{RELEASE}", name)
|
|
if err == nil && len(out) > 0 {
|
|
return strings.TrimSpace(string(out))
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func commandOutput(cmd string, args ...string) ([]byte, error) {
|
|
if os.Getenv("METIS_SENTINEL_NSENTER") == "1" {
|
|
nsenterArgs := []string{"-t", "1", "-m", "-u", "-n", "-i", "-p", "--", cmd}
|
|
nsenterArgs = append(nsenterArgs, args...)
|
|
return exec.Command("nsenter", nsenterArgs...).Output()
|
|
}
|
|
return exec.Command(cmd, args...).Output()
|
|
}
|