chore: update prole.cfg defaults for dev cluster, refine config overrides

- Updated default paths, namespaces, and cluster names for k3d/dev configuration.
- Added `skip_names` set to exclude `prod.cfg` and `gcp.cfg` from override processing.
- Refined init parameters for Supabase, Kubernetes, and database deployments to align with dev-specific settings.
- Enhanced test coverage for excluded config files (`prod.cfg`, `gcp.cfg`) within `test_prole_conf.py`.
This commit is contained in:
chrisfu 2026-04-12 12:52:43 -07:00
parent 65369811f7
commit 138a01c181
6 changed files with 248 additions and 15 deletions

View File

@ -1,18 +1,3 @@
# Port mappings for Prole Tools (generated).
# Format: key: local=... remote=... ns=... svc=... address=...
argocd: local=8081 remote=80 ns=argocd svc=argocd-server address=0.0.0.0
supabase-studio: local=18080 remote=3000 ns=supabase svc=studio address=0.0.0.0
supabase-auth: local=9999 remote=9999 ns=supabase svc=auth address=127.0.0.1
supabase-rest: local=3001 remote=3000 ns=supabase svc=rest address=0.0.0.0
supabase-realtime: local=4000 remote=4000 ns=supabase svc=realtime address=0.0.0.0
gitea-http: local=13000 remote=3000 ns=gitea svc=gitea-http address=0.0.0.0
gitea-ssh: local=22 remote=22 ns=gitea svc=gitea-ssh address=0.0.0.0
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 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
supabase-kong: local=8000 remote=8000 ns=supabase svc=kong address=0.0.0.0

View File

