netmaker/logic/sysinfo.go
Abhishek Kondur eed32cd2d6
Merge pull request #3735 from gravitl/NM-166
NM-166: Device Posture Checks
2025-12-05 10:33:11 +04:00

227 lines
5.3 KiB
Go

package logic
import (
"bytes"
"os"
"os/exec"
"runtime"
"strings"
)
type OSInfo struct {
OS string `json:"os"` // e.g. "ubuntu", "windows", "macos"
OSFamily string `json:"os_family"` // e.g. "linux-debian", "windows"
OSVersion string `json:"os_version"` // e.g. "22.04", "10.0.22631"
KernelVersion string `json:"kernel_version"` // e.g. "6.8.0"
}
/// --- classification helpers you already had ---
func NormalizeOSName(raw string) string {
return strings.ToLower(strings.TrimSpace(raw))
}
// OSFamily returns a normalized OS family string.
// Examples: "linux-debian", "linux-redhat", "linux-arch", "linux-other", "windows", "darwin"
func OSFamily(osName string) string {
osName = NormalizeOSName(osName)
// Non-Linux first
if strings.Contains(osName, "windows") {
return "windows"
}
if strings.Contains(osName, "darwin") || strings.Contains(osName, "mac") || strings.Contains(osName, "os x") {
return "darwin"
}
// Linux families
switch {
// Debian family
case containsAny(osName,
"debian", "ubuntu", "pop", "linuxmint", "kali", "raspbian", "elementary"):
return "linux-debian"
// Red Hat family
case containsAny(osName,
"rhel", "red hat", "centos", "rocky", "alma", "fedora", "oracle linux", "ol"):
return "linux-redhat"
// SUSE family
case containsAny(osName,
"suse", "opensuse", "sles"):
return "linux-suse"
// Arch family
case containsAny(osName,
"arch", "manjaro", "endeavouros", "garuda"):
return "linux-arch"
// Gentoo
case strings.Contains(osName, "gentoo"):
return "linux-gentoo"
// Alpine, Amazon, BusyBox, etc.
case containsAny(osName,
"alpine", "amazon", "busybox"):
return "linux-other"
}
// Fallbacks
if strings.Contains(osName, "linux") {
return "linux-other"
}
return "unknown"
}
func containsAny(s string, subs ...string) bool {
for _, sub := range subs {
if strings.Contains(s, sub) {
return true
}
}
return false
}
/// --- public entrypoint ---
// GetOSInfo returns OS, OSFamily, OSVersion and KernelVersion for the current platform.
func GetOSInfo() OSInfo {
switch runtime.GOOS {
case "linux":
return getLinuxOSInfo()
case "darwin":
return getDarwinOSInfo()
case "windows":
return getWindowsOSInfo()
default:
// Fallback for other UNIX-likes; best-effort
kernel := strings.TrimSpace(runCmd("uname", "-r"))
name := runtime.GOOS
return OSInfo{
OS: NormalizeOSName(name),
OSFamily: OSFamily(name),
OSVersion: "",
KernelVersion: CleanVersion(kernel),
}
}
}
/// --- Linux ---
func getLinuxOSInfo() OSInfo {
var osName, osVersion string
data, err := os.ReadFile("/etc/os-release")
if err == nil {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := parts[0]
value := strings.Trim(parts[1], `"'`)
switch key {
case "ID":
osName = value
case "VERSION_ID":
osVersion = value
}
}
}
if osName == "" {
// Fallback
osName = "linux"
}
kernel := strings.TrimSpace(runCmd("uname", "-r"))
// trim extras like -generic
if idx := strings.Index(kernel, "-"); idx > 0 {
kernel = kernel[:idx]
}
normName := NormalizeOSName(osName)
return OSInfo{
OS: "linux",
OSFamily: OSFamily(normName),
OSVersion: CleanVersion(osVersion),
KernelVersion: CleanVersion(kernel),
}
}
/// --- macOS (darwin) ---
func getDarwinOSInfo() OSInfo {
productName := strings.TrimSpace(runCmd("sw_vers", "-productName"))
productVer := strings.TrimSpace(runCmd("sw_vers", "-productVersion"))
if productName == "" {
productName = "macos"
}
kernel := strings.TrimSpace(runCmd("uname", "-r"))
if idx := strings.Index(kernel, "-"); idx > 0 {
kernel = kernel[:idx]
}
normName := NormalizeOSName(productName)
return OSInfo{
OS: "darwin",
OSFamily: OSFamily(normName), // "darwin"
OSVersion: CleanVersion(productVer), // e.g. "15.0"
KernelVersion: CleanVersion(kernel),
}
}
/// --- Windows ---
func getWindowsOSInfo() OSInfo {
// OS name: we just say "windows"
osName := "windows"
// OS version via "wmic" or "ver" as fallback
var version string
// Try wmic first (may be missing on newer builds but often still present)
out := runCmd("wmic", "os", "get", "Version", "/value")
for _, line := range strings.Split(out, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "Version=") {
version = strings.TrimPrefix(line, "Version=")
version = strings.TrimSpace(version)
break
}
}
if version == "" {
// Fallback to "ver"
raw := strings.TrimSpace(runCmd("cmd", "/C", "ver"))
version = raw // you can add better parsing if you need
}
// On Windows, kernel and OS version are effectively tied; reuse
kernel := version
normName := NormalizeOSName(osName)
return OSInfo{
OS: "windows", // "windows"
OSFamily: OSFamily(normName),
OSVersion: CleanVersion(version), // e.g. "10.0.22631"
KernelVersion: CleanVersion(kernel),
}
}
/// --- small helper to run commands safely ---
func runCmd(name string, args ...string) string {
cmd := exec.Command(name, args...)
var buf bytes.Buffer
cmd.Stdout = &buf
_ = cmd.Run() // ignore error; best-effort
return buf.String()
}