env_provider.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. # -*- coding: utf-8 -*-
  2. """
  3. Umgebungsvariablen-basierter Secrets-Provider.
  4. Lädt Secrets aus Umgebungsvariablen mit optionalem Präfix.
  5. """
  6. from __future__ import annotations
  7. import os
  8. from typing import Any, TypeVar
  9. from trixy_core.config.secrets.provider import SecretsProvider, SecretValue
  10. T = TypeVar("T")
  11. class EnvironmentSecretsProvider(SecretsProvider):
  12. """
  13. Provider für Secrets aus Umgebungsvariablen.
  14. Unterstützt Präfixe für Namespacing und verschiedene
  15. Namenskonventionen.
  16. Example:
  17. provider = EnvironmentSecretsProvider(prefix="TRIXY_SECRET_")
  18. # Liest TRIXY_SECRET_DATABASE_PASSWORD
  19. password = provider.get("database_password")
  20. # Mit Konvertierung
  21. provider = EnvironmentSecretsProvider(
  22. prefix="APP_",
  23. transform="upper" # key wird zu UPPER_CASE
  24. )
  25. """
  26. def __init__(
  27. self,
  28. prefix: str = "",
  29. transform: str = "upper",
  30. separator: str = "_",
  31. env: dict[str, str] | None = None,
  32. ) -> None:
  33. """
  34. Initialisiert den Environment-Provider.
  35. Args:
  36. prefix: Präfix für Variablennamen.
  37. transform: Namenstransformation ('upper', 'lower', 'none').
  38. separator: Trennzeichen für hierarchische Namen.
  39. env: Optionales Environment-Dictionary.
  40. """
  41. super().__init__("EnvironmentSecretsProvider")
  42. self._prefix = prefix
  43. self._transform = transform
  44. self._separator = separator
  45. self._env = env if env is not None else os.environ
  46. def _transform_key(self, key: str) -> str:
  47. """Transformiert einen Schlüssel in den Variablennamen."""
  48. # Punkte durch Separator ersetzen
  49. transformed = key.replace(".", self._separator)
  50. # Transformation anwenden
  51. if self._transform == "upper":
  52. transformed = transformed.upper()
  53. elif self._transform == "lower":
  54. transformed = transformed.lower()
  55. # Präfix hinzufügen
  56. return f"{self._prefix}{transformed}"
  57. def _reverse_transform_key(self, env_name: str) -> str | None:
  58. """Transformiert einen Variablennamen zurück in einen Schlüssel."""
  59. # Präfix entfernen
  60. if not env_name.startswith(self._prefix):
  61. return None
  62. key = env_name[len(self._prefix):]
  63. # Transformation umkehren
  64. if self._transform == "upper":
  65. key = key.lower()
  66. elif self._transform == "lower":
  67. key = key.upper()
  68. # Separator durch Punkte ersetzen
  69. key = key.replace(self._separator, ".")
  70. return key
  71. def get(self, key: str, default: T | None = None) -> T | None:
  72. """
  73. Holt einen Secret-Wert aus der Umgebung.
  74. Args:
  75. key: Schlüssel (wird transformiert).
  76. default: Standardwert.
  77. Returns:
  78. Secret-Wert oder Default.
  79. """
  80. env_name = self._transform_key(key)
  81. return self._env.get(env_name, default)
  82. def get_secret(self, key: str) -> SecretValue | None:
  83. """
  84. Holt ein Secret mit Metadaten.
  85. Args:
  86. key: Schlüssel.
  87. Returns:
  88. SecretValue oder None.
  89. """
  90. env_name = self._transform_key(key)
  91. value = self._env.get(env_name)
  92. if value is None:
  93. return None
  94. return SecretValue(
  95. key=key,
  96. value=value,
  97. source=f"env:{env_name}",
  98. metadata={"env_name": env_name},
  99. )
  100. def exists(self, key: str) -> bool:
  101. """Prüft, ob die Umgebungsvariable existiert."""
  102. env_name = self._transform_key(key)
  103. return env_name in self._env
  104. def list_keys(self) -> list[str]:
  105. """Listet alle Schlüssel mit dem konfigurierten Präfix."""
  106. keys = []
  107. for env_name in self._env:
  108. key = self._reverse_transform_key(env_name)
  109. if key is not None:
  110. keys.append(key)
  111. return sorted(keys)
  112. def set(self, key: str, value: Any) -> bool:
  113. """
  114. Setzt eine Umgebungsvariable.
  115. Args:
  116. key: Schlüssel.
  117. value: Wert.
  118. Returns:
  119. True.
  120. """
  121. env_name = self._transform_key(key)
  122. os.environ[env_name] = str(value)
  123. if self._env is not os.environ:
  124. self._env[env_name] = str(value)
  125. return True
  126. def delete(self, key: str) -> bool:
  127. """
  128. Löscht eine Umgebungsvariable.
  129. Args:
  130. key: Schlüssel.
  131. Returns:
  132. True wenn vorhanden.
  133. """
  134. env_name = self._transform_key(key)
  135. if env_name in os.environ:
  136. del os.environ[env_name]
  137. if env_name in self._env:
  138. del self._env[env_name]
  139. return True
  140. return False
  141. def get_env_name(self, key: str) -> str:
  142. """
  143. Gibt den Umgebungsvariablennamen für einen Schlüssel zurück.
  144. Args:
  145. key: Schlüssel.
  146. Returns:
  147. Umgebungsvariablenname.
  148. """
  149. return self._transform_key(key)
  150. @classmethod
  151. def with_dotenv(
  152. cls,
  153. dotenv_path: str = ".env",
  154. prefix: str = "",
  155. override: bool = False,
  156. ) -> "EnvironmentSecretsProvider":
  157. """
  158. Erstellt einen Provider und lädt eine .env-Datei.
  159. Args:
  160. dotenv_path: Pfad zur .env-Datei.
  161. prefix: Präfix für Variablen.
  162. override: Ob existierende Variablen überschrieben werden.
  163. Returns:
  164. EnvironmentSecretsProvider-Instanz.
  165. """
  166. from pathlib import Path
  167. env = dict(os.environ)
  168. dotenv = Path(dotenv_path)
  169. if dotenv.exists():
  170. with open(dotenv, "r") as f:
  171. for line in f:
  172. line = line.strip()
  173. # Kommentare und leere Zeilen überspringen
  174. if not line or line.startswith("#"):
  175. continue
  176. # Name=Wert parsen
  177. if "=" in line:
  178. name, value = line.split("=", 1)
  179. name = name.strip()
  180. value = value.strip()
  181. # Anführungszeichen entfernen
  182. if (
  183. (value.startswith('"') and value.endswith('"'))
  184. or (value.startswith("'") and value.endswith("'"))
  185. ):
  186. value = value[1:-1]
  187. # Nur setzen wenn nicht vorhanden oder override
  188. if override or name not in env:
  189. env[name] = value
  190. os.environ[name] = value
  191. return cls(prefix=prefix, env=env)