ADR 0001 — S3/LTA connection config lives on the DataLocation (self-serve)#
Status: Accepted
Date: 2026-06-19
Affects:
ops-db(model + migration),ops-db-api,ops-db-ui,data-transfer
Context#
An S3 long-term-archive (LTA) location needs three things to be usable: a bucket/region (what it is), an endpoint URL (where to reach it), an access method (plain S3 via boto3, or Coscine), plus credentials.
Historically the S3DataLocation row stored only bucket_name and region.
The endpoint URL was removed from the DB (migration a3e7c1f28d01) and resolved
at runtime from data-transfer dynaconf settings (S3_ENDPOINTS, keyed by
location name, with an S3_ENDPOINT_URL fallback). The access method was a
single deployment-wide switch (S3_METHOD = boto3 | coscine) with global
COSCINE_PROJECT / COSCINE_RESOURCE.
Consequences of that split:
Commissioning an S3 store was not self-serve. Adding a location in the UI did nothing useful until someone also edited
data-transferconfig insystem-integrationand redeployed — and the UI gave no hint of that coupling.Silent fallback footgun. A location whose
namehad noS3_ENDPOINTSentry silently used the global default endpoint.All-or-nothing method. Because
S3_METHODis global, every S3 location had to use the same method; you could not have one LTA on Coscine and another on plain S3 at the same time. Coscine mode also ignored the per-location bucket, collapsing every location onto one global Coscine resource.
The original argument for keeping the endpoint out of the DB was that it can be
per-worker (different network vantage points reach the same bucket via
different URLs, e.g. an in-cluster minio:9000 shortcut vs a public URL).
Decision#
Move the non-secret connection config onto S3DataLocation and make the
admin UI the source of truth, under one operating rule that neutralises the
per-worker objection:
Each S3 location has a single endpoint URL that is reachable from every worker that routes to it. Routing is single-site → its LTA, and we ensure the chosen endpoint is reachable from all those workers (i.e. we forgo in-cluster shortcut URLs in favour of one reachable URL).
New columns on s3_data_location (migration c7e2a9b41f08), all nullable:
Column |
Meaning |
|---|---|
|
S3 endpoint (re-adds what |
|
|
|
boto3 addressing: |
|
Coscine project id (identifier, not a secret) |
|
Coscine resource id (not a secret) |
Secrets stay in the environment, keyed per-location as today:
{SITE}_{LOCATION}_S3_ACCESS_KEY_ID / _SECRET_ACCESS_KEY, and now
{SITE}_{LOCATION}_COSCINE_API_TOKEN, each with a global fallback.
Resolution is additive with fallback — data-transfer prefers the DB value
and falls back to its existing dynaconf settings when the column is NULL:
endpoint = location.endpoint_url or S3_ENDPOINTS.get(name) or S3_ENDPOINT_URL
method = location.access_method or S3_METHOD # 's3'/'boto3' ≡ boto3 path
project = location.coscine_project or COSCINE_PROJECT
resource = location.coscine_resource or COSCINE_RESOURCE
Consequences#
Self-serve commissioning. An admin creates an S3/LTA location in the UI — bucket, region, endpoint, method (S3 or Coscine), and Coscine project/resource — and only drops the credentials/token into the deployment env once. No
system-integrationconfig edit ordata-transferredeploy.Mixed backends. Method is per-location, so US can be plain S3 while Cologne is Coscine, simultaneously.
Non-breaking. Existing deployments keep working via the dynaconf fallback; locations migrate to DB-managed one at a time.
Trade-off accepted. A single
endpoint_urlper location means we give up per-worker-within-an-environment endpoints (e.g. an in-clusterminio:9000shortcut alongside a public URL for the same bucket). Per-environment endpoints still work because each environment has its own database (dev’s row can holdminio:9000while prod holds the public URL). This is acceptable under the single-reachable-endpoint operating rule above.Secrets are never in the DB, so the DB is not a full connection descriptor by itself — credentials and the Coscine token remain deployment/env concerns.