Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions echo/agent/settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import base64
import json
from functools import lru_cache
from typing import Any, Optional
Expand Down Expand Up @@ -37,13 +38,24 @@ class Settings(BaseSettings):
@field_validator("vertex_credentials", "gcp_sa_json", mode="before")
@classmethod
def _parse_service_account_json(cls, value: Any) -> Optional[dict[str, Any]]:
# Accept a dict, a raw JSON string, or a base64-encoded JSON string.
# The prod/echo-next secret stores GCP_SA_JSON base64-encoded, matching
# the server's _coerce_service_account behavior.
if value is None or isinstance(value, dict):
return value
if isinstance(value, str):
stripped = value.strip()
if not stripped:
if stripped in {"", "null", "None"}:
return None
return json.loads(stripped)
try:
return json.loads(stripped)
except json.JSONDecodeError:
try:
return json.loads(base64.b64decode(stripped))
except Exception as exc:
raise ValueError(
"Service account JSON must be valid JSON or base64-encoded JSON"
) from exc
raise ValueError("Expected a JSON object or JSON string")

class Config:
Expand Down
15 changes: 15 additions & 0 deletions echo/agent/tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,18 @@ def test_settings_reads_env(monkeypatch):
assert settings.agent_cors_origins == "http://localhost:1111,http://localhost:2222"

get_settings.cache_clear()


def test_settings_decodes_base64_gcp_sa_json(monkeypatch):
import base64
import json as _json

get_settings.cache_clear()
raw = {"type": "service_account", "project_id": "b64-proj"}
b64 = base64.b64encode(_json.dumps(raw).encode()).decode()
monkeypatch.setenv("GCP_SA_JSON", b64)
monkeypatch.setenv("VERTEX_LOCATION", "eu")

settings = get_settings()
assert settings.gcp_sa_json == raw
get_settings.cache_clear()
Loading