fester/backend/config.py

108 lines
3.7 KiB
Python
Executable File

"""Configuration loader.
Reads from the path in FESTER_CONFIG env var if set, otherwise from
config.yaml in the project root. Provides CONFIG as a module-level dict.
F-06 / F-17 remediation:
External-service endpoints (MinIO, Prometheus, Firecracker SSH key,
default node host) are now resolved from environment variables with
sensible fallbacks — no hardcoded ``localhost:9000`` /
``http://localhost:9090`` / ``/root/.ssh/fc_builder`` literals in
the runtime modules. Operators override via env vars; the YAML
config can still set them under ``CONFIG["external"]``.
"""
import os
from pathlib import Path
import yaml
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Allow override via env var (for tests / dev / multi-cluster setups)
_env_config = os.environ.get("FESTER_CONFIG")
if _env_config:
CONFIG_PATH = _env_config
else:
CONFIG_PATH = os.path.join(BASE_DIR, "config.yaml")
# Graceful fallback: if the requested config doesn't exist, use a sensible
# default so the backend can still boot (e.g. in a fresh clone or container).
if not Path(CONFIG_PATH).exists():
CONFIG = {
"master": {"name": "fester-master", "role": "control"},
"nodes": [
{
"name": os.environ.get("FESTER_NODE_NAME", "localhost"),
"host": os.environ.get("FESTER_NODE_HOST", "127.0.0.1"),
"max_jobs": int(os.environ.get("FESTER_NODE_MAX_JOBS", "4")),
},
],
"projects": [],
"btc": {
"enabled": True,
"root": "/opt/BTC",
"primary": False,
},
}
else:
with open(CONFIG_PATH, "r") as f:
CONFIG = yaml.safe_load(f) or {}
# Ensure required keys exist
CONFIG.setdefault("master", {"name": "fester-master", "role": "control"})
CONFIG.setdefault("nodes", [])
CONFIG.setdefault("projects", [])
# BTC (Build Tool Chain) configuration
CONFIG.setdefault("btc", {})
CONFIG["btc"].setdefault("enabled", True)
CONFIG["btc"].setdefault("root", "/opt/BTC")
CONFIG["btc"].setdefault("primary", False)
# ---------------------------------------------------------------------
# External-service endpoints (F-06 / F-17)
# ---------------------------------------------------------------------
# Single source of truth for every external service Fester talks to.
# Modules import EXTERNAL_CONFIG instead of hardcoding URLs / paths.
def _env_bool(name: str, default: bool = False) -> bool:
return os.environ.get(name, "").lower() in ("1", "true", "yes", "on")
EXTERNAL_CONFIG: dict = {
# MinIO build cache
"minio_endpoint": os.environ.get("FESTER_MINIO_ENDPOINT", "localhost:9000"),
"minio_access_key": os.environ.get("FESTER_MINIO_ACCESS_KEY", ""),
"minio_secret_key": os.environ.get("FESTER_MINIO_SECRET_KEY", ""),
"minio_bucket": os.environ.get("FESTER_MINIO_BUCKET", "fester-cache"),
"minio_secure": _env_bool("FESTER_MINIO_SECURE", False),
"minio_required": _env_bool("FESTER_MINIO_REQUIRED", False),
# Prometheus
"prometheus_url": os.environ.get(
"FESTER_PROM_URL", os.environ.get("PROM_URL", "http://localhost:9090"),
),
# Firecracker
"firecracker_bin": os.environ.get("FESTER_FIRECRACKER_BIN", "firecracker"),
"firecracker_ssh_key": os.environ.get(
"FESTER_FC_SSH_KEY", "/root/.ssh/fc_builder",
),
"firecracker_socket_dir": os.environ.get(
"FESTER_FC_SOCKET_DIR", "/run/fester/firecracker",
),
# Cluster agent
"agent_port": int(os.environ.get("FESTER_AGENT_PORT", "8787")),
}
def get_external() -> dict:
"""Return a deep copy of the external-service config dict.
Returned at call time so tests / operators can monkey-patch env
vars between requests.
"""
return dict(EXTERNAL_CONFIG)