prole/docs/intellij-refactor-prompt.md
chrisfu 3312c39b1f feat: GCP/GKE CNPG hardening, Artifact Registry traffic light, and knoe-system namespace fixes
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>
2026-04-04 19:36:08 -07:00

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() from registry.py
  • Implement initialize, start, update, stop, restart, status for k3d mode (extract the k3d branches that currently live inside each function in registry.py)
  • update() calls _ensure_k3d_registry()
  • stop() deletes the k3d registry and the fallback Docker container
  • status() checks k3d registry list

A2. Create knoe/core/ops/k3s_registry.py

  • Move _manifest() and _deployment_is_available() from registry.py
  • Implement initialize, start, update, stop, restart, status for k3s mode (this is the current default fall-through path in registry.py)
  • update() applies k8s/registry/deployment.yaml, reconciles ReplicaSets, waits rollout

A3. Create knoe/core/ops/k8s_registry.py

  • update() / stop() / restart(): log a no-op message and return
  • status(): return True (GCP Artifact Registry is externally managed)
  • initialize() / start(): delegate to update()

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() from garage_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.yaml
  • update() calls _repair_released_garage_pvs() before applying manifests
  • Cluster-scoped resources (no -n flag): {"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 registry etc.)
  • 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