mirror of
https://github.com/Rainyy21/k3s-health-dashboard.git
synced 2026-10-11 00:50:20 -04:00
fix syntax error and it pass the test case
This commit is contained in:
+140
-8
@@ -1,15 +1,147 @@
|
||||
import os
|
||||
import kubernetes import client, config
|
||||
from datetime import datetime, timezone
|
||||
from kubernetes import client, config
|
||||
|
||||
def get_core_client() -> client.CoreV1Api:
|
||||
if os.getenv("IN_CLUSTER"):
|
||||
config.load_incluster_config()
|
||||
else:
|
||||
config.load_kube_config( # runs locally
|
||||
config_file=os.getenv("KUBECONFIG", "~/.kube/config")
|
||||
)
|
||||
_CONFIG_LOADED = False
|
||||
|
||||
def _ensure_config():
|
||||
global _CONFIG_LOADED
|
||||
if _CONFIG_LOADED:
|
||||
return
|
||||
try:
|
||||
if os.getenv("IN_CLUSTER"):
|
||||
config.load_incluster_config()
|
||||
else:
|
||||
# Load local kubeconfig
|
||||
config_path = os.getenv("KUBECONFIG", os.path.expanduser("~/.kube/config"))
|
||||
config.load_kube_config(config_file=config_path)
|
||||
except Exception:
|
||||
# If we can't load config (e.g. in tests where we mock), we don't want to crash here
|
||||
# but let the client creation fail later or be mocked.
|
||||
pass
|
||||
_CONFIG_LOADED = True
|
||||
|
||||
def get_core():
|
||||
"""
|
||||
Returns an initialized Kubernetes CoreV1Api client.
|
||||
Matches the name expected by test fixtures.
|
||||
"""
|
||||
_ensure_config()
|
||||
return client.CoreV1Api()
|
||||
|
||||
def get_core_client() -> client.CoreV1Api:
|
||||
"""Legacy alias for get_core() if needed."""
|
||||
return get_core()
|
||||
|
||||
def get_custom_client() -> client.CustomObjectsApi:
|
||||
_ensure_config()
|
||||
return client.CustomObjectsApi()
|
||||
|
||||
def fetch_nodes():
|
||||
v1 = get_core()
|
||||
nodes = v1.list_node().items
|
||||
result = []
|
||||
for n in nodes:
|
||||
conditions = {c.type: c.status for c in n.status.conditions} if n.status.conditions else {}
|
||||
|
||||
# Role logic
|
||||
labels = n.metadata.labels or {}
|
||||
roles = []
|
||||
if "node-role.kubernetes.io/control-plane" in labels:
|
||||
roles.append("control-plane")
|
||||
if "node-role.kubernetes.io/master" in labels:
|
||||
roles.append("master")
|
||||
|
||||
role_str = ", ".join(roles) if roles else "worker"
|
||||
|
||||
result.append({
|
||||
"name": n.metadata.name,
|
||||
"ready": conditions.get("Ready") == "True",
|
||||
"roles": role_str,
|
||||
"age": _format_age(n.metadata.creation_timestamp),
|
||||
"cpu_cap": _parse_millicores(n.status.capacity.get("cpu", "0")),
|
||||
"mem_cap_mi": _parse_mebibytes(n.status.capacity.get("memory", "0")),
|
||||
"mem_alloc_mi": _parse_mebibytes(n.status.allocatable.get("memory", "0")),
|
||||
"os": n.status.node_info.os_image,
|
||||
"kubelet_version": n.status.node_info.kubelet_version,
|
||||
"mem_pressure": conditions.get("MemoryPressure", "False"),
|
||||
"disk_pressure": conditions.get("DiskPressure", "False"),
|
||||
"pid_pressure": conditions.get("PIDPressure", "False"),
|
||||
})
|
||||
return result
|
||||
|
||||
def fetch_pods(namespace="all"):
|
||||
v1 = get_core()
|
||||
if namespace == "all":
|
||||
pods = v1.list_pod_for_all_namespaces().items
|
||||
else:
|
||||
pods = v1.list_namespaced_pod(namespace=namespace).items
|
||||
|
||||
result = []
|
||||
for p in pods:
|
||||
result.append({
|
||||
"namespace": p.metadata.namespace,
|
||||
"name": p.metadata.name,
|
||||
"ready": _is_pod_ready(p),
|
||||
"phase": p.status.phase or "Unknown",
|
||||
"restarts": _get_pod_restarts(p),
|
||||
"node": p.spec.node_name or "—",
|
||||
"age": _format_age(p.metadata.creation_timestamp),
|
||||
})
|
||||
return result
|
||||
|
||||
def _is_pod_ready(pod):
|
||||
if not pod.status.conditions:
|
||||
return False
|
||||
for c in pod.status.conditions:
|
||||
if c.type == "Ready":
|
||||
return c.status == "True"
|
||||
return False
|
||||
|
||||
def _get_pod_restarts(pod):
|
||||
if not pod.status.container_statuses:
|
||||
return 0
|
||||
return sum(c.restart_count for c in pod.status.container_statuses)
|
||||
|
||||
def _format_age(ts):
|
||||
if not ts:
|
||||
return "—"
|
||||
now = datetime.now(timezone.utc)
|
||||
delta = now - ts
|
||||
if delta.days > 0:
|
||||
return f"{delta.days}d"
|
||||
hours = delta.seconds // 3600
|
||||
if hours > 0:
|
||||
return f"{hours}h"
|
||||
minutes = delta.seconds // 60
|
||||
return f"{minutes}m"
|
||||
|
||||
def _parse_millicores(val):
|
||||
if not val:
|
||||
return 0.0
|
||||
if isinstance(val, (int, float)):
|
||||
return float(val)
|
||||
if val.endswith("m"):
|
||||
return float(val[:-1]) / 1000.0
|
||||
return float(val)
|
||||
|
||||
def _parse_mebibytes(val):
|
||||
if not val:
|
||||
return 0.0
|
||||
if isinstance(val, (int, float)):
|
||||
return float(val)
|
||||
|
||||
# Simple parser for Ki, Mi, Gi, Ti
|
||||
suffix_map = {
|
||||
"Ki": 1/1024,
|
||||
"Mi": 1,
|
||||
"Gi": 1024,
|
||||
"Ti": 1024*1024,
|
||||
}
|
||||
|
||||
for suffix, multiplier in suffix_map.items():
|
||||
if val.endswith(suffix):
|
||||
return float(val[:-len(suffix)]) * multiplier
|
||||
|
||||
# Bytes to MiB if no suffix
|
||||
return float(val) / (1024 * 1024)
|
||||
|
||||
Reference in New Issue
Block a user