Skip to content

Commit ef7ae50

Browse files
committed
Allow overriding config values via environment variables
Configuration values can now be overridden at runtime with `XCPNG_TESTS_`-prefixed environment variables. Double underscores separate path segments (e.g. `XCPNG_TESTS_network__free_nics='["eth1"]'` overrides `config.network.free_nics`). Values are parsed as TOML when possible, falling back to plain strings - so booleans, integers, arrays, and inline tables work naturally alongside plain string values. Signed-off-by: Gaëtan Lehmann <gaetan.lehmann@vates.tech>
1 parent 5f93fa3 commit ef7ae50

1 file changed

Lines changed: 26 additions & 0 deletions

File tree

lib/config_loader.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import os
34
import sys
45
import tomllib
56
import warnings
@@ -223,6 +224,30 @@ def _merge_dicts(base: dict[str, Any], override: dict[str, Any]) -> dict[str, An
223224
return base
224225

225226

227+
def _parse_env_value(raw: str) -> Any:
228+
"""Parse env var value as TOML, falling back to plain string."""
229+
try:
230+
return tomllib.loads(f"x = {raw}")["x"]
231+
except tomllib.TOMLDecodeError:
232+
return raw
233+
234+
235+
def _apply_env_overrides(data: dict[str, Any]) -> dict[str, Any]:
236+
"""Override config values from XCPNG_CFG__* env vars."""
237+
prefix = "XCPNG_TESTS_"
238+
overrides: dict[str, Any] = {}
239+
for key, raw in os.environ.items():
240+
if not key.startswith(prefix):
241+
continue
242+
path = key.removeprefix(prefix).lower().split("__")
243+
value = _parse_env_value(raw)
244+
branch = overrides
245+
for part in path[:-1]:
246+
branch = branch.setdefault(part, {})
247+
branch[path[-1]] = value
248+
return _merge_dicts(data, overrides) if overrides else data
249+
250+
226251
def _replace_password_hash_placeholder(obj: Any, password_hash: str) -> Any:
227252
"""Recursively replace <PASSWORD_HASH> placeholders with actual hash."""
228253
if isinstance(obj, str):
@@ -254,6 +279,7 @@ def _build_config(base_data: dict[str, Any]) -> Config:
254279
password = base_data["host"].get("default_password", "")
255280
password_hash = hash_password(password)
256281
base_data = _replace_password_hash_placeholder(base_data, password_hash)
282+
base_data = _apply_env_overrides(base_data)
257283
try:
258284
return Config(**base_data)
259285
except Exception as e:

0 commit comments

Comments
 (0)