mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
Align installer namespace/topology handling and add regression tests
Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
parent
4164cf9f87
commit
22420408f9
@ -7,6 +7,7 @@
|
||||
"cluster_name": "knoe-db",
|
||||
"desired_instances": 3,
|
||||
"eligible_nodes": [
|
||||
"gandalf.prole.org",
|
||||
"merlin.prole.org",
|
||||
"myrddin.prole.org",
|
||||
"pi.prole.org"
|
||||
@ -17,7 +18,7 @@
|
||||
"regenerated": false,
|
||||
"reused": true
|
||||
},
|
||||
"plan_hash": "10dccbb8fe133be4",
|
||||
"plan_id": "cnpg-placement-10dccbb8fe133be4",
|
||||
"plan_hash": "7036c5f65b6ef0db",
|
||||
"plan_id": "cnpg-placement-7036c5f65b6ef0db",
|
||||
"schema_version": "v1"
|
||||
}
|
||||
|
||||
@ -6,6 +6,6 @@ garage: local=3900 remote=3900 ns=knoe-system svc=garage address=0.0.0.0
|
||||
openbao: local=8200 remote=8200 ns=knoe-system svc=openbao address=0.0.0.0
|
||||
opentofu: local=8080 remote=8080 ns=knoe-system svc=opentofu address=0.0.0.0
|
||||
dashboard: local=8443 remote=443 ns=kubernetes-dashboard svc=kubernetes-dashboard-kong-proxy address=127.0.0.1
|
||||
postgres: local=5432 remote=5432 ns=knoe-db-17-7-043-18-140-17-7-043-18-140-17-7-043-18-140 svc=knoe-db-rw address=0.0.0.0
|
||||
postgres: local=5432 remote=5432 ns=knoe-db svc=knoe-db-rw address=0.0.0.0
|
||||
prometheus: local=9090 remote=9090 ns=monitoring svc=kps-kube-prometheus-stack-prometheus address=127.0.0.1
|
||||
grafana: local=3000 remote=80 ns=monitoring svc=kps-grafana address=0.0.0.0
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
; Prole Master Configuration File
|
||||
; Generated by install.py on 2026-03-29 19:03:07
|
||||
; Generated by install.py on 2026-03-29 22:54:52
|
||||
; This file is used as input for Ansible deployment and k8s cluster creation.
|
||||
|
||||
[User]
|
||||
@ -53,32 +53,25 @@ init_cluster.cluster_env = service
|
||||
init_cluster.deployment_target = prole-service-cluster
|
||||
init_cluster.gitops_enabled = false
|
||||
init_cluster.k3s_server_url = https://myrddin.prole.org:6443
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:HI4QqnXC0_CddPHY:xg1q0N70GVGDMn588uF15I3w39t3kjJVhEDxYyGNLluTwYK7VI8BsOJY9t5BnSbTY7EgYXW6NhvlV4djJ1dHQ4k2NjvjkEjwS3f8fgCyiv449RnDARCoAy6Y5_uXm9Ci5lkpjk6oLjmp_-i3WuXO8Ybj-9qIWwKe1ZeJxg==}
|
||||
init_cluster.k3s_token = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
|
||||
init_cluster.kerberos_enabled = true
|
||||
init_cluster.mode = k3s
|
||||
init_cluster.start_cluster = true
|
||||
init_cluster.supabase_analytics_enabled = true
|
||||
init_cluster.supabase_auth_enabled = true
|
||||
init_cluster.supabase_enabled = false
|
||||
init_cluster.supabase_meta_enabled = true
|
||||
init_cluster.supabase_realtime_enabled = true
|
||||
init_cluster.supabase_studio_enabled = false
|
||||
init_cluster.supabase_studio_url = db.prole.org
|
||||
init_cnpg_deploy.force_rollout = false
|
||||
init_cnpg_deploy.run_deploy = true
|
||||
init_db_build.run_build = true
|
||||
init_password.cluster_name = ${CLUSTER_NAME}
|
||||
init_password.db_host_port = 5432
|
||||
init_password.db_namespace = ${DATABASE_NAMESPACE}
|
||||
init_password.db_password = ${PROLE_SECRET:v1:PjGWbznR1azeYyuX:Aga3J66GBbGlb1d8QxX1eGR_Gtqw_lPiFu23RH4cNldXrL-S2lCsYf5_iFbYvZB_}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:PjGWbznR1azeYyuX:Aga3J66GBbGlb1d8QxX1eGR_Gtqw_lPiFu23RH4cNldXrL-S2lCsYf5_iFbYvZB_}
|
||||
init_password.db_password = ${PROLE_SECRET:v1:4QVLv6L_Zb6w1Zja:4PHGYWVndekQb3ZG7hjNAoIQ_T2yEZlS}
|
||||
init_password.db_password_confirm = ${PROLE_SECRET:v1:4QVLv6L_Zb6w1Zja:4PHGYWVndekQb3ZG7hjNAoIQ_T2yEZlS}
|
||||
init_password.db_username = root
|
||||
init_password.generate_ssh_key = true
|
||||
init_scripts.run_scripts = true
|
||||
kerberos_config.enabled = true
|
||||
kerberos_config.init_authority = false
|
||||
kerberos_config.kdc = 10.0.0.3
|
||||
kerberos_config.password = ${PROLE_SECRET:v1:vcWmYfhC0O42zIJa:Lt20P_X9KZLN-HC--wv0E48d4vEiH3z4B8rRCNjb7-RV9Ijx}
|
||||
kerberos_config.password = ${PROLE_SECRET:v1:lDmjQ4Cj45LYBD9_:9Aes8WNTZ97oYpi1aFvS8ODHDCQJJJCUBFgNrJBZ3a-Mgwp2}
|
||||
kerberos_config.realm = PROLE.ORG
|
||||
kerberos_config.test_connection = false
|
||||
kerberos_config.user = administrator
|
||||
@ -92,14 +85,14 @@ ollama_config.server_port = 11434
|
||||
ARGOCD_NAMESPACE = argocd
|
||||
CLUSTER_ENV = service
|
||||
CLUSTER_NAME = knoe-db
|
||||
CNPG_ELIGIBLE_NODES = merlin.prole.org,myrddin.prole.org,pi.prole.org
|
||||
CNPG_ELIGIBLE_NODES = gandalf.prole.org,merlin.prole.org,myrddin.prole.org,pi.prole.org
|
||||
CNPG_PLACEMENT_PLAN_FILE = /Users/chrisfu/dev/prole/conf/cnpg-placement/knoe-system-knoe-db.json
|
||||
CNPG_PLACEMENT_PLAN_HASH = 10dccbb8fe133be4
|
||||
CNPG_PLACEMENT_PLAN_ID = cnpg-placement-10dccbb8fe133be4
|
||||
CNPG_STAGE1_NODE = merlin.prole.org
|
||||
DATABASE_NAMESPACE = knoe-db-17-7-043-18-140-17-7-043-18-140-17-7-043-18-140
|
||||
CNPG_PLACEMENT_PLAN_HASH = 7036c5f65b6ef0db
|
||||
CNPG_PLACEMENT_PLAN_ID = cnpg-placement-7036c5f65b6ef0db
|
||||
CNPG_STAGE1_NODE = gandalf.prole.org
|
||||
DATABASE_NAMESPACE = knoe-db
|
||||
DB_HOST_PORT = 5432
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:JKAbSEfmoY7qCzGW:Kin_0B_SV7A5RDyNYfXFi43jbcI_DOe6UwCbNuLMQ67h6tVXef5Wui-ErAW7JEoJ}
|
||||
DB_PASSWORD = ${PROLE_SECRET:v1:FgbkzVICzbA4WX7l:t5eAnQUYrTfAiXuUUPgZ8rBJVqAp-WFA}
|
||||
DEPLOYMENT_MODE = k3s
|
||||
DEPLOYMENT_TARGET = prole-service-cluster
|
||||
DOCKER_PRELOAD = false
|
||||
@ -107,10 +100,9 @@ KNOE_DB_USER = root
|
||||
KUBECONTEXT = knoe.dev.prole.org
|
||||
OPTIONAL_WORKLOADS_MIN_READY_SCHEDULABLE_NODES = 2
|
||||
PROLE_K3S_SERVER = https://myrddin.prole.org:6443
|
||||
PROLE_K3S_TOKEN = K107c8c6000488eca4a067d8a73119bbae2f07b4ea1bac7d8d3dc9c500cbb8acb18::server:04572345810eae2f9619a6ed4239702b
|
||||
REGISTRY_NAMESPACE = knoe-system
|
||||
PROLE_K3S_TOKEN = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
|
||||
SERVICE_NAMESPACE = knoe-system
|
||||
SYNOLOGY_ROOTS = /synology/d001,/synology/d002,/synology/d003,/synology/d004
|
||||
SYNOLOGY_ROOTS = /synology/d001,/synology/d002,/synology/d003,/synology/d004,/synology/d005
|
||||
|
||||
[Welcome]
|
||||
; No configuration values captured yet for this section.
|
||||
@ -131,14 +123,7 @@ KDC_AUTO_DETECTED = 10.0.0.3
|
||||
KERBEROS_AUTO_ENABLED = True
|
||||
|
||||
[Port Forwards]
|
||||
PORT_FORWARD_K3S_MAPPING_1 = id=argocd;namespace=argocd;target=svc/argocd-server;address=0.0.0.0;hostPort=8081;servicePort=80;protocol=TCP;description=ArgoCD
|
||||
PORT_FORWARD_K3S_MAPPING_2 = id=garage;namespace=knoe-system;target=svc/garage;address=0.0.0.0;hostPort=3900;servicePort=3900;protocol=TCP;description=Garage S3
|
||||
PORT_FORWARD_K3S_MAPPING_3 = id=openbao;namespace=knoe-system;target=svc/openbao;address=0.0.0.0;hostPort=8200;servicePort=8200;protocol=TCP;description=OpenBao
|
||||
PORT_FORWARD_K3S_MAPPING_4 = id=opentofu;namespace=knoe-system;target=svc/opentofu;address=0.0.0.0;hostPort=8080;servicePort=8080;protocol=TCP;description=OpenTofu
|
||||
PORT_FORWARD_K3S_MAPPING_5 = id=dashboard;namespace=kubernetes-dashboard;target=svc/kubernetes-dashboard-kong-proxy;address=127.0.0.1;hostPort=8443;servicePort=443;protocol=TCP;description=Kubernetes Dashboard
|
||||
PORT_FORWARD_K3S_MAPPING_6 = id=postgres;namespace=${DATABASE_NAMESPACE};target=svc/knoe-db-rw;address=0.0.0.0;hostPort=5432;servicePort=5432;protocol=TCP;description=PostgreSQL (primary)
|
||||
PORT_FORWARD_K3S_MAPPING_7 = id=prometheus;namespace=monitoring;target=svc/kps-kube-prometheus-stack-prometheus;address=127.0.0.1;hostPort=9090;servicePort=9090;protocol=TCP;description=Prometheus UI
|
||||
PORT_FORWARD_K3S_MAPPING_8 = id=grafana;namespace=monitoring;target=svc/kps-grafana;address=0.0.0.0;hostPort=3000;servicePort=80;protocol=TCP;description=Grafana UI
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[System Environment]
|
||||
PROLE_CONF = /Users/chrisfu/dev/prole/conf
|
||||
@ -159,12 +144,13 @@ OLLAMA_SERVER_HOST = fairyland.prole.org
|
||||
OLLAMA_SERVER_PORT = 11434
|
||||
|
||||
[Optional Features]
|
||||
AT_REST_ENCRYPTION_ENABLED = true
|
||||
GITOPS_ENABLED = false
|
||||
KERBEROS_ENABLED = true
|
||||
SUPABASE_ENABLED = true
|
||||
AT_REST_ENCRYPTION_ENABLED = True
|
||||
GITOPS_ENABLED = False
|
||||
KERBEROS_ENABLED = True
|
||||
SUPABASE_ENABLED = False
|
||||
|
||||
[GitOps]
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[Database Creation]
|
||||
; No configuration values captured yet for this section.
|
||||
@ -172,34 +158,32 @@ SUPABASE_ENABLED = true
|
||||
[Initialize Cluster]
|
||||
ENVIRONMENT = service
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:k9baSCELlyPkN1nT:l45ZjmSD0i6jjG9WgAEAyqKIjvRY5RdVEvRAWmMDCTwAhWAv6vOqW3QTp98MjGlixQR9r2fD-DYg42w9BlBltiXZpBKy3g16YS5NxCq-yxU0FLtimuujreKnUuX0QQymOgyQsZD3co_zWoxwAaB0SfpxVoO2XbC6lxn1MQ==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
|
||||
|
||||
[Dev Cluster (k3d)]
|
||||
CLUSTER_ENV = k3d-knoe-dev-cluster
|
||||
CLUSTER_ENV = dev
|
||||
DISPLAY_NAME = knoe-dev-cluster
|
||||
KUBECTL_CONTEXT = service
|
||||
KUBECTL_CONTEXT = knoe.dev.prole.org
|
||||
MODE = k3d
|
||||
|
||||
[Service Cluster (k3s)]
|
||||
CLUSTER_ENV = prole-service-cluster
|
||||
DISPLAY_NAME = prole-service-cluster
|
||||
K3S_SERVER_URL = https://myrddin.prole.org:6443
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:Qt-K_U5RwLwTiQt9:FkC_VDt1A-NStDuWxzxrZCh5Ebo_WGSEef8CA8OUHcGUg3sPDYYmNsscNlBgD0lnEmgWapix7ZdEgRc0pe6d7grZhCrfg-Fe-5CPeISOWvgUVOedyl6MfAZPciD95aI47NKayoElYFx8dUOb4durP7yfh_2t8zj3rW2O1A==}
|
||||
K3S_TOKEN = ${PROLE_SECRET:v1:ncw40j3021elkcqU:OO4QXoXGTi4ptxeNvqFV9WXNsh_f2QEqSYnJxIR-MW-0J50oItM9NLQeQT3rY2Hju2C2VlSyeuKi3-MBGsm3wzQl5m1yjuVQ9MKrT20u2_pjyom3GuIhfCEx6agfadRHH_9lAio3wTVdvZqBgrTRyMQ67Um07eZRsXBTUA==}
|
||||
MODE = k3s
|
||||
PIPELINE_URL = http://myrddin.prole.org:8080
|
||||
|
||||
[Prod Cluster (k8s)]
|
||||
ARTIFACTS_DIR =
|
||||
ARTIFACTS_DIR = /Users/chrisfu/dev/prole/data/staging
|
||||
CLUSTER_ENV = prole-prod-cluster
|
||||
DISPLAY_NAME = prole-prod-cluster
|
||||
MODE = k8s
|
||||
PIPELINE_URL = http://myrddin.prole.org:8080
|
||||
|
||||
[Docker Build]
|
||||
; No configuration values captured yet for this section.
|
||||
|
||||
[Initialization Scripts]
|
||||
; No configuration values captured yet for this section.
|
||||
STATUS = Attempted
|
||||
|
||||
[Deployment]
|
||||
MODE = k3s
|
||||
|
||||
@ -257,7 +257,6 @@ STATUS_SCRIPTS=(
|
||||
"init_cloudnative_pg.sh"
|
||||
"init_openbao.sh"
|
||||
"init_kong.sh"
|
||||
"init_db_manager.sh"
|
||||
"init_monitoring.sh"
|
||||
"init_cnpg_backup.sh"
|
||||
"init_port_forwards.sh"
|
||||
|
||||
@ -463,8 +463,6 @@ def _mount_rows():
|
||||
|
||||
def _fs_stats(mountpoint: str):
|
||||
host_mount = Path("/host") / mountpoint.lstrip("/")
|
||||
if not host_mount.exists():
|
||||
return None, None, None
|
||||
try:
|
||||
st = os.statvfs(str(host_mount))
|
||||
size = int(st.f_frsize * st.f_blocks)
|
||||
|
||||
@ -844,6 +844,12 @@ class ClusterScreenMixin:
|
||||
return "dev"
|
||||
return key
|
||||
|
||||
def _is_valid_k8s_namespace(self, value: str | None) -> bool:
|
||||
ns = (value or "").strip()
|
||||
if not ns or len(ns) > 63:
|
||||
return False
|
||||
return re.fullmatch(r"[a-z0-9]([-a-z0-9]*[a-z0-9])?", ns) is not None
|
||||
|
||||
def _get_service_namespace(self) -> str:
|
||||
"""Resolve service namespace with UI/env/config fallbacks.
|
||||
|
||||
@ -852,23 +858,25 @@ class ClusterScreenMixin:
|
||||
"""
|
||||
|
||||
# 1) Explicit user/config/env choice wins.
|
||||
candidate_namespaces: list[str] = []
|
||||
try:
|
||||
ns = (self.service_namespace.get() or "").strip()
|
||||
candidate_namespaces.append((self.service_namespace.get() or "").strip())
|
||||
except Exception:
|
||||
ns = ""
|
||||
if not ns:
|
||||
try:
|
||||
ns = (
|
||||
pass
|
||||
try:
|
||||
candidate_namespaces.append(
|
||||
(
|
||||
(self.prole_cfg_data.get("Global", {}) or {})
|
||||
.get("SERVICE_NAMESPACE", "")
|
||||
.strip()
|
||||
)
|
||||
except Exception:
|
||||
ns = ""
|
||||
if not ns:
|
||||
ns = (os.environ.get("SERVICE_NAMESPACE") or "").strip()
|
||||
if ns:
|
||||
return ns
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
candidate_namespaces.append((os.environ.get("SERVICE_NAMESPACE") or "").strip())
|
||||
for ns in candidate_namespaces:
|
||||
if self._is_valid_k8s_namespace(ns):
|
||||
return ns
|
||||
|
||||
# 2) If the cluster is reachable, infer the common services namespace.
|
||||
inferred = ""
|
||||
@ -876,7 +884,7 @@ class ClusterScreenMixin:
|
||||
inferred = (self._infer_common_services_namespace() or "").strip()
|
||||
except Exception:
|
||||
inferred = ""
|
||||
if inferred:
|
||||
if self._is_valid_k8s_namespace(inferred):
|
||||
return inferred
|
||||
|
||||
# 3) Stable defaults for known environments.
|
||||
|
||||
@ -254,8 +254,10 @@ class ClusterNodesScreenMixin:
|
||||
|
||||
def _cfg(_evt=None):
|
||||
try:
|
||||
req_w = table.winfo_reqwidth()
|
||||
canvas_w = canvas.winfo_width()
|
||||
canvas.itemconfigure(inner, width=max(canvas_w, req_w))
|
||||
canvas.configure(scrollregion=canvas.bbox("all"))
|
||||
canvas.itemconfigure(inner, height=canvas.winfo_height())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -527,20 +529,28 @@ class ClusterNodesScreenMixin:
|
||||
self._overlay_widgets.append(msg_frame)
|
||||
msg_win = self.bg_canvas.create_window(48, 292, window=msg_frame, anchor="nw", width=905, height=86)
|
||||
self._canvas_items.append(msg_win)
|
||||
msg_text = "\n".join(messages[-4:]) if messages else "Starting topology discovery"
|
||||
msg_lbl = tk.Label(
|
||||
msg_text = "\n".join(messages) if messages else "Starting topology discovery"
|
||||
msg_scroll = tk.Scrollbar(msg_frame, orient="vertical")
|
||||
msg_scroll.pack(side="right", fill="y")
|
||||
msg_out = tk.Text(
|
||||
msg_frame,
|
||||
text=msg_text,
|
||||
bg="#f6f8fb",
|
||||
fg="#1d1d1f",
|
||||
justify="left",
|
||||
anchor="w",
|
||||
font=("SF Pro Text", 10),
|
||||
padx=10,
|
||||
pady=8,
|
||||
relief="flat",
|
||||
highlightthickness=0,
|
||||
bd=0,
|
||||
wrap="word",
|
||||
yscrollcommand=msg_scroll.set,
|
||||
)
|
||||
msg_lbl.pack(fill="both", expand=True)
|
||||
self._overlay_widgets.append(msg_lbl)
|
||||
msg_out.pack(side="left", fill="both", expand=True)
|
||||
msg_scroll.configure(command=msg_out.yview)
|
||||
msg_out.insert("1.0", msg_text)
|
||||
msg_out.see("end")
|
||||
msg_out.configure(state="disabled")
|
||||
self._overlay_widgets.extend([msg_scroll, msg_out])
|
||||
|
||||
topology = state.get("topology") if isinstance(state.get("topology"), ClusterTopology) else None
|
||||
y = self._cluster_nodes_render_summary_cards(topology, 48, 388)
|
||||
|
||||
@ -402,6 +402,9 @@ class DatabaseOptionsScreenMixin:
|
||||
elif ext_id == "pg_cron":
|
||||
# pg_cron is bundled with Percona PG packages in this build flow.
|
||||
pkg = ""
|
||||
elif ext_id == "pg_tde":
|
||||
# pg_tde uses Percona-specific package naming handled separately in templates.
|
||||
pkg = ""
|
||||
elif ext_id == "pgbadger":
|
||||
pkg = "percona-pgbadger"
|
||||
elif ext_id == "pg_stat_monitor":
|
||||
|
||||
@ -175,7 +175,6 @@ class ServicesScreenMixin:
|
||||
]
|
||||
scripts.append(("Prole DB Backup", "init_cnpg_backup.sh"))
|
||||
scripts.append(("Kong API Gateway", "init_kong.sh"))
|
||||
scripts.append(("DB Manager", "init_db_manager.sh"))
|
||||
scripts.append(("Monitoring", "init_monitoring.sh"))
|
||||
mode = self._deployment_mode()
|
||||
if mode != "k3d":
|
||||
@ -228,8 +227,19 @@ class ServicesScreenMixin:
|
||||
env["KNOE_DB_USER"] = self.db_username.get()
|
||||
env["DB_PASSWORD"] = password
|
||||
env["GRAFANA_ADMIN_PASSWORD"] = password
|
||||
env["NAMESPACE"] = (self.db_namespace.get() or "").strip()
|
||||
env["PROLE_NAMESPACE"] = env["NAMESPACE"]
|
||||
db_namespace = (self.db_namespace.get() or "").strip()
|
||||
if not db_namespace:
|
||||
db_namespace = (
|
||||
((getattr(self, "prole_cfg_data", {}) or {}).get("Global", {}) or {})
|
||||
.get("DATABASE_NAMESPACE", "")
|
||||
.strip()
|
||||
)
|
||||
if not db_namespace:
|
||||
db_namespace = "default"
|
||||
|
||||
env["DATABASE_NAMESPACE"] = db_namespace
|
||||
env["NAMESPACE"] = db_namespace
|
||||
env["PROLE_NAMESPACE"] = db_namespace
|
||||
env["SERVICE_NAMESPACE"] = self._get_service_namespace()
|
||||
if self.kerberos_realm.get().strip():
|
||||
env["KRB5_REALM"] = self.kerberos_realm.get().strip()
|
||||
@ -325,7 +335,11 @@ class ServicesScreenMixin:
|
||||
pass
|
||||
|
||||
rc_svc = 0
|
||||
svc_namespace = env.get("NAMESPACE") or env.get("SERVICE_NAMESPACE") or "default"
|
||||
svc_namespace = (
|
||||
env.get("SERVICE_NAMESPACE")
|
||||
or env.get("NAMESPACE")
|
||||
or "default"
|
||||
)
|
||||
registry_ns = env.get("REGISTRY_NAMESPACE") or svc_namespace
|
||||
project_root = getattr(self.controller, "project_root", PROJECT_ROOT)
|
||||
|
||||
@ -542,56 +556,7 @@ class ServicesScreenMixin:
|
||||
"Skipping Kong because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 6. init_db_manager.sh start
|
||||
if overall_success:
|
||||
script = "init_db_manager.sh"
|
||||
if script in self.script_consoles:
|
||||
_select_tab(script)
|
||||
self.script_consoles[script].clear()
|
||||
self.script_consoles[script].write(f"Running {script} start...\n")
|
||||
|
||||
log_path = _log_path_for(script)
|
||||
self._record_install_log(log_path)
|
||||
try:
|
||||
log_fp = log_path.open("a", encoding="utf-8")
|
||||
except Exception:
|
||||
log_fp = None
|
||||
|
||||
def _dbmgr_line(line):
|
||||
self.script_consoles[script].write(line)
|
||||
self._process_script_output_line(line)
|
||||
if log_fp:
|
||||
try:
|
||||
log_fp.write(line)
|
||||
log_fp.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
rc_dbmgr = self.controller.run_script(
|
||||
script, args=mode_args + ["start"], env=env, on_line=_dbmgr_line
|
||||
)
|
||||
if log_fp:
|
||||
try:
|
||||
log_fp.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if rc_dbmgr != 0:
|
||||
self.script_consoles[script].write(
|
||||
f"\nERROR: {script} start failed with code {rc_dbmgr}\n"
|
||||
)
|
||||
overall_success = False
|
||||
else:
|
||||
self.script_consoles[script].write(
|
||||
f"\n{script} completed successfully.\n"
|
||||
)
|
||||
else:
|
||||
if "init_db_manager.sh" in self.script_consoles:
|
||||
self.script_consoles["init_db_manager.sh"].write(
|
||||
"Skipping DB Manager because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 7. init_monitoring.sh initialize
|
||||
# 6. init_monitoring.sh initialize
|
||||
if overall_success and opt_allowed:
|
||||
script = "init_monitoring.sh"
|
||||
_select_tab(script)
|
||||
@ -688,7 +653,7 @@ class ServicesScreenMixin:
|
||||
"Skipping Monitoring initialization because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 8. init_nginx_ingress.sh initialize (k3s only — not used in k3d)
|
||||
# 7. init_nginx_ingress.sh initialize (k3s only — not used in k3d)
|
||||
if overall_success and mode != "k3d":
|
||||
script = "init_nginx_ingress.sh"
|
||||
_select_tab(script)
|
||||
@ -736,7 +701,7 @@ class ServicesScreenMixin:
|
||||
"Skipping Nginx Ingress because previous steps failed.\n"
|
||||
)
|
||||
|
||||
# 9. init_port_forwards.sh start (k3d only — not used in k3s)
|
||||
# 8. init_port_forwards.sh start (k3d only — not used in k3s)
|
||||
if overall_success and mode != "k3s":
|
||||
script = "init_port_forwards.sh"
|
||||
if script in self.script_consoles:
|
||||
@ -799,7 +764,10 @@ class ServicesScreenMixin:
|
||||
)
|
||||
|
||||
# Verify critical secrets
|
||||
ns = env.get("NAMESPACE", "default")
|
||||
ns = (
|
||||
str(env.get("DATABASE_NAMESPACE") or env.get("NAMESPACE") or "default").strip()
|
||||
or "default"
|
||||
)
|
||||
self.script_consoles["init_cloudnative_pg.sh"].write(
|
||||
f"\nVerifying critical secrets in namespace {ns}...\n"
|
||||
)
|
||||
|
||||
@ -1 +1 @@
|
||||
145
|
||||
150
|
||||
9
prole.sh
9
prole.sh
@ -1215,18 +1215,15 @@ PY
|
||||
# 6. Kong API Gateway
|
||||
run_init_script init_kong.sh -n "${NAMESPACE:-knoe-db}" start
|
||||
|
||||
# 7. DB Manager
|
||||
run_init_script init_db_manager.sh start
|
||||
|
||||
# 8. Monitoring (Grafana, Prometheus)
|
||||
# 7. Monitoring (Grafana, Prometheus)
|
||||
run_init_script init_monitoring.sh initialize
|
||||
|
||||
# 9. Nginx Ingress (non-k3d only)
|
||||
# 8. Nginx Ingress (non-k3d only)
|
||||
if [[ "$local_mode" != "k3d" ]]; then
|
||||
run_init_script init_nginx_ingress.sh initialize
|
||||
fi
|
||||
|
||||
# 10. Port forwards (k3d only)
|
||||
# 9. Port forwards (k3d only)
|
||||
if [[ "$local_mode" == "k3d" && -x "${ROOT_DIR}/etc/init_port_forwards.sh" ]]; then
|
||||
run_init_script init_port_forwards.sh -c "${PROLE_CFG_DEFAULT}" start
|
||||
fi
|
||||
|
||||
@ -276,7 +276,6 @@ class ProleDeployment(ProleInstaller):
|
||||
- init_cloudnative_pg.sh (CNPG operator)
|
||||
- init_kong.sh (API gateway)
|
||||
- init_postgrest.sh (PostgREST on port 3000)
|
||||
- init_db_manager.sh (DB manager)
|
||||
- init_monitoring.sh (Prometheus/Grafana stack)
|
||||
- supabase/deploy.sh (Supabase stack, if enabled)
|
||||
"""
|
||||
@ -293,7 +292,6 @@ class ProleDeployment(ProleInstaller):
|
||||
("init_common_services.sh", svc_args),
|
||||
("init_kong.sh", ["start"]),
|
||||
("init_postgrest.sh", ["start"]),
|
||||
("init_db_manager.sh", ["start"]),
|
||||
("init_monitoring.sh", ["initialize"]),
|
||||
]
|
||||
|
||||
|
||||
@ -1,55 +1,58 @@
|
||||
Network Discovery Summary:
|
||||
Primary Router: 10.0.0.1 (eero_5d:50:f2)
|
||||
DNS Servers: 10.0.0.4, 10.0.0.5, 100.100.100.100
|
||||
DNS Servers: 10.0.0.4, 100.100.100.100, 10.0.0.5
|
||||
Detected Devices:
|
||||
- 10.0.0.35 [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): Ports [22, 445, 5900, 11434, 88], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Ollama', 'Active Directory Related']
|
||||
- 10.0.0.4 [b8:27:eb:b3:6f:2b] (RaspberryPiF_b3:6f:2b): Ports [22, 53, 443, 2049], Services: ['DNS', 'SSH', 'NFS', 'Web Server']
|
||||
- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.26 [c8:db:26:08:52:f8] (Logitech_08:52:f8): Ports [], Services: []
|
||||
- 10.0.0.5 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
|
||||
- 10.0.0.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.22 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.203 [00:11:32:3b:2f:08] (Synology_3b:2f:08): Ports [22, 80, 443, 2049, 445], Services: ['SSH', 'NFS', 'Web Server', 'SMB/CIFS (Possible Windows/AD)']
|
||||
- 10.0.0.3 [2c:cf:67:8c:2b:47] (RaspberryPi_8c:2b:47): Ports [22, 53, 80, 443, 2049, 445, 5900, 88, 389, 636], Services: ['DNS', 'SSH', 'NFS', 'VNC', 'Web Server', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.5 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
|
||||
- 10.0.0.4 [b8:27:eb:b3:6f:2b] (RaspberryPiF_b3:6f:2b): Ports [22, 53, 443, 2049], Services: ['DNS', 'SSH', 'NFS', 'Web Server']
|
||||
- 10.0.0.6 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.32 [48:a6:b8:a4:8e:cc] (Sonos_a4:8e:cc): Ports [], Services: []
|
||||
- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.1 [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
|
||||
- 10.0.0.7 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.2 [dc:a4:ca:ea:1a:2f] (Apple_ea:1a:2f): Ports [], Services: []
|
||||
- 10.0.0.22 [2c:cf:67:45:8d:97] (RaspberryPi_45:8d:97): Ports [22, 80, 443], Services: ['SSH', 'Web Server']
|
||||
- 10.0.0.26 [c8:db:26:08:52:f8] (Logitech_08:52:f8): Ports [], Services: []
|
||||
- 10.0.0.204 [50:eb:f6:56:f3:26] (ASUSTekCOMPU_56:f3:26): Ports [22, 3389, 445, 5900, 11434], Services: ['SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'RDP (Windows)', 'Ollama']
|
||||
- 10.0.0.33 [ec:b5:fa:b0:76:e4] (PhilipsLight_b0:76:e4): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.34 [3a:b4:ce:79:b0:55] (3a:b4:ce:79:b0:55): Ports [], Services: []
|
||||
- 10.0.0.37 [54:07:7d:22:c0:b2] (Netgear_22:c0:b2): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.45 [f8:b4:6a:30:a4:1f] (HewlettPacka_30:a4:1f): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
|
||||
- 10.0.0.48 [0c:fe:45:53:f4:3c] (SonyInteract_53:f4:3c): Ports [], Services: []
|
||||
- 10.0.0.52 [2c:cf:67:ca:93:ce] (RaspberryPi_ca:93:ce): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.205 [a8:20:66:28:12:e7] (Apple_28:12:e7): Ports [22, 53, 445, 5900, 88], Services: ['DNS', 'SSH', 'VNC', 'SMB/CIFS (Possible Windows/AD)', 'Active Directory Related']
|
||||
- 10.0.0.73 [4c:a9:19:b3:12:f8] (TuyaSmart_b3:12:f8): Ports [], Services: []
|
||||
- 10.0.0.58 [7e:46:74:2a:b0:b2] (7e:46:74:2a:b0:b2): Ports [], Services: []
|
||||
- 10.0.0.60 [c6:d0:1a:97:9f:e9] (c6:d0:1a:97:9f:e9): Ports [], Services: []
|
||||
- 10.0.0.35\ [7e:19:9a:fe:9f:ed] (7e:19:9a:fe:9f:ed): Ports [], Services: []
|
||||
- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
|
||||
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.46 [d4:f7:d5:40:ab:17] (SonyInteract_40:ab:17): Ports [], Services: []
|
||||
- 10.0.0.41 [b8:27:eb:57:10:d9] (RaspberryPiF_57:10:d9): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.107 [b8:06:0d:b7:7c:56] (TuyaSmart_b7:7c:56): Ports [], Services: []
|
||||
- 10.0.0.100 [24:fc:e5:51:cf:74] (SamsungElect_51:cf:74): Ports [], Services: []
|
||||
- 10.0.0.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
|
||||
- 10.0.0.189 [00:17:88:a3:2f:cc] (PhilipsLight_a3:2f:cc): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.73 [4c:a9:19:b3:12:f8] (TuyaSmart_b3:12:f8): Ports [], Services: []
|
||||
- 10.0.0.206 [00:a0:de:a2:0b:ef] (Yamaha_a2:0b:ef): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.94 [f0:20:ff:bf:9a:19] (Intel_bf:9a:19): Ports [], Services: []
|
||||
Note: Potential host for NVIDIA cards (PC Hardware vendor)
|
||||
- 10.0.0.95 [b8:27:eb:88:3a:41] (RaspberryPiF_88:3a:41): Ports [22, 2049, 5900], Services: ['SSH', 'NFS', 'VNC']
|
||||
- 10.0.0.106 [48:a6:b8:a7:50:60] (Sonos_a7:50:60): Ports [], Services: []
|
||||
- 10.0.0.99 [20:28:bc:f1:dc:ba] (Visionscape_f1:dc:ba): Ports [], Services: []
|
||||
- 10.0.0.111 [ca:0b:6c:5d:0a:e5] (ca:0b:6c:5d:0a:e5): Ports [], Services: []
|
||||
- 10.0.0.117 [40:f5:20:1e:5e:91] (Espressif_1e:5e:91): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.113 [60:81:10:92:02:4e] (Apple_92:02:4e): Ports [], Services: []
|
||||
- 10.0.0.123 [3c:ef:8c:96:e3:3c] (ZhejiangDahu_96:e3:3c): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.124 [9c:8e:cd:02:73:c4] (AmcrestTechn_02:73:c4): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.112 [f8:33:31:40:58:c0] (TexasInstrum_40:58:c0): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.117 [40:f5:20:1e:5e:91] (Espressif_1e:5e:91): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.127 [58:55:ca:4a:60:6e] (Apple_4a:60:6e): Ports [22], Services: ['SSH']
|
||||
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.125 [a0:60:32:04:73:ba] (AmcrestTechn_04:73:ba): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.130 [f8:bb:bf:a3:84:eb] (eero_a3:84:eb): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.143 [60:5f:8d:7a:aa:32] (eero_7a:aa:32): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.128 [20:c9:d0:94:30:dd] (Apple_94:30:dd): Ports [22, 53, 5900], Services: ['DNS', 'SSH', 'VNC']
|
||||
- 10.0.0.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): Ports [], Services: []
|
||||
- 10.0.0.145 [7c:a6:b0:04:db:1c] (7c:a6:b0:04:db:1c): Ports [], Services: []
|
||||
- 10.0.0.175 [28:80:88:e4:10:1a] (Netgear_e4:10:1a): Ports [80], Services: ['Web Server']
|
||||
- 10.0.0.155 [62:17:97:b3:06:a1] (62:17:97:b3:06:a1): Ports [], Services: []
|
||||
- 10.0.0.160 [96:c3:fd:58:02:6c] (96:c3:fd:58:02:6c): Ports [], Services: []
|
||||
- 10.0.0.170 [b0:ee:7b:ca:73:99] (Roku_ca:73:99): Ports [], Services: []
|
||||
- 10.0.0.188 [00:16:6c:c5:1f:54] (SamsungElect_c5:1f:54): Ports [80, 443], Services: ['Web Server']
|
||||
- 10.0.0.196 [60:5f:8d:88:08:f2] (eero_88:08:f2): Ports [53], Services: ['DNS']
|
||||
- 10.0.0.208 [a0:ad:9f:30:84:5f] (ASUSTekCOMPU_30:84:5f): Ports [], Services: []
|
||||
- 10.0.0.1\ [9c:57:bc:5d:50:f2] (eero_5d:50:f2): Ports [], Services: []
|
||||
- 10.0.0.58 [7e:46:74:2a:b0:b2] (7e:46:74:2a:b0:b2): Ports [], Services: []
|
||||
- 10.0.0.199 [7c:a6:b0:01:a7:33] (7c:a6:b0:01:a7:33): Ports [], Services: []
|
||||
- 10.0.0.60 [c6:d0:1a:97:9f:e9] (c6:d0:1a:97:9f:e9): Ports [], Services: []
|
||||
Ollama Instances found at: 10.0.0.35, 10.0.0.204
|
||||
|
||||
Ansible Inventory Summary:
|
||||
|
||||
@ -116,7 +116,6 @@ _STATUS_SCRIPTS: list[str] = [
|
||||
"init_openbao.sh",
|
||||
"init_kong.sh",
|
||||
"init_postgrest.sh",
|
||||
"init_db_manager.sh",
|
||||
"init_monitoring.sh",
|
||||
"init_cnpg_backup.sh",
|
||||
"init_port_forwards.sh",
|
||||
|
||||
132
tests/installer/test_cluster_nodes_render_smoke.py
Normal file
132
tests/installer/test_cluster_nodes_render_smoke.py
Normal file
@ -0,0 +1,132 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import knoe.ui.screens.cluster_nodes as cluster_nodes
|
||||
from knoe.ui.screens.cluster_nodes import ClusterNodesScreenMixin
|
||||
|
||||
|
||||
def test_render_cluster_nodes_page_topology_messages_are_scrollable(monkeypatch):
|
||||
class _DummyWidget:
|
||||
def __init__(self, *_args, **_kwargs):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
return None
|
||||
|
||||
def place_forget(self):
|
||||
return None
|
||||
|
||||
def configure(self, **_kwargs):
|
||||
return None
|
||||
|
||||
class _DummyFrame(_DummyWidget):
|
||||
def pack(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
class _DummyScrollbar(_DummyWidget):
|
||||
instances: list["_DummyScrollbar"] = []
|
||||
|
||||
def __init__(self, *_args, **kwargs):
|
||||
super().__init__()
|
||||
self.command = kwargs.get("command")
|
||||
_DummyScrollbar.instances.append(self)
|
||||
|
||||
def pack(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
def set(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
def configure(self, **kwargs):
|
||||
if "command" in kwargs:
|
||||
self.command = kwargs["command"]
|
||||
|
||||
class _DummyText(_DummyWidget):
|
||||
instances: list["_DummyText"] = []
|
||||
|
||||
def __init__(self, *_args, **kwargs):
|
||||
super().__init__()
|
||||
self.yscrollcommand = kwargs.get("yscrollcommand")
|
||||
self.contents = ""
|
||||
self.state = "normal"
|
||||
_DummyText.instances.append(self)
|
||||
|
||||
def pack(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
def insert(self, _idx: str, text: str):
|
||||
self.contents += text
|
||||
|
||||
def see(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
def configure(self, **kwargs):
|
||||
if "state" in kwargs:
|
||||
self.state = kwargs["state"]
|
||||
|
||||
def yview(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(cluster_nodes.tk, "Frame", _DummyFrame)
|
||||
monkeypatch.setattr(cluster_nodes.tk, "Scrollbar", _DummyScrollbar)
|
||||
monkeypatch.setattr(cluster_nodes.tk, "Text", _DummyText)
|
||||
monkeypatch.setattr(cluster_nodes.tk, "Button", _DummyWidget)
|
||||
|
||||
class _DummyCanvas:
|
||||
def __init__(self):
|
||||
self._next_id = 1
|
||||
|
||||
def create_text(self, *_args, **_kwargs):
|
||||
item = self._next_id
|
||||
self._next_id += 1
|
||||
return item
|
||||
|
||||
def create_window(self, *_args, **_kwargs):
|
||||
item = self._next_id
|
||||
self._next_id += 1
|
||||
return item
|
||||
|
||||
class _DummyApp(ClusterNodesScreenMixin):
|
||||
def __init__(self):
|
||||
self.bg_canvas = _DummyCanvas()
|
||||
self._canvas_items: list[int] = []
|
||||
self._overlay_widgets: list[object] = []
|
||||
self._cluster_nodes_topology_state = {
|
||||
"phase": "complete",
|
||||
"messages": [f"line-{i}" for i in range(1, 7)],
|
||||
"topology": None,
|
||||
"topology_path": "",
|
||||
"selected": set(),
|
||||
"busy": True,
|
||||
"expanded": None,
|
||||
}
|
||||
|
||||
def _clear_canvas_page(self):
|
||||
return None
|
||||
|
||||
def _should_show_cluster_nodes_screen(self):
|
||||
return True
|
||||
|
||||
def _render_title(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
def _render_paragraph(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
def _cluster_nodes_start_discovery(self, force: bool = False):
|
||||
return None
|
||||
|
||||
def _cluster_nodes_render_summary_cards(self, _topology, _x, y):
|
||||
return y
|
||||
|
||||
def _cluster_nodes_render_table(self, _topology, _x, y):
|
||||
return y
|
||||
|
||||
app = _DummyApp()
|
||||
app._render_cluster_nodes_page()
|
||||
|
||||
assert _DummyScrollbar.instances
|
||||
assert _DummyText.instances
|
||||
assert _DummyText.instances[0].state == "disabled"
|
||||
assert "line-1" in _DummyText.instances[0].contents
|
||||
assert "line-6" in _DummyText.instances[0].contents
|
||||
assert _DummyScrollbar.instances[0].command is not None
|
||||
@ -312,15 +312,17 @@ class TestRunPostApplyScripts:
|
||||
dep.prole_cfg_data["Deployment"] = {"KUBECONFIG_PATH": ""}
|
||||
dep._run_script = MagicMock(return_value=0)
|
||||
dep._run_supabase_deploy = MagicMock(return_value=True)
|
||||
assert dep._run_post_apply_scripts() is True
|
||||
assert dep._run_script.call_count == 6
|
||||
with patch("prole.deployment.cnpg_initialize", return_value=None):
|
||||
assert dep._run_post_apply_scripts() is True
|
||||
assert dep._run_script.call_count == 4
|
||||
|
||||
def test_script_failure(self, tmp_path):
|
||||
dep, ctrl = _make_deployment(tmp_path)
|
||||
dep.prole_cfg_data["Deployment"] = {"KUBECONFIG_PATH": ""}
|
||||
dep._run_script = MagicMock(return_value=1)
|
||||
dep._run_supabase_deploy = MagicMock(return_value=True)
|
||||
assert dep._run_post_apply_scripts() is False
|
||||
with patch("prole.deployment.cnpg_initialize", return_value=None):
|
||||
assert dep._run_post_apply_scripts() is False
|
||||
|
||||
def test_kerberos_flag(self, tmp_path):
|
||||
dep, ctrl = _make_deployment(
|
||||
@ -332,7 +334,8 @@ class TestRunPostApplyScripts:
|
||||
dep.prole_cfg_data["Deployment"] = {"KUBECONFIG_PATH": ""}
|
||||
dep._run_script = MagicMock(return_value=0)
|
||||
dep._run_supabase_deploy = MagicMock(return_value=True)
|
||||
dep._run_post_apply_scripts()
|
||||
with patch("prole.deployment.cnpg_initialize", return_value=None):
|
||||
dep._run_post_apply_scripts()
|
||||
first_call = dep._run_script.call_args_list[0]
|
||||
assert "init_common_services" in str(first_call)
|
||||
assert "-k" in str(first_call)
|
||||
|
||||
@ -139,9 +139,304 @@ def test_tk_init_scripts_runner_invokes_init_certmgr(tmp_path, monkeypatch):
|
||||
assert "init_common_services.sh" not in called_scripts
|
||||
# CNPG is now Python-owned; shell script must never be dispatched
|
||||
assert "init_cloudnative_pg.sh" not in called_scripts
|
||||
assert "init_db_manager.sh" not in called_scripts
|
||||
assert screen.prole_cfg_data["Initialization Scripts"]["STATUS"] == "Completed"
|
||||
|
||||
|
||||
def test_tk_init_scripts_common_services_prefers_service_namespace(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
import knoe.ui.screens.services as services
|
||||
from knoe.ui.screens.services import ServicesScreenMixin
|
||||
|
||||
class _Var:
|
||||
def __init__(self, value=""):
|
||||
self._value = value
|
||||
|
||||
def get(self):
|
||||
return self._value
|
||||
|
||||
class _Console:
|
||||
def __init__(self):
|
||||
self.buf: list[str] = []
|
||||
|
||||
def clear(self):
|
||||
self.buf.clear()
|
||||
|
||||
def write(self, content: str):
|
||||
self.buf.append(content)
|
||||
|
||||
class _Widget:
|
||||
def winfo_exists(self):
|
||||
return False
|
||||
|
||||
def configure(self, **_kwargs):
|
||||
return None
|
||||
|
||||
class _Canvas:
|
||||
def winfo_exists(self):
|
||||
return False
|
||||
|
||||
def itemconfig(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
class _ImmediateThread:
|
||||
def __init__(self, target, daemon=True):
|
||||
self._target = target
|
||||
|
||||
def start(self):
|
||||
self._target()
|
||||
|
||||
monkeypatch.setattr(services.threading, "Thread", _ImmediateThread)
|
||||
monkeypatch.setattr(services, "cnpg_initialize", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.registry_ops, "update", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.garage_store_ops, "update", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.opentofu_ops, "update", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.monitoring_ops, "initialize", lambda **_kw: None)
|
||||
|
||||
openbao_calls: list[dict] = []
|
||||
|
||||
def _openbao_update(**kwargs):
|
||||
openbao_calls.append(kwargs)
|
||||
|
||||
monkeypatch.setattr(services.openbao_ops, "update", _openbao_update)
|
||||
|
||||
class _DummyScreen(ServicesScreenMixin):
|
||||
KUBECTL_STATUS_TAB = "__kubectl_status__"
|
||||
|
||||
def __init__(self):
|
||||
self._action_flags = {}
|
||||
self.controller = MagicMock()
|
||||
self.controller.run_script.return_value = 0
|
||||
|
||||
self._init_scripts_button = _Widget()
|
||||
self.bg_canvas = _Canvas()
|
||||
self._init_scripts_status_label = 0
|
||||
|
||||
self._script_tab_index = {}
|
||||
self.script_consoles = {
|
||||
"init_common_services.sh": _Console(),
|
||||
"common_services_log": _Console(),
|
||||
"init_certmgr.sh": _Console(),
|
||||
"init_cloudnative_pg.sh": _Console(),
|
||||
"init_cnpg_backup.sh": _Console(),
|
||||
"init_kong.sh": _Console(),
|
||||
"init_monitoring.sh": _Console(),
|
||||
"init_nginx_ingress.sh": _Console(),
|
||||
"init_port_forwards.sh": _Console(),
|
||||
self.KUBECTL_STATUS_TAB: _Console(),
|
||||
}
|
||||
|
||||
self.db_username = _Var("prole")
|
||||
self.db_password = _Var("pw")
|
||||
self.db_namespace = _Var("knoe-db")
|
||||
|
||||
self.kerberos_enabled = _Var(False)
|
||||
self.kerberos_realm = _Var("")
|
||||
self.kerberos_kdc = _Var("")
|
||||
self.kerberos_user = _Var("")
|
||||
self.kerberos_password = _Var("")
|
||||
self.cluster_env = _Var("prole-service-cluster")
|
||||
|
||||
self.prole_cfg_data = {"Initialization Scripts": {"STATUS": ""}}
|
||||
self._scripts_success = False
|
||||
|
||||
def safe_after(self, fn, delay: int = 0):
|
||||
return fn()
|
||||
|
||||
def _deployment_mode(self):
|
||||
return "k3s"
|
||||
|
||||
def _resolve_prole_logs_dir(self):
|
||||
return tmp_path
|
||||
|
||||
def _get_service_namespace(self):
|
||||
return "knoe-system"
|
||||
|
||||
def _sync_port_forward_mappings(self):
|
||||
return None
|
||||
|
||||
def _update_legacy_port_mapping(self):
|
||||
return None
|
||||
|
||||
def _record_install_log(self, _path):
|
||||
return None
|
||||
|
||||
def _process_script_output_line(self, _line: str):
|
||||
return None
|
||||
|
||||
def _run_cmd_capture(self, _args):
|
||||
return 0, ""
|
||||
|
||||
def _save_prole_cfg(self):
|
||||
return None
|
||||
|
||||
def update_footer(self):
|
||||
return None
|
||||
|
||||
def check_services_status_async(self):
|
||||
return None
|
||||
|
||||
screen = _DummyScreen()
|
||||
screen.run_init_scripts()
|
||||
|
||||
assert openbao_calls
|
||||
assert openbao_calls[0]["namespace"] == "knoe-system"
|
||||
|
||||
|
||||
def test_tk_init_scripts_uses_fallback_database_namespace_for_secret_checks(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
import knoe.ui.screens.services as services
|
||||
from knoe.ui.screens.services import ServicesScreenMixin
|
||||
|
||||
class _Var:
|
||||
def __init__(self, value=""):
|
||||
self._value = value
|
||||
|
||||
def get(self):
|
||||
return self._value
|
||||
|
||||
class _Console:
|
||||
def __init__(self):
|
||||
self.buf: list[str] = []
|
||||
|
||||
def clear(self):
|
||||
self.buf.clear()
|
||||
|
||||
def write(self, content: str):
|
||||
self.buf.append(content)
|
||||
|
||||
class _Widget:
|
||||
def winfo_exists(self):
|
||||
return False
|
||||
|
||||
def configure(self, **_kwargs):
|
||||
return None
|
||||
|
||||
class _Canvas:
|
||||
def winfo_exists(self):
|
||||
return False
|
||||
|
||||
def itemconfig(self, *_args, **_kwargs):
|
||||
return None
|
||||
|
||||
class _ImmediateThread:
|
||||
def __init__(self, target, daemon=True):
|
||||
self._target = target
|
||||
|
||||
def start(self):
|
||||
self._target()
|
||||
|
||||
monkeypatch.setattr(services.threading, "Thread", _ImmediateThread)
|
||||
monkeypatch.setattr(services, "cnpg_initialize", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.registry_ops, "update", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.openbao_ops, "update", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.garage_store_ops, "update", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.opentofu_ops, "update", lambda **_kw: None)
|
||||
monkeypatch.setattr(services.monitoring_ops, "initialize", lambda **_kw: None)
|
||||
|
||||
secret_check_calls: list[list[str]] = []
|
||||
|
||||
class _DummyScreen(ServicesScreenMixin):
|
||||
KUBECTL_STATUS_TAB = "__kubectl_status__"
|
||||
|
||||
def __init__(self):
|
||||
self._action_flags = {}
|
||||
self.controller = MagicMock()
|
||||
self.controller.run_script.return_value = 0
|
||||
|
||||
self._init_scripts_button = _Widget()
|
||||
self.bg_canvas = _Canvas()
|
||||
self._init_scripts_status_label = 0
|
||||
|
||||
self._script_tab_index = {}
|
||||
self.script_consoles = {
|
||||
"init_common_services.sh": _Console(),
|
||||
"common_services_log": _Console(),
|
||||
"init_certmgr.sh": _Console(),
|
||||
"init_cloudnative_pg.sh": _Console(),
|
||||
"init_cnpg_backup.sh": _Console(),
|
||||
"init_kong.sh": _Console(),
|
||||
"init_monitoring.sh": _Console(),
|
||||
"init_nginx_ingress.sh": _Console(),
|
||||
"init_port_forwards.sh": _Console(),
|
||||
self.KUBECTL_STATUS_TAB: _Console(),
|
||||
}
|
||||
|
||||
self.db_username = _Var("prole")
|
||||
self.db_password = _Var("pw")
|
||||
# Reproducer: UI field is blank
|
||||
self.db_namespace = _Var("")
|
||||
|
||||
self.kerberos_enabled = _Var(False)
|
||||
self.kerberos_realm = _Var("")
|
||||
self.kerberos_kdc = _Var("")
|
||||
self.kerberos_user = _Var("")
|
||||
self.kerberos_password = _Var("")
|
||||
self.cluster_env = _Var("prole-service-cluster")
|
||||
|
||||
self.prole_cfg_data = {
|
||||
"Initialization Scripts": {"STATUS": ""},
|
||||
"Global": {"DATABASE_NAMESPACE": "knoe-db"},
|
||||
}
|
||||
self._scripts_success = False
|
||||
|
||||
def safe_after(self, fn, delay: int = 0):
|
||||
return fn()
|
||||
|
||||
def _deployment_mode(self):
|
||||
return "k3s"
|
||||
|
||||
def _resolve_prole_logs_dir(self):
|
||||
return tmp_path
|
||||
|
||||
def _get_service_namespace(self):
|
||||
return "knoe-system"
|
||||
|
||||
def _sync_port_forward_mappings(self):
|
||||
return None
|
||||
|
||||
def _update_legacy_port_mapping(self):
|
||||
return None
|
||||
|
||||
def _record_install_log(self, _path):
|
||||
return None
|
||||
|
||||
def _process_script_output_line(self, _line: str):
|
||||
return None
|
||||
|
||||
def _run_cmd_capture(self, args):
|
||||
secret_check_calls.append(args)
|
||||
if args[:4] == ["kubectl", "get", "secret", "knoe-db-user"]:
|
||||
return (0, "") if args[-1] == "knoe-db" else (1, "")
|
||||
if args[:4] == ["kubectl", "get", "secret", "knoe-db-superuser"]:
|
||||
return (0, "") if args[-1] == "knoe-db" else (1, "")
|
||||
if args[:4] == ["kubectl", "get", "secret", "cnpg-admin-key"]:
|
||||
return (0, "") if args[-1] == "knoe-db" else (1, "")
|
||||
return 0, ""
|
||||
|
||||
def _save_prole_cfg(self):
|
||||
return None
|
||||
|
||||
def update_footer(self):
|
||||
return None
|
||||
|
||||
def check_services_status_async(self):
|
||||
return None
|
||||
|
||||
screen = _DummyScreen()
|
||||
screen.run_init_scripts()
|
||||
|
||||
assert screen.prole_cfg_data["Initialization Scripts"]["STATUS"] == "Completed"
|
||||
checked_ns = {
|
||||
args[-1]
|
||||
for args in secret_check_calls
|
||||
if args[:3] == ["kubectl", "get", "secret"] and "-n" in args
|
||||
}
|
||||
assert "knoe-db" in checked_ns
|
||||
|
||||
|
||||
def test_init_monitoring_grafana_password_capture_does_not_wipe_monitoring_data_dir(
|
||||
tmp_path, monkeypatch
|
||||
):
|
||||
|
||||
@ -317,3 +317,58 @@ def test_discover_cluster_topology_includes_storage_items(tmp_path, monkeypatch)
|
||||
assert len(node.storage_items) == 1
|
||||
assert node.storage_items[0].label == "Synology d004"
|
||||
assert node.storage_items[0].io_class == "io_slow_durable"
|
||||
|
||||
|
||||
def test_collector_fs_stats_permission_denied_does_not_abort_collection() -> None:
|
||||
script = topology_mod._collector_python_script()
|
||||
namespace: dict[str, object] = {}
|
||||
exec(script, namespace)
|
||||
|
||||
class _PathWithDeniedExists:
|
||||
def __init__(self, value: str) -> None:
|
||||
self.value = value
|
||||
|
||||
def __truediv__(self, other: str):
|
||||
base = self.value.rstrip("/")
|
||||
child = str(other).lstrip("/")
|
||||
return _PathWithDeniedExists(f"{base}/{child}" if base else f"/{child}")
|
||||
|
||||
def exists(self) -> bool:
|
||||
raise PermissionError("denied")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
class _Stat:
|
||||
f_frsize = 4096
|
||||
f_blocks = 1000
|
||||
f_bavail = 200
|
||||
f_bfree = 300
|
||||
|
||||
def fake_statvfs(path: str):
|
||||
if "run/user/1000/doc" in path:
|
||||
raise PermissionError("denied")
|
||||
return _Stat()
|
||||
|
||||
namespace["Path"] = _PathWithDeniedExists
|
||||
namespace["_mount_rows"] = lambda: [
|
||||
("/dev/sda1", "/run/user/1000/doc", "ext4"),
|
||||
("/dev/sdb1", "/data", "ext4"),
|
||||
]
|
||||
namespace["_read_rotational"] = lambda _block_name: None
|
||||
namespace["_read_transport"] = lambda *_args: "unknown"
|
||||
namespace["_infer_storage_class"] = lambda *_args: "unknown"
|
||||
namespace["os"].statvfs = fake_statvfs
|
||||
|
||||
filesystems = namespace["_collect_filesystems"]()
|
||||
|
||||
assert len(filesystems) == 2
|
||||
first = filesystems[0]
|
||||
second = filesystems[1]
|
||||
assert first["mountpoint"] == "/run/user/1000/doc"
|
||||
assert first["sizeBytes"] is None
|
||||
assert first["usedBytes"] is None
|
||||
assert first["availableBytes"] is None
|
||||
assert second["mountpoint"] == "/data"
|
||||
assert second["sizeBytes"] == 4096 * 1000
|
||||
assert second["availableBytes"] == 4096 * 200
|
||||
|
||||
@ -139,3 +139,18 @@ def test_service_namespace_infers_registry_namespace(monkeypatch):
|
||||
)
|
||||
|
||||
assert Dummy()._get_service_namespace() == "knoe-system"
|
||||
|
||||
|
||||
def test_service_namespace_rejects_context_like_value_and_falls_back():
|
||||
"""Regression: kube context names must not be treated as Kubernetes namespaces."""
|
||||
|
||||
class Dummy(ClusterScreenMixin):
|
||||
def __init__(self):
|
||||
self.service_namespace = _Var("knoe.dev.prole.org")
|
||||
self.cluster_env = _Var("service")
|
||||
self.prole_cfg_data = {"Global": {}}
|
||||
|
||||
def _infer_common_services_namespace(self) -> str:
|
||||
return ""
|
||||
|
||||
assert Dummy()._get_service_namespace() == "knoe-system"
|
||||
|
||||
@ -180,6 +180,8 @@ def test_dockerfile_generation_percona_18(mock_installer, tmp_path):
|
||||
# Percona build flow should not install non-existent cron package variants
|
||||
assert "percona-postgresql-18-cron" not in content
|
||||
assert "percona-postgresql-18-pg_cron" not in content
|
||||
# pg_tde is handled by dedicated Percona package naming and must not use generic mapping
|
||||
assert "percona-postgresql-18-pg_tde" not in content
|
||||
|
||||
# SQL creation steps
|
||||
assert "CREATE EXTENSION IF NOT EXISTS pgcrypto;" in content
|
||||
|
||||
Loading…
Reference in New Issue
Block a user