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

158 lines
6.1 KiB
Markdown

# 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
```python
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 = {}`:
```python
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:
```python
_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:
```python
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