@ -1049,6 +1049,16 @@ DEPENDENCIES = [
"version_cmd": "brew list --versions kubectx 2>/dev/null || brew info kubectx 2>/dev/null | head -n1",
"bin": "kubectx",
},
{
"id": "gcloud",
"name": "gcloud",
"parent": "brew",
"description": "Google Cloud SDK CLI for GKE authentication and context setup",
"url": "https://cloud.google.com/sdk/docs/install",
"install_cmd": "brew install --cask google-cloud-sdk || brew install google-cloud-sdk",
"check_cmd": "gcloud --version",
"bin": "gcloud",
},
{
"id": "docker",
"name": "Docker",
@ -1125,6 +1135,13 @@ def _augment_env_for_brew(env: dict | None = None) -> dict:
prepend: list[str] = []
if bin_dir and bin_dir not in parts:
prepend.append(bin_dir)
try:
sdk_bin_dir = str(prefix / "share" / "google-cloud-sdk" / "bin")
if os.path.isdir(sdk_bin_dir) and sdk_bin_dir not in parts:
prepend.append(sdk_bin_dir)
except Exception:
# SDK path is optional
pass
try:
if os.path.isdir(sbin_dir) and sbin_dir not in parts:
prepend.append(sbin_dir)

View File

@ -4699,6 +4699,9 @@ class KnoeConsoleInstaller(KnoeInstaller):
self.log(f"[MISSING] {dep['name']}")
if not missing:
if not self._ensure_gcloud_auth_for_k8s():
self.prole_cfg_data["Dependencies"]["STATUS"] = "Missing"
return False
self.prole_cfg_data["Dependencies"]["STATUS"] = "All installed"
return True
@ -4736,9 +4739,59 @@ class KnoeConsoleInstaller(KnoeInstaller):
self.err(f"[ERROR] Still missing: {', '.join(still_missing)}")
self.prole_cfg_data["Dependencies"]["STATUS"] = "Missing"
return False
if not self._ensure_gcloud_auth_for_k8s():
self.prole_cfg_data["Dependencies"]["STATUS"] = "Missing"
return False
self.prole_cfg_data["Dependencies"]["STATUS"] = "All installed"
return True
def _ensure_gcloud_auth_for_k8s(self) -> bool:
if self._deployment_mode() != "k8s":
return True
cmd_base, gcloud_env = inst_config._gcloud_plain(os.environ.copy())
token_cmd = list(cmd_base) + ["auth", "print-access-token", "--quiet"]
try:
token_res = subprocess.run(
token_cmd,
capture_output=True,
text=True,
env=gcloud_env,
timeout=20,
)
if token_res.returncode == 0 and (token_res.stdout or "").strip():
return True
except Exception:
pass
interactive = bool(getattr(sys.stdin, "isatty", lambda: False)())
if interactive:
login_cmd = list(cmd_base) + ["auth", "login", "--no-launch-browser"]
self.log(
"[ACTION] No active gcloud session found. Starting login flow: "
+ " ".join(shlex.quote(part) for part in login_cmd)
)
try:
login_rc = self._run_cmd(login_cmd)
if login_rc == 0:
token_res = subprocess.run(
token_cmd,
capture_output=True,
text=True,
env=gcloud_env,
timeout=20,
)
if token_res.returncode == 0 and (token_res.stdout or "").strip():
return True
except Exception:
pass
self.err(
"[ERROR] gcloud is installed but no active auth session is available for GKE. "
"Run `gcloud auth login --no-launch-browser`, complete the URL/code flow on a browser-capable machine, then retry deploy."
)
return False
def _step_network_scan(self) -> None:
if not self._get_input_bool(
"network_scan.run", DEFAULT_ACTION_FLAGS.get("network_scan.run", True)

View File

@ -1,6 +1,7 @@
from __future__ import annotations
import logging
import os
import shlex
import subprocess
import sys
import time
@ -68,6 +69,9 @@ class DependenciesMilestone(Milestone):
)
if not missing:
if not self._ensure_gcloud_auth_for_k8s(state):
self._set_status(state, "Missing")
return
self._set_status(state, "All installed")
if progress:
progress("All dependencies installed", 1.0)
@ -114,10 +118,61 @@ class DependenciesMilestone(Milestone):
self.logger.error(f"Still missing: {', '.join(still_missing)}")
self._set_status(state, "Missing")
else:
if not self._ensure_gcloud_auth_for_k8s(state):
self._set_status(state, "Missing")
return
self._set_status(state, "All installed")
if progress:
progress("All dependencies installed", 1.0)
def _ensure_gcloud_auth_for_k8s(self, state: InstallerState) -> bool:
cluster_env = str(state.inputs.get("init_cluster.cluster_env", "")).strip().lower()
if cluster_env not in {"prod", "production", "k8s"}:
return True
cmd_base, gcloud_env = inst_config._gcloud_plain(os.environ.copy())
token_cmd = list(cmd_base) + ["auth", "print-access-token", "--quiet"]
try:
token_res = subprocess.run(
token_cmd,
capture_output=True,
text=True,
env=gcloud_env,
timeout=20,
)
if token_res.returncode == 0 and (token_res.stdout or "").strip():
return True
except Exception:
pass
interactive = bool(getattr(sys.stdin, "isatty", lambda: False)())
if interactive:
login_cmd = list(cmd_base) + ["auth", "login", "--no-launch-browser"]
self.logger.info(
"[ACTION] No active gcloud session found. Starting login flow: %s",
" ".join(shlex.quote(part) for part in login_cmd),
)
try:
login_rc = self._run_cmd(login_cmd)
if login_rc == 0:
token_res = subprocess.run(
token_cmd,
capture_output=True,
text=True,
env=gcloud_env,
timeout=20,
)
if token_res.returncode == 0 and (token_res.stdout or "").strip():
return True
except Exception:
pass
self.logger.error(
"gcloud is installed but no active auth session is available for GKE. "
"Run `gcloud auth login --no-launch-browser` in a terminal, complete the URL/code flow on a browser-capable machine, then retry deploy."
)
return False
def _set_status(self, state: InstallerState, status: str):
if "Dependencies" not in state.config_data:
state.config_data["Dependencies"] = {}

View File

@ -1013,3 +1013,79 @@ class TestPrepareOpenTofuPipeline:
assert inst.prole_cfg_data["Deployment"]["OPENTOFU_PIPELINE_DIR"] == str(
expected_dir
)
# ---------------------------------------------------------------------------
# _step_dependencies / gcloud auth preflight
# ---------------------------------------------------------------------------
class TestStepDependencies:
def test_step_dependencies_marks_missing_when_k8s_auth_missing(self, monkeypatch):
inst = _TestableSilentInstaller(inputs={"init_cluster.cluster_env": "prod"})
inst.dependencies = [{"id": "gcloud", "name": "gcloud"}]
monkeypatch.setattr(
"knoe.core.actions.inst_config.get_dep_info",
lambda _dep: (True, "/usr/bin/gcloud", "1.0"),
)
monkeypatch.setattr(inst, "_ensure_gcloud_auth_for_k8s", lambda: False)
ok = inst._step_dependencies()
assert ok is False
assert inst.prole_cfg_data["Dependencies"]["STATUS"] == "Missing"
def test_ensure_gcloud_auth_for_k8s_non_interactive_failure(self, monkeypatch):
import knoe.core.actions as actions_mod
inst = _TestableSilentInstaller(inputs={"init_cluster.cluster_env": "prod"})
monkeypatch.setattr(
actions_mod.inst_config,
"_gcloud_plain",
lambda _env: (["gcloud"], {"PATH": "x"}),
)
monkeypatch.setattr(
actions_mod.subprocess,
"run",
lambda *_a, **_k: subprocess.CompletedProcess([], 1, stdout="", stderr="no auth"),
)
monkeypatch.setattr(actions_mod.sys.stdin, "isatty", lambda: False, raising=False)
errors = []
monkeypatch.setattr(inst, "err", lambda msg: errors.append(msg))
ok = inst._ensure_gcloud_auth_for_k8s()
assert ok is False
assert any("gcloud auth login --no-launch-browser" in msg for msg in errors)
def test_ensure_gcloud_auth_for_k8s_interactive_login_success(self, monkeypatch):
import knoe.core.actions as actions_mod
inst = _TestableSilentInstaller(inputs={"init_cluster.cluster_env": "prod"})
monkeypatch.setattr(
actions_mod.inst_config,
"_gcloud_plain",
lambda _env: (["gcloud"], {"PATH": "x"}),
)
responses = [
subprocess.CompletedProcess([], 1, stdout="", stderr="no auth"),
subprocess.CompletedProcess([], 0, stdout="tok123\n", stderr=""),
]
def _fake_run(*_a, **_k):
return responses.pop(0)
monkeypatch.setattr(actions_mod.subprocess, "run", _fake_run)
monkeypatch.setattr(actions_mod.sys.stdin, "isatty", lambda: True, raising=False)
login_calls = []
monkeypatch.setattr(inst, "_run_cmd", lambda cmd, **_kw: login_calls.append(cmd) or 0)
ok = inst._ensure_gcloud_auth_for_k8s()
assert ok is True
assert login_calls

View File

@ -648,6 +648,53 @@ def test_dependencies_install_succeeds_all():
assert state.config_data.get("Dependencies", {}).get("STATUS") == "All installed"
def test_dependencies_k8s_fails_without_gcloud_auth_session():
state = _make_state(
**{
"dependencies.auto_install_missing": "True",
"init_cluster.cluster_env": "prod",
}
)
milestone = DependenciesMilestone()
dep = {"id": "gcloud", "name": "gcloud", "install_cmd": "brew install gcloud"}
no_token = MagicMock(returncode=1, stdout="", stderr="not logged in")
with patch("knoe.config.get_dep_info", return_value=(True, "/usr/bin/gcloud", "1.0")), \
patch("knoe.config.DEPENDENCIES", [dep]), \
patch("knoe.config._gcloud_plain", return_value=(["gcloud"], {"PATH": "x"})), \
patch("knoe.core.milestones.subprocess.run", return_value=no_token), \
patch("knoe.core.milestones.sys.stdin.isatty", return_value=False):
milestone.execute(state)
assert state.config_data.get("Dependencies", {}).get("STATUS") == "Missing"
def test_dependencies_k8s_interactive_login_recovers_auth_session():
state = _make_state(
**{
"dependencies.auto_install_missing": "True",
"init_cluster.cluster_env": "prod",
}
)
milestone = DependenciesMilestone()
dep = {"id": "gcloud", "name": "gcloud", "install_cmd": "brew install gcloud"}
no_token = MagicMock(returncode=1, stdout="", stderr="not logged in")
with_token = MagicMock(returncode=0, stdout="tok123\n", stderr="")
with patch("knoe.config.get_dep_info", return_value=(True, "/usr/bin/gcloud", "1.0")), \
patch("knoe.config.DEPENDENCIES", [dep]), \
patch("knoe.config._gcloud_plain", return_value=(["gcloud"], {"PATH": "x"})), \
patch("knoe.core.milestones.subprocess.run", side_effect=[no_token, with_token]), \
patch("knoe.core.milestones.sys.stdin.isatty", return_value=True), \
patch.object(milestone, "_run_cmd", return_value=0) as run_cmd_mock:
milestone.execute(state)
assert state.config_data.get("Dependencies", {}).get("STATUS") == "All installed"
run_cmd_mock.assert_called_once()
# ---------------------------------------------------------------------------
# InitializationScriptsMilestone._regenerate_port_mapping_cfg
# ---------------------------------------------------------------------------