From 450cee35565dfe54884a2ac71f108a9931a98f9b Mon Sep 17 00:00:00 2001 From: gx Date: Fri, 22 May 2026 05:46:03 +0100 Subject: [PATCH] =?UTF-8?q?controller:=20${VAR}=20env=20interpolation=20?= =?UTF-8?q?=D0=B2=20YAML=20config=20(=D0=B4=D0=BB=D1=8F=20secrets)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Config.from_yaml теперь recursively expands ${VAR} и ${VAR:-default} в string values через os.environ. Позволяет хранить tokens / passwords в gitignored .env, passed контейнеру через compose env section: controller.yaml: extra_http_headers: Authorization: "Bearer ${GRAFANA_TOKEN}" .env (gitignored): GRAFANA_TOKEN=glsa_xxx docker-compose.override.yml controller: environment: GRAFANA_TOKEN: "${GRAFANA_TOKEN:-}" # compose interpolates от .env Co-Authored-By: Claude Opus 4.7 --- controller/cuda_grid_controller/config.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/controller/cuda_grid_controller/config.py b/controller/cuda_grid_controller/config.py index bffd9cc..963fa4f 100644 --- a/controller/cuda_grid_controller/config.py +++ b/controller/cuda_grid_controller/config.py @@ -28,13 +28,33 @@ from __future__ import annotations import os +import re from pathlib import Path -from typing import Self +from typing import Any, Self import yaml from pydantic import BaseModel, Field, field_validator +_ENV_PATTERN = re.compile(r"\$\{(\w+)(?::-([^}]*))?\}") + + +def _expand_env(obj: Any) -> Any: + """Recursively replace ${VAR} and ${VAR:-default} в string values + через os.environ. Используется при load YAML config — secrets вроде + API tokens хранятся в .env (gitignored) и interpolate'ятся при start.""" + if isinstance(obj, str): + return _ENV_PATTERN.sub( + lambda m: os.environ.get(m.group(1), m.group(2) or ""), + obj, + ) + if isinstance(obj, dict): + return {k: _expand_env(v) for k, v in obj.items()} + if isinstance(obj, list): + return [_expand_env(v) for v in obj] + return obj + + class BrokerCfg(BaseModel): host: str = "localhost" port: int = 1883 @@ -202,4 +222,5 @@ class Config(BaseModel): def from_yaml(cls, path: Path | str) -> Self: with open(path) as f: data = yaml.safe_load(f) or {} + data = _expand_env(data) return cls.model_validate(data)