mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
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:
parent
090b0e882a
commit
cedcaae2c5
246
knoe/config.py
246
knoe/config.py
@ -268,6 +268,8 @@ PROLE_PROPS_PATH = PROLE_APP_DIR / "prole.properties"
|
||||
|
||||
import logging
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup_logging(verbose=False, debug=False):
|
||||
"""Set up logging for the installer.
|
||||
@ -998,7 +1000,25 @@ def _merge_kubeconfig(kubeconfig_path: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
DEPENDENCIES = [
|
||||
def _run_as_root_noninteractive(command: str) -> str:
|
||||
return (
|
||||
"if command -v sudo >/dev/null 2>&1; then "
|
||||
f"sudo -n {command}; "
|
||||
f"else {command}; fi"
|
||||
)
|
||||
|
||||
|
||||
def _apt_install(pkg: str) -> str:
|
||||
return _run_as_root_noninteractive(
|
||||
f"env DEBIAN_FRONTEND=noninteractive apt-get install -y {pkg}"
|
||||
)
|
||||
|
||||
|
||||
def _rpm_install(manager: str, pkg: str) -> str:
|
||||
return _run_as_root_noninteractive(f"{manager} install -y {pkg}")
|
||||
|
||||
|
||||
_MACOS_DEPENDENCIES = [
|
||||
{
|
||||
"id": "brew",
|
||||
"name": "Homebrew",
|
||||
@ -1090,6 +1110,218 @@ DEPENDENCIES = [
|
||||
]
|
||||
|
||||
|
||||
def _linux_dependencies(manager: str) -> list[dict]:
|
||||
if manager not in {"apt", "dnf", "yum"}:
|
||||
return []
|
||||
|
||||
install = _apt_install if manager == "apt" else lambda pkg: _rpm_install(manager, pkg)
|
||||
return [
|
||||
{
|
||||
"id": "python",
|
||||
"name": "python",
|
||||
"description": "Python programming language",
|
||||
"url": "https://www.python.org",
|
||||
"install_cmd": install("python3"),
|
||||
"check_cmd": "python3 --version",
|
||||
"bin": "python3",
|
||||
},
|
||||
{
|
||||
"id": "ansible",
|
||||
"name": "ansible",
|
||||
"description": "Infrastructure automation tool",
|
||||
"url": "https://www.ansible.com",
|
||||
"install_cmd": install("ansible"),
|
||||
"check_cmd": "ansible --version",
|
||||
"bin": "ansible",
|
||||
},
|
||||
{
|
||||
"id": "kubectl",
|
||||
"name": "kubectl",
|
||||
"description": "Kubernetes command-line tool",
|
||||
"url": "https://kubernetes.io/docs/reference/kubectl/",
|
||||
"install_cmd": install("kubectl"),
|
||||
"check_cmd": "kubectl version --client",
|
||||
"bin": "kubectl",
|
||||
},
|
||||
{
|
||||
"id": "kubectx",
|
||||
"name": "kubectx",
|
||||
"description": "Tool to switch between Kubernetes contexts",
|
||||
"url": "https://github.com/ahmetb/kubectx",
|
||||
"install_cmd": install("kubectx"),
|
||||
"check_cmd": "kubectx -h",
|
||||
"bin": "kubectx",
|
||||
},
|
||||
{
|
||||
"id": "gcloud",
|
||||
"name": "gcloud",
|
||||
"description": "Google Cloud SDK CLI for GKE authentication and context setup",
|
||||
"url": "https://cloud.google.com/sdk/docs/install",
|
||||
"install_cmd": install("google-cloud-cli"),
|
||||
"check_cmd": "gcloud --version",
|
||||
"bin": "gcloud",
|
||||
},
|
||||
{
|
||||
"id": "docker",
|
||||
"name": "Docker",
|
||||
"description": "Container platform for running Prole services",
|
||||
"url": "https://www.docker.com",
|
||||
"install_cmd": install("docker.io" if manager == "apt" else "docker"),
|
||||
"check_cmd": "docker --version",
|
||||
"bin": "docker",
|
||||
},
|
||||
{
|
||||
"id": "k3d",
|
||||
"name": "k3d",
|
||||
"description": "Lightweight wrapper to run k3s in Docker",
|
||||
"url": "https://k3d.io",
|
||||
"install_cmd": install("k3d"),
|
||||
"check_cmd": "k3d --version",
|
||||
"bin": "k3d",
|
||||
},
|
||||
{
|
||||
"id": "opentofu",
|
||||
"name": "OpenTofu",
|
||||
"description": "Open-source infrastructure as code tool",
|
||||
"url": "https://opentofu.org",
|
||||
"install_cmd": install("opentofu"),
|
||||
"check_cmd": "tofu --version",
|
||||
"bin": "tofu",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
_DEP_PLATFORM_CACHE: Optional[dict] = None
|
||||
|
||||
|
||||
def _detect_linux_package_manager() -> tuple[Optional[str], Optional[str]]:
|
||||
if shutil.which("apt-get") and shutil.which("dpkg"):
|
||||
return "apt", "apt/deb"
|
||||
if shutil.which("dnf"):
|
||||
return "dnf", "rpm"
|
||||
if shutil.which("yum"):
|
||||
return "yum", "rpm"
|
||||
if shutil.which("rpm"):
|
||||
return "rpm", "rpm"
|
||||
return None, None
|
||||
|
||||
|
||||
def detect_dependency_platform(refresh: bool = False) -> dict:
|
||||
global _DEP_PLATFORM_CACHE
|
||||
if _DEP_PLATFORM_CACHE is not None and not refresh:
|
||||
return dict(_DEP_PLATFORM_CACHE)
|
||||
|
||||
os_raw = (platform.system() or "").lower()
|
||||
arch = (platform.machine() or "unknown").lower()
|
||||
info: dict[str, Optional[str] | bool] = {
|
||||
"os": None,
|
||||
"arch": arch,
|
||||
"package_manager": None,
|
||||
"package_family": None,
|
||||
"supported": False,
|
||||
"reason": None,
|
||||
}
|
||||
|
||||
if os_raw == "darwin":
|
||||
info.update(
|
||||
{
|
||||
"os": "macos",
|
||||
"package_manager": "brew",
|
||||
"package_family": "brew",
|
||||
"supported": True,
|
||||
}
|
||||
)
|
||||
elif os_raw == "linux":
|
||||
manager, family = _detect_linux_package_manager()
|
||||
info.update({"os": "linux", "package_manager": manager, "package_family": family})
|
||||
if manager in {"apt", "dnf", "yum"}:
|
||||
info["supported"] = True
|
||||
elif manager == "rpm":
|
||||
info["reason"] = (
|
||||
"Linux RPM database detected but no supported package installer "
|
||||
"(dnf/yum) was found."
|
||||
)
|
||||
elif shutil.which("brew"):
|
||||
if _parse_bool(os.environ.get("PROLE_LINUX_ALLOW_BREW"), False):
|
||||
info.update(
|
||||
{
|
||||
"package_manager": "brew",
|
||||
"package_family": "brew",
|
||||
"supported": True,
|
||||
"reason": "Linux Homebrew explicitly enabled via PROLE_LINUX_ALLOW_BREW.",
|
||||
}
|
||||
)
|
||||
else:
|
||||
info["reason"] = (
|
||||
"Linux Homebrew detected but native package managers (apt/dnf/yum) "
|
||||
"were not found. Refusing brew fallback by default."
|
||||
)
|
||||
else:
|
||||
info["reason"] = "No supported Linux package manager detected (apt/dnf/yum)."
|
||||
else:
|
||||
info["reason"] = f"Unsupported operating system: {platform.system() or 'unknown'}"
|
||||
|
||||
_DEP_PLATFORM_CACHE = dict(info)
|
||||
return dict(info)
|
||||
|
||||
|
||||
def get_dependency_milestone_title() -> str:
|
||||
info = detect_dependency_platform()
|
||||
if info.get("os") == "linux":
|
||||
return "Linux Dependencies"
|
||||
if info.get("os") == "macos":
|
||||
return "Mac OS Dependencies"
|
||||
return "Dependencies"
|
||||
|
||||
|
||||
def get_dependency_platform_summary() -> str:
|
||||
info = detect_dependency_platform()
|
||||
return (
|
||||
f"os={info.get('os') or 'unknown'}, "
|
||||
f"arch={info.get('arch') or 'unknown'}, "
|
||||
f"package_family={info.get('package_family') or 'unknown'}, "
|
||||
f"package_manager={info.get('package_manager') or 'unknown'}"
|
||||
)
|
||||
|
||||
|
||||
def get_platform_dependencies() -> list[dict]:
|
||||
info = detect_dependency_platform()
|
||||
if not info.get("supported"):
|
||||
return []
|
||||
|
||||
if info.get("os") == "macos":
|
||||
return [dict(dep) for dep in _MACOS_DEPENDENCIES]
|
||||
|
||||
if info.get("os") == "linux":
|
||||
manager = info.get("package_manager")
|
||||
if manager == "brew":
|
||||
return [dict(dep) for dep in _MACOS_DEPENDENCIES if dep["id"] != "brew"]
|
||||
return _linux_dependencies(str(manager))
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def _validate_dependency_backend(dependencies: list[dict] | None = None) -> tuple[bool, str]:
|
||||
info = detect_dependency_platform()
|
||||
if not info.get("supported"):
|
||||
reason = info.get("reason") or "Unsupported dependency backend"
|
||||
return False, str(reason)
|
||||
|
||||
if info.get("os") == "linux":
|
||||
deps = dependencies if dependencies is not None else get_platform_dependencies()
|
||||
brew_refs = [dep.get("id") for dep in deps if "brew" in str(dep.get("install_cmd") or "")]
|
||||
if brew_refs and info.get("package_manager") != "brew":
|
||||
return (
|
||||
False,
|
||||
"Linux dependency set contains Homebrew commands while native package manager "
|
||||
f"{info.get('package_manager')} is active: {', '.join([str(r) for r in brew_refs])}",
|
||||
)
|
||||
return True, ""
|
||||
|
||||
|
||||
DEPENDENCIES = get_platform_dependencies()
|
||||
|
||||
|
||||
def _resolve_brew_bin() -> str | None:
|
||||
"""Return an absolute path to a `brew` binary if one is found in common locations."""
|
||||
|
||||
@ -1157,13 +1389,20 @@ def _augment_env_for_brew(env: dict | None = None) -> dict:
|
||||
return env
|
||||
|
||||
|
||||
def _augment_env_for_dependency_backend(env: dict | None = None) -> dict:
|
||||
info = detect_dependency_platform()
|
||||
if info.get("package_manager") == "brew":
|
||||
return _augment_env_for_brew(env)
|
||||
return env or os.environ.copy()
|
||||
|
||||
|
||||
def get_dep_info(dep: dict) -> Tuple[bool, Optional[str], Optional[str]]:
|
||||
bin_name = dep.get("bin") or dep["name"]
|
||||
location = None
|
||||
version = None
|
||||
installed = False
|
||||
try:
|
||||
env = _augment_env_for_brew(os.environ.copy())
|
||||
env = _augment_env_for_dependency_backend(os.environ.copy())
|
||||
if bin_name:
|
||||
# Avoid `bash -l` here: login shell init scripts on some distros
|
||||
# can overwrite PATH, defeating our augmented PATH for Linuxbrew.
|
||||
@ -1222,7 +1461,8 @@ def load_dependencies(refresh: bool = True) -> list[dict]:
|
||||
- location: Optional[str]
|
||||
- version: Optional[str]
|
||||
"""
|
||||
deps: list[dict] = [dict(d) for d in DEPENDENCIES]
|
||||
deps_source = DEPENDENCIES if DEPENDENCIES else get_platform_dependencies()
|
||||
deps: list[dict] = [dict(d) for d in deps_source]
|
||||
if refresh:
|
||||
for dep in deps:
|
||||
try:
|
||||
|
||||
@ -4749,7 +4749,7 @@ class KnoeConsoleInstaller(KnoeInstaller):
|
||||
if self._deployment_mode() != "k8s":
|
||||
return True
|
||||
|
||||
gcloud_env = inst_config._augment_env_for_brew(os.environ.copy())
|
||||
gcloud_env = inst_config._augment_env_for_dependency_backend(os.environ.copy())
|
||||
cmd_base = ["gcloud"]
|
||||
token_cmd = cmd_base + ["auth", "print-access-token", "--quiet"]
|
||||
try:
|
||||
|
||||
@ -43,16 +43,37 @@ def _stream_line(line: str) -> None:
|
||||
|
||||
class DependenciesMilestone(Milestone):
|
||||
def __init__(self):
|
||||
super().__init__("dependencies", "Dependency Verification")
|
||||
super().__init__("dependencies", inst_config.get_dependency_milestone_title())
|
||||
self.logger = logging.getLogger("DependenciesMilestone")
|
||||
|
||||
def execute(
|
||||
self, state: InstallerState, progress: ProgressCallback | None = None
|
||||
) -> None:
|
||||
platform_info = inst_config.detect_dependency_platform()
|
||||
self.logger.info(
|
||||
"Dependency platform detected: %s",
|
||||
inst_config.get_dependency_platform_summary(),
|
||||
)
|
||||
|
||||
if progress:
|
||||
progress("Checking dependencies...", 0.1)
|
||||
|
||||
dependencies = inst_config.DEPENDENCIES
|
||||
if not dependencies:
|
||||
dependencies = inst_config.get_platform_dependencies()
|
||||
|
||||
backend_ok, backend_reason = inst_config._validate_dependency_backend()
|
||||
if not backend_ok:
|
||||
self.logger.error("Dependency backend unsupported: %s", backend_reason)
|
||||
self._set_status(state, "Missing")
|
||||
return
|
||||
|
||||
if not dependencies:
|
||||
reason = platform_info.get("reason") or "No dependencies configured for detected platform"
|
||||
self.logger.error("Dependency resolution failed: %s", reason)
|
||||
self._set_status(state, "Missing")
|
||||
return
|
||||
|
||||
missing = []
|
||||
|
||||
for i, dep in enumerate(dependencies):
|
||||
@ -130,7 +151,7 @@ class DependenciesMilestone(Milestone):
|
||||
if cluster_env not in {"prod", "production", "k8s"}:
|
||||
return True
|
||||
|
||||
gcloud_env = inst_config._augment_env_for_brew(os.environ.copy())
|
||||
gcloud_env = inst_config._augment_env_for_dependency_backend(os.environ.copy())
|
||||
cmd_base = ["gcloud"]
|
||||
token_cmd = cmd_base + ["auth", "print-access-token", "--quiet"]
|
||||
try:
|
||||
@ -175,9 +196,11 @@ class DependenciesMilestone(Milestone):
|
||||
return False
|
||||
|
||||
def _set_status(self, state: InstallerState, status: str):
|
||||
if "Dependencies" not in state.config_data:
|
||||
state.config_data["Dependencies"] = {}
|
||||
state.config_data["Dependencies"]["STATUS"] = status
|
||||
sections = ["Dependencies", inst_config.get_dependency_milestone_title()]
|
||||
for section in sections:
|
||||
if section not in state.config_data:
|
||||
state.config_data[section] = {}
|
||||
state.config_data[section]["STATUS"] = status
|
||||
|
||||
|
||||
class NetworkScanMilestone(Milestone):
|
||||
|
||||
@ -61,7 +61,7 @@ class Milestone(ABC):
|
||||
proc_env = os.environ.copy()
|
||||
if env:
|
||||
proc_env.update(env)
|
||||
proc_env = inst_config._augment_env_for_brew(proc_env)
|
||||
proc_env = inst_config._augment_env_for_dependency_backend(proc_env)
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
|
||||
@ -10,7 +10,6 @@ Covers:
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@ -22,6 +21,10 @@ from knoe.config import (
|
||||
_extract_inline_vault_block,
|
||||
_try_read_ansible_vault_value,
|
||||
get_dep_info,
|
||||
detect_dependency_platform,
|
||||
get_dependency_milestone_title,
|
||||
get_platform_dependencies,
|
||||
_validate_dependency_backend,
|
||||
load_dependencies,
|
||||
get_ui_icon_image_path,
|
||||
get_ui_background_image_path,
|
||||
@ -296,6 +299,17 @@ def test_get_dep_info_prepends_linuxbrew_to_path_when_present():
|
||||
return m
|
||||
|
||||
with patch.dict(os.environ, {"PATH": "/usr/bin:/bin"}, clear=False), \
|
||||
patch(
|
||||
"knoe.config.detect_dependency_platform",
|
||||
return_value={
|
||||
"os": "macos",
|
||||
"arch": "arm64",
|
||||
"package_manager": "brew",
|
||||
"package_family": "brew",
|
||||
"supported": True,
|
||||
"reason": None,
|
||||
},
|
||||
), \
|
||||
patch("knoe.config.os.path.isfile", side_effect=lambda p: p == "/home/linuxbrew/.linuxbrew/bin/brew"), \
|
||||
patch("knoe.config.os.access", return_value=True), \
|
||||
patch("knoe.config.os.path.isdir", side_effect=lambda p: p == "/home/linuxbrew/.linuxbrew/sbin"), \
|
||||
@ -319,6 +333,92 @@ def test_get_dep_info_version_cmd_success():
|
||||
assert ver == "Client Version: v1.28.0"
|
||||
|
||||
|
||||
def test_detect_dependency_platform_linux_apt():
|
||||
with patch("knoe.config._DEP_PLATFORM_CACHE", None), \
|
||||
patch("knoe.config.platform.system", return_value="Linux"), \
|
||||
patch("knoe.config.platform.machine", return_value="aarch64"), \
|
||||
patch(
|
||||
"knoe.config.shutil.which",
|
||||
side_effect=lambda cmd: {
|
||||
"apt-get": "/usr/bin/apt-get",
|
||||
"dpkg": "/usr/bin/dpkg",
|
||||
}.get(cmd),
|
||||
):
|
||||
info = detect_dependency_platform(refresh=True)
|
||||
|
||||
assert info["os"] == "linux"
|
||||
assert info["package_family"] == "apt/deb"
|
||||
assert info["package_manager"] == "apt"
|
||||
assert info["supported"] is True
|
||||
|
||||
|
||||
def test_detect_dependency_platform_linux_rejects_brew_fallback_by_default():
|
||||
with patch("knoe.config._DEP_PLATFORM_CACHE", None), \
|
||||
patch("knoe.config.platform.system", return_value="Linux"), \
|
||||
patch("knoe.config.platform.machine", return_value="aarch64"), \
|
||||
patch("knoe.config.shutil.which", side_effect=lambda cmd: "/home/linuxbrew/.linuxbrew/bin/brew" if cmd == "brew" else None), \
|
||||
patch.dict(os.environ, {"PROLE_LINUX_ALLOW_BREW": ""}, clear=False):
|
||||
info = detect_dependency_platform(refresh=True)
|
||||
|
||||
assert info["os"] == "linux"
|
||||
assert info["supported"] is False
|
||||
assert "Refusing brew fallback" in str(info["reason"])
|
||||
|
||||
|
||||
def test_get_platform_dependencies_linux_apt_includes_opentofu_without_brew_dependency():
|
||||
with patch(
|
||||
"knoe.config.detect_dependency_platform",
|
||||
return_value={
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"package_manager": "apt",
|
||||
"package_family": "apt/deb",
|
||||
"supported": True,
|
||||
"reason": None,
|
||||
},
|
||||
):
|
||||
deps = get_platform_dependencies()
|
||||
|
||||
ids = {d["id"] for d in deps}
|
||||
assert "brew" not in ids
|
||||
opentofu = next(d for d in deps if d["id"] == "opentofu")
|
||||
assert "apt-get install -y opentofu" in str(opentofu.get("install_cmd") or "")
|
||||
|
||||
|
||||
def test_validate_dependency_backend_rejects_linux_brew_commands():
|
||||
with patch(
|
||||
"knoe.config.detect_dependency_platform",
|
||||
return_value={
|
||||
"os": "linux",
|
||||
"arch": "aarch64",
|
||||
"package_manager": "apt",
|
||||
"package_family": "apt/deb",
|
||||
"supported": True,
|
||||
"reason": None,
|
||||
},
|
||||
):
|
||||
ok, reason = _validate_dependency_backend(
|
||||
[{"id": "opentofu", "install_cmd": "brew install opentofu"}]
|
||||
)
|
||||
|
||||
assert ok is False
|
||||
assert "Homebrew commands" in reason
|
||||
|
||||
|
||||
def test_get_dependency_milestone_title_linux_and_macos():
|
||||
with patch(
|
||||
"knoe.config.detect_dependency_platform",
|
||||
return_value={"os": "linux"},
|
||||
):
|
||||
assert get_dependency_milestone_title() == "Linux Dependencies"
|
||||
|
||||
with patch(
|
||||
"knoe.config.detect_dependency_platform",
|
||||
return_value={"os": "macos"},
|
||||
):
|
||||
assert get_dependency_milestone_title() == "Mac OS Dependencies"
|
||||
|
||||
|
||||
def test_get_dep_info_exception_returns_false():
|
||||
dep = {"id": "broken", "name": "broken", "bin": "broken"}
|
||||
with patch("knoe.config.shutil.which", side_effect=OSError("broken")):
|
||||
|
||||
@ -665,7 +665,7 @@ def test_dependencies_k8s_fails_without_gcloud_auth_session():
|
||||
|
||||
with patch("knoe.config.get_dep_info", return_value=(True, "/usr/bin/gcloud", "1.0")), \
|
||||
patch("knoe.config.DEPENDENCIES", [dep]), \
|
||||
patch("knoe.config._augment_env_for_brew", return_value={"PATH": "x"}), \
|
||||
patch("knoe.config._augment_env_for_dependency_backend", return_value={"PATH": "x"}), \
|
||||
patch("knoe.config._gcloud_plain", side_effect=_unexpected_gcloud_plain), \
|
||||
patch("knoe.core.milestones.subprocess.run", return_value=no_token), \
|
||||
patch("knoe.core.milestones.sys.stdin.isatty", return_value=False):
|
||||
@ -692,7 +692,7 @@ def test_dependencies_k8s_interactive_login_recovers_auth_session():
|
||||
|
||||
with patch("knoe.config.get_dep_info", return_value=(True, "/usr/bin/gcloud", "1.0")), \
|
||||
patch("knoe.config.DEPENDENCIES", [dep]), \
|
||||
patch("knoe.config._augment_env_for_brew", return_value={"PATH": "x"}), \
|
||||
patch("knoe.config._augment_env_for_dependency_backend", return_value={"PATH": "x"}), \
|
||||
patch("knoe.config._gcloud_plain", side_effect=_unexpected_gcloud_plain), \
|
||||
patch("knoe.core.milestones.subprocess.run", side_effect=[no_token, with_token]), \
|
||||
patch("knoe.core.milestones.sys.stdin.isatty", return_value=True), \
|
||||
|
||||
Loading…
Reference in New Issue
Block a user