from __future__ import annotations import os import re from dataclasses import dataclass, field EXPECTED_PROJECT = "teleo-501523" EXPECTED_REGION = "europe-west6" EXPECTED_INSTANCE = "teleo-pgvector-standby" EXPECTED_CONNECTION_NAME = f"{EXPECTED_PROJECT}:{EXPECTED_REGION}:{EXPECTED_INSTANCE}" EXPECTED_DATABASE = "teleo_canonical" EXPECTED_AUTHORIZATION_ROLE = "kb_observatory_read" EXPECTED_IAM_DATABASE_USER = f"sa-observatory-read-adapter@{EXPECTED_PROJECT}.iam" SERVICE_REVISION_PATTERN = re.compile(r"^[0-9a-f]{40}$") @dataclass(frozen=True) class Settings: project_id: str instance_connection_name: str database: str iam_database_user: str authorization_role: str api_key: str = field(repr=False) service_revision: str = "unknown" port: int = 8080 @classmethod def from_env(cls) -> Settings: settings = cls( project_id=os.environ.get("GCP_PROJECT_ID", ""), instance_connection_name=os.environ.get("CLOUD_SQL_INSTANCE", ""), database=os.environ.get("DB_NAME", ""), iam_database_user=os.environ.get("DB_IAM_USER", ""), authorization_role=os.environ.get("DB_AUTHORIZATION_ROLE", ""), api_key=os.environ.get("OBSERVATORY_API_KEY", ""), service_revision=os.environ.get("SERVICE_REVISION", "unknown"), port=int(os.environ.get("PORT", "8080")), ) settings.validate() return settings def validate(self) -> None: if self.project_id != EXPECTED_PROJECT: raise ValueError(f"GCP_PROJECT_ID must be {EXPECTED_PROJECT}") if self.instance_connection_name != EXPECTED_CONNECTION_NAME: raise ValueError(f"CLOUD_SQL_INSTANCE must be {EXPECTED_CONNECTION_NAME}") if self.database != EXPECTED_DATABASE: raise ValueError(f"DB_NAME must be {EXPECTED_DATABASE}") if self.authorization_role != EXPECTED_AUTHORIZATION_ROLE: raise ValueError(f"DB_AUTHORIZATION_ROLE must be {EXPECTED_AUTHORIZATION_ROLE}") if self.iam_database_user != EXPECTED_IAM_DATABASE_USER: raise ValueError(f"DB_IAM_USER must be {EXPECTED_IAM_DATABASE_USER}") if ( len(self.api_key) < 32 or len(self.api_key) > 512 or not self.api_key.isascii() or any(character.isspace() for character in self.api_key) ): raise ValueError("OBSERVATORY_API_KEY must be 32-512 non-whitespace ASCII characters") if SERVICE_REVISION_PATTERN.fullmatch(self.service_revision) is None: raise ValueError("SERVICE_REVISION must be a lowercase 40-character Git commit SHA") if not 1 <= self.port <= 65535: raise ValueError("PORT is outside the valid TCP port range")