mirror of
https://github.com/dredx/prole.git
synced 2026-09-24 16:44:33 +00:00
## GCP / Cluster Environment Screen - Auto-populate Cloud tab from conf/prod/gcp.cfg on screen open (org_id, billing_account, billing_project, project_id) - gcloud auth validity checked on screen startup; friendly modal dialog streams gcloud auth login output live so user never leaves the app - Live GKE cluster browser: fetches clusters via gcloud container clusters list, displays with checkmark selector, auto-selects saved cluster - Selecting a cluster runs get-credentials, sets KUBECONFIG/KUBECONTEXT, and syncs the region dropdown to the selected cluster's location - Region dropdown populated live from gcloud compute regions list with checkmark on currently selected region; graceful fallback when offline - New 'GCP Storage' tab with workload->StorageClass mapping (CNPG->premium-rwo, Redis/Monitoring->standard-rwo, Garage->garage-hdd) and Fetch from Cluster - Provider readonly field styled correctly (no solid-black on macOS) - Stale prole.cfg/conf/prole.cfg symlinks removed; all config I/O now resolves env-specific paths via prole_conf.entrypoint_path() ## GKE Autopilot Compatibility (Common Services) - Synology iSCSI StorageClass and static PVs guarded behind PROLE_MODE!=k8s in init_openbao.sh (GKE Autopilot forbids hostPath/iSCSI volumes) - In-cluster Docker registry (hostPath) skipped in k8s mode; GCP Artifact Registry used instead - Kong renamed knoe-svc-kong in k8s mode; all health-check kubectl calls in init_common_services.sh and status_common_services.sh updated accordingly - DNS endpoints switched from *.prole.org to *.knoe.dev in k8s mode (api.knoe.dev, git.knoe.dev, svc.knoe.dev); ingress uses gce class - New GKE-clean Kong manifests under deploy/opentofu/k8s/manifests/prole/: no k3s node affinity, explicit Autopilot resource requests/limits ## Garage S3 Store (GKE) - New garage-statefulset-gcp.yaml targeting garage-hdd StorageClass (pd-standard, avoids SSD_TOTAL_GB quota exhaustion in us-west3) - New storageclass-gcp-hdd.yaml (pd-standard, Retain, WaitForFirstConsumer) - GCP StorageClass manifests skipped on re-runs (Autopilot built-ins are immutable; skip-if-exists guard added) - PVC deletion guard extended to cover any storageClass (not just synology) so stale claims are cleaned before StatefulSet recreation ## Topology (GKE Autopilot) - DaemonSet collector skipped in prod mode (forbidden in kube-system by GKE Warden); Kubernetes-only node facts path used instead - All ready GKE nodes assumed cnpg-eligible and monitoring-eligible without taint/synology-mount checks (skip_collector + assume_nodes_eligible flags) ## KUBECONFIG / kubectl (k8s mode) - actions.py: new elif mode==k8s branch sets KUBECONFIG=~/.kube/config and injects KUBECONTEXT from prole_cfg_data into script env - _build_kubectl_cmd falls back to Global.KUBECONTEXT when init_cluster.selected_kubectx is empty - _activate_selected_gke_cluster persists KUBECONFIG/KUBECONTEXT to prole_cfg_data and saves prole.cfg immediately after get-credentials ## Database Build Screen (GKE) - Registry display shows correct Artifact Registry URL (<region>-docker.pkg.dev/<project>/<namespace>/knoe-db) in green - Build+push: gcloud auth configure-docker, auto-creates AR repository named after SERVICE_NAMESPACE (e.g. knoe-system) if missing, then docker tag + push; falls back to gcr.io if region unavailable - GCP config loaded from conf/prod/gcp.cfg on every screen entry; keys normalised to lowercase so project_id lookup is always consistent ## Config / Namespace persistence - prole_conf.py activate_environment: symlink creation removed; sets CLUSTER_ENV env-var so all subsequent calls resolve correct env directory - knoe/ui/screens/__init__.py: startup config load uses entrypoint_path() instead of hardcoded conf/prole.cfg; seeds SERVICE_NAMESPACE=knoe-system for managed envs so Common Services never defaults to 'default' - cfg.py _save_prole_cfg: saves to env-specific path via entrypoint_path() - etc/prole_cfg.sh: removed all ln -snf symlink creation Co-authored-by: Junie <junie@jetbrains.com>
254 lines
9.9 KiB
Python
254 lines
9.9 KiB
Python
"""Knoe System Authority screen.
|
|
|
|
Ensures the in-cluster authority workload is available, then provisions
|
|
Kerberos principals and database access. The database master password is used
|
|
for admin@PROLE.LOCAL and the Kerberos admin credential is used for PROLE.ORG.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import threading
|
|
import tkinter as tk
|
|
|
|
from knoe import screen as ui
|
|
from knoe.core.env import PROJECT_ROOT
|
|
|
|
|
|
class KnoeUsersScreenMixin:
|
|
"""Knoe System Authority screen."""
|
|
|
|
def _render_knoe_users_page(self):
|
|
self._knoe_users_success = False
|
|
self._knoe_users_deploying = False
|
|
self._knoe_users_status_var = tk.StringVar(value="Ready")
|
|
|
|
content_width = self.bg_canvas.winfo_width() or 975
|
|
right_margin = content_width - 48
|
|
|
|
# ── Letterhead ────────────────────────────────────────────────────────
|
|
ui.canvas_text(
|
|
self,
|
|
right_margin,
|
|
40,
|
|
"knoe.dev",
|
|
fill="#6e6e73",
|
|
font=("SF Pro Text", 32, "bold"),
|
|
anchor="ne",
|
|
)
|
|
ui.canvas_text(
|
|
self,
|
|
right_margin,
|
|
85,
|
|
"infrastructure.auto()",
|
|
fill="#6e6e73",
|
|
font=("SF Pro Text", 18),
|
|
anchor="ne",
|
|
)
|
|
|
|
# ── Title & description ───────────────────────────────────────────────
|
|
self._render_title("Knoe System Authority", y=150)
|
|
|
|
self._render_paragraph(
|
|
"Ensures the authority workload is available in the service namespace. "
|
|
"Mode `k3s` deploys `authority-prole-auth`; mode `prod|gcp` deploys "
|
|
"`authority-gcp-auth`. Provision Kerberos principals, "
|
|
"export the postgres service keytab, patch CNPG managed roles (admin, "
|
|
"guest, developer), initialise the demo schema, and wire admin into "
|
|
"ArgoCD RBAC. The database master password is used for admin@PROLE.LOCAL; "
|
|
"the Kerberos admin credential from the Authentication screen is used "
|
|
"to connect PROLE.ORG.",
|
|
y=205,
|
|
)
|
|
|
|
# ── Status line ───────────────────────────────────────────────────────
|
|
status_item = ui.canvas_text(
|
|
self,
|
|
48,
|
|
370,
|
|
"Status: Ready",
|
|
fill="black",
|
|
font=("SF Pro Text", 12),
|
|
)
|
|
|
|
def _update_status(*_):
|
|
try:
|
|
self.bg_canvas.itemconfig(
|
|
status_item,
|
|
text=f"Status: {self._knoe_users_status_var.get()}",
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
self._knoe_users_status_var.trace_add("write", _update_status)
|
|
|
|
# ── Console output ────────────────────────────────────────────────────
|
|
self._knoe_users_console = self._create_console_output(
|
|
y=400, title="Provisioning Output", width=content_width - 96, height=355
|
|
)
|
|
|
|
# ── Provision button ──────────────────────────────────────────────────
|
|
self._knoe_users_btn = tk.Button(
|
|
self.bg_canvas,
|
|
text="Provision Users & Auth",
|
|
command=self.run_knoe_users_provision,
|
|
bg="#F5F5DC",
|
|
fg="black",
|
|
activebackground="#E5E5D5",
|
|
activeforeground="black",
|
|
highlightbackground="#F5F5DC",
|
|
highlightcolor="#F5F5DC",
|
|
highlightthickness=0,
|
|
relief="flat",
|
|
bd=0,
|
|
cursor="hand2",
|
|
disabledforeground="#8B8B7A",
|
|
font=("SF Pro Text", 11),
|
|
padx=20,
|
|
pady=10,
|
|
)
|
|
btn_window = self.bg_canvas.create_window(
|
|
48, 760, window=self._knoe_users_btn, anchor="nw"
|
|
)
|
|
self._canvas_items.append(btn_window)
|
|
self._overlay_widgets.append(self._knoe_users_btn)
|
|
|
|
# ── Script runner ─────────────────────────────────────────────────────────
|
|
|
|
def run_knoe_users_provision(self): # noqa: C901
|
|
if getattr(self, "_knoe_users_deploying", False):
|
|
return
|
|
|
|
self._knoe_users_deploying = True
|
|
self._knoe_users_success = False
|
|
self._knoe_users_btn.configure(state="disabled")
|
|
self._knoe_users_status_var.set("Provisioning…")
|
|
self._knoe_users_console.clear()
|
|
self.update_footer()
|
|
|
|
def worker():
|
|
script_path = PROJECT_ROOT / "etc" / "init_knoe_users.sh"
|
|
if not script_path.exists():
|
|
self._knoe_users_console.write(
|
|
f"Error: {script_path} not found.\n"
|
|
)
|
|
self.safe_after(
|
|
lambda: self._knoe_users_status_var.set("Failed (script not found)")
|
|
)
|
|
self.safe_after(
|
|
lambda: self._knoe_users_btn.configure(state="normal")
|
|
)
|
|
self._knoe_users_deploying = False
|
|
return
|
|
|
|
env = os.environ.copy()
|
|
env["PROLE_HOME"] = str(PROJECT_ROOT)
|
|
env["PROLE_SERVICE"] = str(PROJECT_ROOT)
|
|
|
|
# Propagate namespace from installer state when available
|
|
service_ns = (
|
|
getattr(self, "service_namespace", None) and
|
|
self.service_namespace.get().strip()
|
|
) or os.environ.get("SERVICE_NAMESPACE", "knoe-system")
|
|
env["SERVICE_NAMESPACE"] = service_ns
|
|
env["KNOE_USERS_NAMESPACE"] = service_ns
|
|
env["KNOE_KDC_NAMESPACE"] = service_ns
|
|
|
|
db_ns = os.environ.get("DATABASE_NAMESPACE", "knoe-db")
|
|
env["DATABASE_NAMESPACE"] = db_ns
|
|
env["KNOE_DB_NAMESPACE"] = db_ns
|
|
|
|
# Use database master password for admin@PROLE.LOCAL
|
|
db_pw = (
|
|
getattr(self, "db_password", None) and
|
|
self.db_password.get().strip()
|
|
) or ""
|
|
if db_pw:
|
|
env["DB_MASTER_PASSWORD"] = db_pw
|
|
env["PROLE_LOCAL_ADMIN_PASSWORD"] = db_pw
|
|
|
|
# Use Kerberos admin credential from Authentication screen for PROLE.ORG
|
|
krb_pw = (
|
|
getattr(self, "kerberos_password", None) and
|
|
self.kerberos_password.get().strip()
|
|
) or ""
|
|
if krb_pw:
|
|
env["PROLE_ORG_ADMIN_PASSWORD"] = krb_pw
|
|
env["KRB5_PASSWORD"] = krb_pw
|
|
|
|
# Pass prole.cfg path so the script sources the right config
|
|
cfg_path = None
|
|
try:
|
|
if self._cfg_path_override and self._cfg_path_override.exists():
|
|
cfg_path = self._cfg_path_override
|
|
else:
|
|
conf_dir = self._resolve_prole_conf_dir()
|
|
candidate = conf_dir / "prole.cfg"
|
|
if candidate.exists():
|
|
cfg_path = candidate
|
|
except Exception:
|
|
cfg_path = None
|
|
if cfg_path:
|
|
env["PROLE_CFG"] = str(cfg_path)
|
|
|
|
cmd = ["bash", str(script_path), "initialize"]
|
|
self._knoe_users_console.write(
|
|
"Ensuring authority workload and provisioning users\n"
|
|
f"Running: {' '.join(cmd)}\n"
|
|
f"Namespace: {service_ns}\n\n"
|
|
)
|
|
|
|
try:
|
|
proc = subprocess.Popen(
|
|
cmd,
|
|
cwd=str(PROJECT_ROOT),
|
|
env=env,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
text=True,
|
|
bufsize=1,
|
|
)
|
|
except Exception as exc:
|
|
self._knoe_users_console.write(f"Failed to start: {exc}\n")
|
|
self.safe_after(
|
|
lambda: self._knoe_users_status_var.set("Failed (launch error)")
|
|
)
|
|
self.safe_after(
|
|
lambda: self._knoe_users_btn.configure(state="normal")
|
|
)
|
|
self._knoe_users_deploying = False
|
|
return
|
|
|
|
if proc.stdout:
|
|
for line in iter(proc.stdout.readline, ""):
|
|
if line:
|
|
self._knoe_users_console.write(line)
|
|
proc.stdout.close()
|
|
rc = proc.wait()
|
|
|
|
if rc == 0:
|
|
self._knoe_users_success = True
|
|
self.safe_after(
|
|
lambda: self._knoe_users_status_var.set("Provisioned Successfully")
|
|
)
|
|
self._knoe_users_console.write(
|
|
"\nAuthority workload ensured and users provisioned successfully.\n"
|
|
)
|
|
else:
|
|
self._knoe_users_success = False
|
|
self.safe_after(
|
|
lambda: self._knoe_users_status_var.set(f"Failed (exit {rc})")
|
|
)
|
|
self._knoe_users_console.write(
|
|
f"\nProvisioning failed with exit code {rc}.\n"
|
|
"Review the output above, resolve the issue, and click "
|
|
"'Provision Users & Auth' again.\n"
|
|
)
|
|
|
|
self._knoe_users_deploying = False
|
|
self.safe_after(lambda: self._knoe_users_btn.configure(state="normal"))
|
|
self.safe_after(self.update_footer)
|
|
|
|
threading.Thread(target=worker, daemon=True).start()
|