Files
redmine-reporter/redmine_reporter/yaml_config.py
Кокос Артем Николаевич c962a93f30 feat: YAML config support (~/.config/redmine-reporter/config.yml)
- Add AppConfig/SmtpConfig/EmailConfig dataclasses with from_yaml()/from_env()
- Add yaml_config.py: ${VAR} resolver, 0700/0600 permission helpers
- Config.get_*() methods gain YAML fallback in priority chain
- Priority: CLI > env > .env > YAML > code defaults
- CLI --init-config generates YAML from current environment
- --force flag allows overwriting existing config
- Secrets default to ${VAR} references, plaintext allowed
- Full backward compatibility with existing .env setups

Closes #46
2026-07-03 18:00:28 +07:00

59 lines
1.7 KiB
Python

import logging
import os
import re
from pathlib import Path
logger = logging.getLogger(__name__)
_ENV_VAR_RE = re.compile(r"\$\{([^}]+)\}")
def resolve_env_vars(value: str) -> str:
"""Replace ${VAR} patterns with environment variable values.
Non-recursive: if ${A} expands to a literal ${B}, ${B} is NOT resolved.
Unknown variables are replaced with empty string and a warning is logged.
"""
if not isinstance(value, str):
return value
def _replacer(match: re.Match) -> str:
var_name = match.group(1)
env_value = os.environ.get(var_name)
if env_value is None:
logger.warning("Environment variable %s is not set, using empty string", var_name)
return ""
return env_value
return _ENV_VAR_RE.sub(_replacer, value)
def ensure_config_dir(path: Path | None = None) -> Path:
"""Create ~/.config/redmine-reporter/ with 0o700 permissions.
Returns the path to the config directory.
No-op if the directory already exists.
"""
if path is None:
path = Path.home() / ".config" / "redmine-reporter"
path.mkdir(mode=0o700, parents=True, exist_ok=True)
return path
def check_file_permissions(path: Path) -> list[str]:
"""Check that a config file has safe permissions (0o600 or stricter).
Returns a list of warning messages. Empty list means no issues.
"""
warnings: list[str] = []
if not path.exists():
return warnings
mode = path.stat().st_mode & 0o777
if mode > 0o600:
warnings.append(
f"Config file permissions are too open ({mode:04o}). "
f"Expected 0600. Fix with: chmod 600 {path}"
)
return warnings