UI screens - database.py: fix mode detection to use env_key priority (prod→k8s, service→k3s) so stale DEPLOYMENT_MODE never overrides the user's chosen environment - database.py: Registry status reads ARTIFACT_REGISTRY_AVAILABLE persisted by cluster screen; uses SERVICE_NAMESPACE for Artifact Registry repo name - cluster.py: add Artifact Registry traffic light (amber→green/red) to prod section; _check_artifact_registry_async persists ARTIFACT_REGISTRY_AVAILABLE into Global cfg - cluster.py: re-trigger Artifact Registry check after GKE cluster selection so the light re-evaluates once region is available from KUBECONTEXT - cluster_nodes.py: fix TclError on Python 3.14 — pady=(2,0) tuple → pady=2 scalar - __init__.py: seed knoe-system namespace when saved value is "default", not only when empty - services.py: replace hardcoded "Prole DB" log string with dynamic cnpg_cluster name Core ops - cloudnative_pg.py: replace one-shot Barman plugin retry with 6-attempt loop; first cert-manager/x509 failure triggers rollout restart + 30 s CA propagation wait; subsequent failures back off up to 60 s per attempt - cloudnative_pg.py: TLS CA CN now uses cluster_name instead of hardcoded "Prole CNPG CA" - registry.py, garage_store.py: refactored into per-mode modules (k3d/k3s/k8s registry and garage store, shared _garage_common) Deploy / config - deploy/gcp/gke/knoe-db.yaml: GKE-specific CNPG cluster manifest (rw/ro/r on separate nodes with premium-rwo storage) - etc/init_common_services.sh, modes/k8s/knoe-db/.version: updated for current deploy - kong-deployment.yaml: updated manifest Tests - test_cluster_nodes_render_smoke.py: add pack/grid, winfo_children, winfo_reqheight, update_idletasks, grid_slaves to dummy widgets; monkeypatch tk.Label so CNPG placement render completes without a real Tkinter root Co-authored-by: Junie <junie@jetbrains.com>
6.1 KiB
IntelliJ AI Assistant — Structural Refactor Prompt
You are performing a focused structural refactor on the prole installer project. Make ONLY the changes described below. Do NOT add features, rename identifiers not listed, or change observable behaviour. After each task, run the existing test suite and stop + report if any tests fail.
TASK A — Split ops/registry.py into mode-specific modules
A1. Create knoe/core/ops/k3d_registry.py
- Move
_ensure_k3d_registry()fromregistry.py - Implement
initialize,start,update,stop,restart,statusfor k3d mode (extract the k3d branches that currently live inside each function inregistry.py) update()calls_ensure_k3d_registry()stop()deletes the k3d registry and the fallback Docker containerstatus()checksk3d registry list
A2. Create knoe/core/ops/k3s_registry.py
- Move
_manifest()and_deployment_is_available()fromregistry.py - Implement
initialize,start,update,stop,restart,statusfor k3s mode (this is the current default fall-through path inregistry.py) update()appliesk8s/registry/deployment.yaml, reconciles ReplicaSets, waits rollout
A3. Create knoe/core/ops/k8s_registry.py
update()/stop()/restart(): log a no-op message and returnstatus(): returnTrue(GCP Artifact Registry is externally managed)initialize()/start(): delegate toupdate()
A4. Rewrite knoe/core/ops/registry.py as a pure dispatcher
from ._services_common import _detect_mode
from types import ModuleType
def _module(mode, env) -> ModuleType:
m = _detect_mode(mode, env)
if m == "k3d":
from . import k3d_registry; return k3d_registry
if m == "k8s":
from . import k8s_registry; return k8s_registry
from . import k3s_registry; return k3s_registry
# initialize / start / update / stop / restart / status
# — each delegates to _module(mode, env).<fn>(**kwargs)
# Public signatures are UNCHANGED.
TASK B — Split ops/garage_store.py into mode-specific modules
B1. Create knoe/core/ops/_garage_common.py
- Move
_garage_namespace()and_ensure_garage_secret()fromgarage_store.py - These helpers are shared by all three mode variants
B2. Create knoe/core/ops/k3s_garage_store.py
- Move
_repair_released_garage_pvs()here (k3s / Synology iSCSI only) _manifest_files()returns:storageclass-synology-iscsi.yaml,iscsi-pvs.yaml,garage-configmap.yaml,garage-statefulset.yaml,garage-service.yamlupdate()calls_repair_released_garage_pvs()before applying manifests- Cluster-scoped resources (no
-nflag):{"storageclass-synology-iscsi.yaml", "iscsi-pvs.yaml"}
B3. Create knoe/core/ops/k3d_garage_store.py
_manifest_files()returns:garage-configmap.yaml,garage-statefulset.yaml,garage-service.yaml(no Synology storage classes, no static PVs)- No PV repair
B4. Create knoe/core/ops/k8s_garage_store.py
_manifest_files()returns:storageclass-gcp-hdd.yaml,garage-configmap.yaml,garage-statefulset-gcp.yaml,garage-service.yaml- No PV repair
- Cluster-scoped:
{"storageclass-gcp-hdd.yaml"}
B5. Rewrite knoe/core/ops/garage_store.py as dispatcher
Same _module() + delegating-function pattern as Task A.
stop() and restart() in garage variants take only namespace, env, log
(no mode parameter — mode is resolved from env).
TASK C — Rename UI nav labels (display text only)
File: knoe/ui/screens/__init__.py
"Kerberos Authentication" → "Knoe Authority" (nav_items; page_id "kerberos_config" unchanged)
"Knoe User Authority" → "Knoe Users" (nav_items; page_id "knoe_users" unchanged)
File: knoe/ui/screens/security.py
- Change the
_render_title("Kerberos Authentication", ...)call →_render_title("Knoe Authority", ...) - Change
text="Enable Kerberos Authentication"→text="Enable Knoe Authority" - Do NOT change
self.prole_cfg_data["Kerberos Authentication"]keys — those are config-file section names
File: knoe/ui/screens/knoe_users.py
- Update any user-facing title Label text that still reads "Knoe User Authority"
- Do NOT change log prefixes or cfg/env key names
TASK D — Add k3d | k3s | k8s mode tab strip to sidebar
File: knoe/ui/screens/__init__.py — in __init__, after self.nav_widgets = {}:
self._mode_tab_widgets: dict = {}
self.deployment_mode = tk.StringVar(value=os.environ.get("PROLE_MODE", "k3s"))
File: knoe/ui/screens/navigation.py — in _create_sidebar_nav(),
before the "INSTALLER" Label, insert:
_MODE_COLORS = {"k3d": "#4A90D9", "k3s": "#27AE60", "k8s": "#E67E22"}
mode_frame = tk.Frame(self.sidebar, bg="#F5F5DC")
mode_frame.pack(fill="x", padx=12, pady=(14, 4))
for label, value in [("k3d", "k3d"), ("k3s", "k3s"), ("k8s", "k8s")]:
btn = tk.Label(
mode_frame, text=label,
bg="#D5D5C5", fg="#555",
font=("SF Pro Text", 9, "bold"),
padx=8, pady=3, cursor="hand2",
)
btn.pack(side="left", padx=2)
btn.bind("<Button-1>", lambda e, v=value: self._set_deployment_mode(v))
self._mode_tab_widgets[value] = btn
self._refresh_mode_tabs()
Add to the navigation mixin:
def _set_deployment_mode(self, mode: str) -> None:
self.deployment_mode.set(mode)
os.environ["PROLE_MODE"] = mode
self._refresh_mode_tabs()
def _refresh_mode_tabs(self) -> None:
_MODE_COLORS = {"k3d": "#4A90D9", "k3s": "#27AE60", "k8s": "#E67E22"}
active = self.deployment_mode.get()
for value, widget in self._mode_tab_widgets.items():
if value == active:
widget.configure(bg=_MODE_COLORS[value], fg="white")
else:
widget.configure(bg="#D5D5C5", fg="#555")
CONSTRAINTS
- All existing import paths must remain valid (
from knoe.core.ops import registryetc.) - All public function signatures are unchanged
- Run tests after each task; stop and report failures before continuing
- Do not create README or documentation files (other than this prompt file)
- Do not touch any files not listed above