Collect Linux distribution and version telemetry across forwarders and web UI
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m7s
CI Test Suite / Run Component Tests & Pipeline Verification (push) Successful in 2m7s
This commit is contained in:
+174
-11
@@ -10,6 +10,7 @@ import json
|
||||
import socket
|
||||
import shutil
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
@@ -41,7 +42,154 @@ def get_ip_addresses() -> List[str]:
|
||||
return sorted(list(ips))
|
||||
|
||||
|
||||
def get_os_details() -> Dict[str, Any]:
|
||||
def parse_os_release_text(content: str) -> Dict[str, str]:
|
||||
"""Parses key=value lines from standard Linux /etc/os-release files."""
|
||||
data = {}
|
||||
for line in content.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
k, v = line.split("=", 1)
|
||||
k = k.strip()
|
||||
v = v.strip().strip('"\'')
|
||||
data[k] = v
|
||||
return data
|
||||
|
||||
|
||||
def get_linux_distro_info(os_release_path: Optional[str] = None) -> Dict[str, str]:
|
||||
"""
|
||||
Accurately detects Linux distribution name, version, ID, and codename.
|
||||
Uses platform.freedesktop_os_release() (Python 3.10+), followed by standard
|
||||
/etc/os-release and /usr/lib/os-release parsing, with legacy release file fallbacks.
|
||||
"""
|
||||
res = {
|
||||
"distro": "",
|
||||
"distro_version": "",
|
||||
"distro_id": "",
|
||||
"distro_codename": "",
|
||||
"pretty_name": ""
|
||||
}
|
||||
|
||||
raw: Dict[str, str] = {}
|
||||
|
||||
if os_release_path and os.path.exists(os_release_path):
|
||||
try:
|
||||
with open(os_release_path, "r", encoding="utf-8", errors="replace") as f:
|
||||
raw = parse_os_release_text(f.read())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not raw:
|
||||
# 1. Try standard library platform.freedesktop_os_release() (Python 3.10+)
|
||||
if hasattr(platform, "freedesktop_os_release"):
|
||||
try:
|
||||
raw = dict(platform.freedesktop_os_release())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not raw:
|
||||
# 2. Check standard /etc/os-release and /usr/lib/os-release
|
||||
for fpath in ("/etc/os-release", "/usr/lib/os-release"):
|
||||
if os.path.exists(fpath):
|
||||
try:
|
||||
with open(fpath, "r", encoding="utf-8", errors="replace") as f:
|
||||
raw = parse_os_release_text(f.read())
|
||||
if raw:
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if raw:
|
||||
distro = raw.get("NAME") or raw.get("ID", "").capitalize() or "Linux"
|
||||
version = raw.get("VERSION_ID") or raw.get("VERSION") or ""
|
||||
pretty = raw.get("PRETTY_NAME") or f"{distro} {version}".strip()
|
||||
distro_id = raw.get("ID", "").lower()
|
||||
codename = raw.get("VERSION_CODENAME") or ""
|
||||
if not codename and "(" in raw.get("VERSION", ""):
|
||||
m = re.search(r"\(([^)]+)\)", raw.get("VERSION", ""))
|
||||
if m:
|
||||
codename = m.group(1).lower()
|
||||
|
||||
res["distro"] = distro
|
||||
res["distro_version"] = version
|
||||
res["distro_id"] = distro_id
|
||||
res["distro_codename"] = codename
|
||||
res["pretty_name"] = pretty
|
||||
return res
|
||||
|
||||
# 3. Legacy file fallbacks for older or non-systemd Linux distributions
|
||||
for rh_file in ("/etc/redhat-release", "/etc/centos-release", "/etc/system-release"):
|
||||
if os.path.exists(rh_file):
|
||||
try:
|
||||
with open(rh_file, "r", encoding="utf-8", errors="replace") as f:
|
||||
content = f.read().strip()
|
||||
if content:
|
||||
res["pretty_name"] = content
|
||||
m = re.search(r"release\s+([\d\.]+)", content, re.IGNORECASE)
|
||||
if m:
|
||||
res["distro_version"] = m.group(1)
|
||||
parts = content.split("release")[0].strip()
|
||||
res["distro"] = parts or "Red Hat / CentOS"
|
||||
res["distro_id"] = "rhel" if "red hat" in content.lower() else ("centos" if "centos" in content.lower() else "linux")
|
||||
return res
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if os.path.exists("/etc/debian_version"):
|
||||
try:
|
||||
with open("/etc/debian_version", "r", encoding="utf-8", errors="replace") as f:
|
||||
ver = f.read().strip()
|
||||
res["distro"] = "Debian"
|
||||
res["distro_version"] = ver
|
||||
res["distro_id"] = "debian"
|
||||
res["pretty_name"] = f"Debian {ver}".strip()
|
||||
return res
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if os.path.exists("/etc/alpine-release"):
|
||||
try:
|
||||
with open("/etc/alpine-release", "r", encoding="utf-8", errors="replace") as f:
|
||||
ver = f.read().strip()
|
||||
res["distro"] = "Alpine Linux"
|
||||
res["distro_version"] = ver
|
||||
res["distro_id"] = "alpine"
|
||||
res["pretty_name"] = f"Alpine Linux {ver}".strip()
|
||||
return res
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if os.path.exists("/etc/arch-release"):
|
||||
res["distro"] = "Arch Linux"
|
||||
res["distro_version"] = "Rolling"
|
||||
res["distro_id"] = "arch"
|
||||
res["pretty_name"] = "Arch Linux"
|
||||
return res
|
||||
|
||||
if os.path.exists("/etc/SuSE-release"):
|
||||
try:
|
||||
with open("/etc/SuSE-release", "r", encoding="utf-8", errors="replace") as f:
|
||||
content = f.read()
|
||||
m = re.search(r"VERSION\s*=\s*([\d\.]+)", content)
|
||||
ver = m.group(1) if m else ""
|
||||
res["distro"] = "openSUSE"
|
||||
res["distro_version"] = ver
|
||||
res["distro_id"] = "opensuse"
|
||||
res["pretty_name"] = f"openSUSE {ver}".strip()
|
||||
return res
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Generic Linux fallback
|
||||
kernel_ver = platform.release()
|
||||
res["distro"] = "Linux"
|
||||
res["distro_version"] = kernel_ver
|
||||
res["distro_id"] = "linux"
|
||||
res["pretty_name"] = f"Linux {kernel_ver}".strip()
|
||||
return res
|
||||
|
||||
|
||||
def get_os_details(os_release_path: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Accurately classifies the operating system, explicitly differentiating between
|
||||
Windows Client (Workstation) and Windows Server, as well as Linux distributions.
|
||||
@@ -51,8 +199,12 @@ def get_os_details() -> Dict[str, Any]:
|
||||
"os_type": "windows_server" | "windows" | "linux" | "darwin" | "unknown",
|
||||
"os_name": str, # e.g. "Windows Server 2022 Datacenter", "Windows 11 Pro", "Ubuntu 22.04 LTS"
|
||||
"is_server": bool, # True for Windows Server or Linux Server; False for desktop clients
|
||||
"distro": str, # e.g. "Ubuntu", "Debian GNU/Linux", "Red Hat Enterprise Linux"
|
||||
"distro_version": str,# e.g. "22.04", "12", "9.3"
|
||||
"distro_id": str, # e.g. "ubuntu", "debian", "rhel", "alpine"
|
||||
"distro_codename": str,# e.g. "jammy", "bookworm"
|
||||
"edition": str, # e.g. "ServerDatacenter", "Professional", "Enterprise"
|
||||
"build": str, # OS build number
|
||||
"build": str, # OS build number or kernel release
|
||||
"architecture": str, # "AMD64", "x86_64", "ARM64"
|
||||
"hostname": str,
|
||||
"ip_addresses": List[str]
|
||||
@@ -61,6 +213,10 @@ def get_os_details() -> Dict[str, Any]:
|
||||
is_server = False
|
||||
os_type = "unknown"
|
||||
os_name = platform.platform()
|
||||
distro = ""
|
||||
distro_version = ""
|
||||
distro_id = ""
|
||||
distro_codename = ""
|
||||
edition = ""
|
||||
build = platform.version()
|
||||
arch = platform.machine() or ("64-bit" if sys.maxsize > 2**32 else "32-bit")
|
||||
@@ -105,15 +261,14 @@ def get_os_details() -> Dict[str, Any]:
|
||||
elif sys.platform.startswith("linux"):
|
||||
os_type = "linux"
|
||||
is_server = True # Linux in enterprise log forwarding contexts is typically server/node
|
||||
if os.path.exists("/etc/os-release"):
|
||||
try:
|
||||
with open("/etc/os-release", "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
if line.startswith("PRETTY_NAME="):
|
||||
os_name = line.strip().split("=", 1)[1].strip('"\'')
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
lin_info = get_linux_distro_info(os_release_path=os_release_path)
|
||||
distro = lin_info.get("distro", "Linux")
|
||||
distro_version = lin_info.get("distro_version", "")
|
||||
distro_id = lin_info.get("distro_id", "")
|
||||
distro_codename = lin_info.get("distro_codename", "")
|
||||
os_name = lin_info.get("pretty_name") or f"{distro} {distro_version}".strip() or "Linux"
|
||||
edition = distro_codename or distro
|
||||
build = platform.release()
|
||||
elif sys.platform == "darwin":
|
||||
os_type = "darwin"
|
||||
is_server = False
|
||||
@@ -126,6 +281,10 @@ def get_os_details() -> Dict[str, Any]:
|
||||
"os_type": os_type,
|
||||
"os_name": os_name,
|
||||
"is_server": is_server,
|
||||
"distro": distro,
|
||||
"distro_version": distro_version,
|
||||
"distro_id": distro_id,
|
||||
"distro_codename": distro_codename,
|
||||
"edition": edition,
|
||||
"build": build,
|
||||
"architecture": arch,
|
||||
@@ -310,6 +469,10 @@ def get_full_system_inventory() -> Dict[str, Any]:
|
||||
"os_type": os_info.get("os_type"),
|
||||
"os_name": os_info.get("os_name"),
|
||||
"is_server": os_info.get("is_server"),
|
||||
"distro": os_info.get("distro", ""),
|
||||
"distro_version": os_info.get("distro_version", ""),
|
||||
"distro_id": os_info.get("distro_id", ""),
|
||||
"distro_codename": os_info.get("distro_codename", ""),
|
||||
"edition": os_info.get("edition"),
|
||||
"build": os_info.get("build"),
|
||||
"architecture": os_info.get("architecture"),
|
||||
|
||||
Reference in New Issue
Block a user