Thread cluster context into the OpenAPI proxy token lookup¶
Motivation¶
Issue #26 tracks the outbound elb-openapi resolver reading the base URL and API token from process-global, single-key caches. In a single Container App revision that manages more than one AKS cluster (each with its own custom domain + token), those globals hold the most-recently-touched cluster's values, so an outbound call made while the user is on cluster A can be sent with cluster B's token — confusing 401s or cross-cluster exposure once two clusters each have a custom domain.
The storage side is already per-cluster keyed (save_openapi_api_token /
get_openapi_api_token write/read openapi:runtime:api-token:cluster:<sha>),
and get_openapi_api_token / get_public_tls_base_url already accept cluster
context. The gap is call sites that have cluster context but do not thread
it.
User-facing change¶
- The API Reference "Try it" reverse proxy (
GET /api/aks/openapi/proxy,aks_openapi_proxy) resolved its runtime-token fallback viaget_openapi_api_token()with no cluster context, even though it already passessubscription_id/resource_group/cluster_nametoget_public_tls_base_urlandget_openapi_api_token_statusin the same handler. It now passes that same context toget_openapi_api_token(...), so a multi-cluster dashboard injects this cluster's admin token rather than the globally most-recently-written one. - Single-cluster behaviour is unchanged: the per-cluster key falls back to the legacy global key on a miss, and a context-less env token still wins first.
API / IaC diff summary¶
- api/routes/aks/openapi_proxy.py
aks_openapi_proxy—get_openapi_api_token()→get_openapi_api_token( subscription_id=sub, resource_group=resource_group, cluster_name=cluster_name). - No IaC change.
Scope notes (remaining #26 surface — deferred)¶
This is one concrete, low-risk slice where the cluster context is fully in scope. The rest of #26 stays open and is not addressed here because the context is not readily available at those call sites without a per-cluster resolver design:
api/services/external_blast.py_base_url()/_headers()remain the context-less global fallback. Callers that have cluster context already resolve per-cluster and passbase_url/api_tokenexplicitly (api/services/blast/external_jobs.py,api/tasks/servicebus/tasks.py,api/tasks/blast/reconcile_task.py). The genuinely context-less callers (the direct facade routes, theget_jobfallbacks inapi/routes/blast/jobs.py/results.py) only have ajob_id, not a cluster, so making them cluster-correct needs a job→cluster resolver — the larger #26 design item.- httpx connection reuse for the OpenAPI plane (issue #30 candidate fix #4) is also folded into this #26 resolver work, since a single shared client cannot span per-cluster base URLs.
Validation evidence¶
uv run pytest -q api/tests/test_openapi_proxy_route.py— 33 passed. New test:test_openapi_proxy_threads_cluster_context_into_token_lookupasserts the fallback receives{subscription_id, resource_group, cluster_name}. Updatedtest_openapi_proxy_uses_runtime_token_when_env_token_missingto a**kwargsstub.uv run pytest -q api/tests/test_openapi_proxy_route.py api/tests/test_route_contracts.py api/tests/test_openapi_rate_limit.py api/tests/test_openapi_tls_hook.py— 49 passed.uv run ruff check— clean.