Skip to content

Commit 3430eec

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 83d8d00 commit 3430eec

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
@@ -218,6 +219,30 @@ def _merge_dicts(base: dict[str, Any], override: dict[str, Any]) -> dict[str, An
218219
return base
219220

220221

222+
def _parse_env_value(raw: str) -> Any:
223+
"""Parse env var value as TOML, falling back to plain string."""
224+
try:
225+
return tomllib.loads(f"x = {raw}")["x"]
226+
except tomllib.TOMLDecodeError:
227+
return raw
228+
229+
230+
def _apply_env_overrides(data: dict[str, Any]) -> dict[str, Any]:
231+
"""Override config values from XCPNG_CFG__* env vars."""
232+
prefix = "XCPNG_TESTS_"
233+
overrides: dict[str, Any] = {}
234+
for key, raw in os.environ.items():
235+
if not key.startswith(prefix):
236+
continue
237+
path = key.removeprefix(prefix).lower().split("__")
238+
value = _parse_env_value(raw)
239+
branch = overrides
240+
for part in path[:-1]:
241+
branch = branch.setdefault(part, {})
242+
branch[path[-1]] = value
243+
return _merge_dicts(data, overrides) if overrides else data
244+
245+
221246
def _replace_password_hash_placeholder(obj: Any, password_hash: str) -> Any:
222247
"""Recursively replace <PASSWORD_HASH> placeholders with actual hash."""
223248
if isinstance(obj, str):
@@ -249,6 +274,7 @@ def _build_config(base_data: dict[str, Any]) -> Config:
249274
password = base_data["host"].get("default_password", "")
250275
password_hash = hash_password(password)
251276
base_data = _replace_password_hash_placeholder(base_data, password_hash)
277+
base_data = _apply_env_overrides(base_data)
252278
try:
253279
return Config(**base_data)
254280
except Exception as e:

0 commit comments

Comments
 (0)