Platform overview¶
This page orients developers: what the platform is, how the repository is organised, what gets built, and how releases work.
The platform in one loop¶
- An administrator defines a site in the Hub UI or through the Hub API.
- The Hub writes the desired state as a Site custom resource
(
apiVersion: operators.ecmwf.int/v2, Custom Resource Definition (CRD)sites.operators.ecmwf.int) in Kubernetes. - The operator reconciles each Site resource into running Site Pods plus the Service, persistent volume claim (PVC), Secret, ConfigMaps, and route — all named after the Site id.
- Users and automation reach the site through the site URL and the Site API.
The Site resource is the only source of truth. 03-domain-model describes it; 09-lifecycle-and-reconciliation describes the reconciliation; 02-architecture shows how the pieces connect.
Repository layout¶
| Path | Contents |
|---|---|
core/ |
sites-core: configuration schema and loader, Site model, Kubernetes exporters, integrations, notifications, jobs |
hub/ |
sites-hub: the Hub web application, Hub API, initializer |
operator/ |
sites-operator: the kopf-based controller |
site/ |
sites-site plus the site applications: Go API, Lua/OpenResty proxy, admin, oauth2, s3, webdav, mirror, initializer, custom application integration |
helm/ecmwf-sites/ |
Helm chart: platform workloads, CRD, role-based access control (RBAC), routing, CronJobs |
docs/ |
These design documents (docs/design/), the end-user guides (docs/end-user/), and the system-administration guides (docs/sys-admin/) |
utilities/ |
Deployment scripts, chart-maintenance tools, release tools, local-dev assets, repository tests |
.github/ |
Workflows and composite actions (see 15-ci-cd) |
Python workspace¶
The four Python packages share one uv workspace (core, hub,
operator, site, listed in the root pyproject.toml). From the
repository root:
make init-workspace runs the same commands. make test refuses to run
unless lua is on PATH (the proxy Lua modules are unit-tested), then runs
unittest discover over each package's Python tests and go test for the
Go API; see 20-testing.
Built images¶
make publish-images IMAGE_TAG=<tag> builds and pushes these images to
$(SITES_PRIVATE_REGISTRY)/$(SITES_PRIVATE_REGISTRY_PROJECT) (defaults
eccr.ecmwf.int and sites-oss):
| Image | Purpose |
|---|---|
core |
Runtime image for the four platform CronJobs (backup, quota, retention, analytics preloader) |
hub |
Hub application server and its init step |
operator |
Site controller |
site-api |
Go file/operation API |
site-proxy |
OpenResty proxy and UI listing |
site-admin |
File Browser administration UI |
site-oauth2 |
OAuth2 Proxy browser login |
site-s3 |
Optional S3-compatible file access |
site-webdav |
Optional WebDAV file access |
site-mirror |
Content mirror container (migration.enabled) |
site-initializer |
Per-site startup preparation |
There is no site-custom image: custom applications supply their own. The
platform Dockerfiles build from a pinned uv + Python 3.12 slim base image,
while the local workspace standardises on 3.13.
Chart defaults are separate: images.hub|core|operator.repository point to
eccr.ecmwf.int/sites/..., while this repository publishes to
$(SITES_PRIVATE_REGISTRY)/$(SITES_PRIVATE_REGISTRY_PROJECT). Set the chart
repository values when you deploy images published from this repository.
Monorepo lockstep versioning¶
One release version covers:
- The Python packages
sites-core,sites-hub,sites-operator,sites-site(each package's__init__.__version__). - The Helm chart
versionandappVersion, and the chart's defaultimages.tag. - The
site.versionsetting used for site application images.
The root VERSION file is the single source. The chart injects
images.tag into the platform configuration as site.version, so site
application images follow the chart release. images.redis.tag is the only
independent image tag.
make check-lockstep-version
(utilities/release/check-lockstep-version.sh) compares the four package
versions, the chart version and appVersion, and images.tag against
VERSION, reports every mismatch, and exits non-zero when any differ. CI
runs it in the release
workflow. Helpers:
./utilities/release/set-version.sh 2.6.0
./utilities/release/check-lockstep-version.sh
# or: make set-version NEW_VERSION=2.6.0
Release flow¶
- Set the version:
make set-version NEW_VERSION=<version>. - Add a
## Version <version>section toCHANGELOG.md. The GitHub release uses this text (utilities/release/changelog-notes.sh). - Merge into
mainand wait for the CI workflow to pass. - Start the Release workflow from
main.
The Release workflow checks the CI gate result for the target commit, re-runs
the repository tests, checks the lockstep versions, and refuses an existing
tag. The tag-and-release job then creates and pushes the tag with a
deploy key that bypasses the organisation tag-protection ruleset, opens the
GitHub release, and calls the Publish workflow. Publish verifies that the tag
matches VERSION, builds and pushes the images and the chart to
oci://eccr.ecmwf.int/sites-oss/ecmwf-sites (the pushed chart copy carries
images.tag set to the release tag), and a separate job installs sitesctl
from the Sites CLI repository (get.ecmwf.int) to build and upload this
documentation. make release is the same path locally: it checks lockstep
and calls make publish with the version read from VERSION. Override the
registry with SITES_PRIVATE_REGISTRY and SITES_PRIVATE_REGISTRY_PROJECT.
The full trigger, permission, and gate model is in
15-ci-cd.
Endpoint anatomy¶
The Hub owns the platform paths under the public host:
/hub/...— Hub UI pages./hub/api/v1/...and/hub/api/v2/...— Hub API./hub/admin/...— Hub administration pages./hub/error/page/<code>/— shared error pages./favicon.svg,/favicon.ico— the configured platform icon (/favicon.icoredirects to the versioned SVG; see 16).- Root-level redirects: the
redirectsblueprint (prefix/) serves legacy paths such as/error/page/<code>/and redirects them to their/hubequivalents.
Each site owns the paths under its own prefix /<space>/<site>/ (the owner
replaces the space when the site has none):
/<space>/<site>/...— site content and listing, served by the site proxy./<space>/<site>/s/api/v2/...— authenticated Site API./<space>/<site>/s/api/v2/pub/...— anonymous read API on public sites./<space>/<site>/s/admin/,s/dav/,s/s3/— optional site applications./<space>/<site>/s/healthz— NGINX stub status; allowed to the Site's own loopback probe and totrusted_ipsonly./s/metrics— Prometheus text from the Lua metrics module. Note this location is not under the site prefix. ServiceMonitors scrape it through the Site Service port (4180); OAuth2 Proxy skips authentication for it only while the metrics integration is enabled, and the proxy then requires a trusted client IP. See 21-observability.
Paths are identical in both routing modes; see 14-routing. A user-facing guide to these paths, with examples, is in end-user: Understanding Sites URLs.
Behaviour requirements¶
| ID | Requirement | Implemented in | Verified by |
|---|---|---|---|
| OVR-01 | One version string covers the Python packages, the chart, and the image tags. | utilities/release/set-version.sh |
make check-lockstep-version (utilities/release/check-lockstep-version.sh) |
| OVR-02 | The root VERSION file is the only place that defines the release version. |
VERSION, utilities/release/changelog-notes.sh |
make check-lockstep-version (utilities/release/check-lockstep-version.sh) |
| OVR-03 | A release publishes the images and the chart under the version read from VERSION, and fails when the versions are not in lockstep. |
Makefile (release, publish), .github/workflows/release.yml, .github/workflows/publish.yml |
— |
| OVR-04 | Site application images follow the chart release through site.version; a user-supplied site.version in chart values cannot override it. |
helm/ecmwf-sites/templates/configmap-sites-config.yaml, core/.../model/site_app_config.py |
core/tests/test_helm_configuration.py (test_chart_injects_site_version_from_images_tag, test_user_supplied_site_version_in_sites_config_is_overridden) |
| OVR-05 | s/healthz and /s/metrics allow loopback and trusted IPs only; the metrics route exists and is authenticated-exempt only while the metrics integration is active. |
site/.../applications/proxy/nginx/health.conf, metrics.conf, oauth2-proxy/oauth2.cfg, helm/.../servicemonitor.yaml |
site/tests/test_proxy_app_config.py (test_health_location_always_allows_loopback, test_health_location_keeps_configured_trusted_ips_after_loopback, test_metrics_wiring_is_rendered_when_metrics_is_enabled, test_disabling_metrics_removes_every_trace_from_the_proxy_configuration) |