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
24 changes: 19 additions & 5 deletions psi/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,10 @@ def _fetch_and_register_infisical(
) -> None:
"""Fetch secrets from Infisical and register with Podman.

Populates ``cache_updates`` with ``{namespaced_name: value_bytes}`` so the
Populates ``cache_updates`` with ``{podman_hex_id: value_bytes}`` so the
caller can flush the encrypted cache once all workloads are processed.
The cache must be keyed by Podman's hex secret ID because that is what
the serve lookup path receives in ``$SECRET_ID``.
"""
from psi.providers.infisical import InfisicalProvider
from psi.providers.infisical.models import InfisicalConfig, resolve_auth
Expand Down Expand Up @@ -204,11 +206,13 @@ def _fetch_and_register_infisical(
logger.info("Found {} secrets", len(secrets))

logger.info("Merged: {} unique secrets", len(merged))
_register_secrets(settings, workload_name, merged)
id_map = _register_secrets(settings, workload_name, merged)
_generate_drop_in(settings, workload_name, merged)

for key, value in values.items():
cache_updates[f"{workload_name}--{key}"] = value
secret_id = id_map.get(key, "")
if secret_id:
cache_updates[secret_id] = value
finally:
provider.close()

Expand All @@ -217,10 +221,16 @@ def _register_secrets(
settings: PsiSettings,
workload_name: str,
secrets: dict[str, str],
) -> None:
"""Create namespaced Podman secrets with mapping data."""
) -> dict[str, str]:
"""Create namespaced Podman secrets with mapping data.

Returns:
Mapping of ``{secret_key: podman_hex_id}`` so the caller can
populate the encrypted cache with the correct lookup key.
"""
transport = httpx.HTTPTransport(uds=_podman_socket_url())
base = f"http://localhost/{_PODMAN_API_VERSION}"
id_map: dict[str, str] = {}

with httpx.Client(transport=transport, timeout=30.0) as client:
for secret_name, mapping_json in secrets.items():
Expand All @@ -232,8 +242,12 @@ def _register_secrets(
content=mapping_json.encode(),
)
resp.raise_for_status()
secret_id = resp.json().get("ID", "")
if secret_id:
id_map[secret_name] = secret_id

logger.info("Registered {} secrets with Podman", len(secrets))
return id_map


def _generate_drop_in(
Expand Down
28 changes: 28 additions & 0 deletions tests/test_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
_RETRY_DELAYS,
_generate_drop_in,
_is_retryable,
_register_secrets,
_setup_infisical_workload,
)

Expand Down Expand Up @@ -343,3 +344,30 @@ def mock_fetch(settings, workload_name, cache_updates):
_setup_infisical_workload(settings, "myapp", {})

assert call_count == 1


class TestRegisterSecretsIdMap:
def test_returns_secret_id_from_podman_api(self, tmp_path: Path) -> None:
"""_register_secrets returns {key: hex_id} for cache keying."""
delete_resp = httpx.Response(204, request=httpx.Request("DELETE", "http://x"))
create_resp = httpx.Response(
200,
json={"ID": "abc123hex"},
request=httpx.Request("POST", "http://x"),
)

def mock_request(method, url, **kwargs):
if method == "DELETE":
return delete_resp
return create_resp

settings = _make_settings(tmp_path)

with patch("psi.setup.httpx.Client") as mock_client_cls:
client = mock_client_cls.return_value.__enter__.return_value
client.delete.return_value = delete_resp
client.post.return_value = create_resp

id_map = _register_secrets(settings, "myapp", {"DB_URL": "{}"})

assert id_map == {"DB_URL": "abc123hex"}
Loading