fix: rename secrets.py → vault.py to avoid shadowing stdlib secrets module
Some checks failed
CI / test-cli (push) Failing after 10s
CI / test-sqlmesh (push) Failing after 9s
CI / test-web (push) Successful in 15s
CI / tag (push) Has been skipped

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-28 23:33:06 +01:00
parent 79ce3f2913
commit 9201a4dca9
4 changed files with 5 additions and 5 deletions

View File

@@ -0,0 +1,67 @@
"""Secrets management via SOPS + age."""
import subprocess
from functools import lru_cache
from pathlib import Path
# Default secrets file path (relative to repo root)
_DEFAULT_SECRETS_PATH = Path(__file__).parent.parent.parent / ".env.prod.sops"
def _parse_dotenv(text: str) -> dict[str, str]:
"""Parse dotenv-format text into a dict, skipping comments and blanks."""
result = {}
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" not in line:
continue
key, _, value = line.partition("=")
result[key.strip()] = value.strip()
return result
@lru_cache(maxsize=1)
def _load_environment(secrets_path: str = None) -> dict[str, str]:
"""Decrypt and load secrets from a SOPS-encrypted dotenv file."""
path = Path(secrets_path) if secrets_path else _DEFAULT_SECRETS_PATH
assert path.exists(), f"Secrets file not found: {path}"
try:
result = subprocess.run(
["sops", "--input-type", "dotenv", "--output-type", "dotenv", "--decrypt", str(path)],
capture_output=True,
text=True,
check=True,
timeout=30,
)
return _parse_dotenv(result.stdout)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to decrypt secrets: {e.stderr.strip()}")
except FileNotFoundError:
raise RuntimeError(
"sops not found. Install with: brew install sops "
"or see https://github.com/getsops/sops/releases"
)
def get_secret(key: str, secrets_path: str = None) -> str | None:
"""Get a secret value by key."""
env = _load_environment(secrets_path)
return env.get(key)
def list_secrets(secrets_path: str = None) -> list[str]:
"""List all available secret keys."""
env = _load_environment(secrets_path)
return list(env.keys())
def test_connection(secrets_path: str = None) -> bool:
"""Test that sops is available and can decrypt the secrets file."""
try:
_load_environment.cache_clear()
_load_environment(secrets_path)
return True
except Exception:
return False