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-transfer config in system-integration and redeployed — and the UI gave no hint of that coupling.

  • Silent fallback footgun. A location whose name had no S3_ENDPOINTS entry silently used the global default endpoint.

  • All-or-nothing method. Because S3_METHOD is 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

endpoint_url

S3 endpoint (re-adds what a3e7c1f28d01 removed)

access_method

s3 (boto3) or coscine; NULL ⇒ global S3_METHOD

path_style

boto3 addressing: True path-style (RDS, MinIO), False virtual-host (NRW Datastorage), NULL ⇒ boto3 default / S3_PATH_STYLE

coscine_project

Coscine project id (identifier, not a secret)

coscine_resource

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 fallbackdata-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-integration config edit or data-transfer redeploy.

  • 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_url per location means we give up per-worker-within-an-environment endpoints (e.g. an in-cluster minio:9000 shortcut alongside a public URL for the same bucket). Per-environment endpoints still work because each environment has its own database (dev’s row can hold minio:9000 while 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.