diff --git a/Dockerfile b/Dockerfile index 5b2facf..fc4bd10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -94,6 +94,22 @@ RUN --mount=type=bind,source=config/patch/s3.py,target=/tmp/s3_patch.py \ echo "[patch] PATCH_FILES=false — s3.py patch skipped (upstream file unchanged)"; \ fi +# Fix the invalid urn MapProxy emits since 6.0.0: correct +# it to the OGC-valid form (urn:ogc:def:crs:EPSG::4326 — upstream renders a +# single colon before the code, which violates OGC 07-092r1). This one edits +# the installed template in place instead of replacing it, so an upstream +# rework of that line aborts the build rather than silently reverting other +# template changes. +RUN --mount=type=bind,source=config/patch/wmts_capabilities_compat.py,target=/tmp/wmts_capabilities_compat.py \ + if [ "${PATCH_FILES}" = "true" ]; then \ + python /tmp/wmts_capabilities_compat.py \ + /opt/venv/lib/python3.11/site-packages/mapproxy/service/templates/wmts100capabilities.xml \ + && echo "[patch] wmts100capabilities.xml applied and verified" \ + || { echo "[patch] wmts100capabilities.xml FAILED — build aborted" >&2; exit 1; }; \ + else \ + echo "[patch] PATCH_FILES=false — wmts100capabilities.xml patch skipped (upstream file unchanged)"; \ + fi + # ── Runtime Stage ─────────────────────────────────────────────────────────── FROM python:3.11-slim-bookworm diff --git a/config/patch/wmts_capabilities_compat.py b/config/patch/wmts_capabilities_compat.py new file mode 100644 index 0000000..e3cfed0 --- /dev/null +++ b/config/patch/wmts_capabilities_compat.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Fix the invalid urn MapProxy emits since 6.0.0. + +MapProxy commit b8f8949b ("restful encoding / style isdefault and +urn:ogc:def:crs for SupportedCRS", first released in 6.0.0) gave + in the WMTS 1.0.0 capabilities template a +"urn:ogc:def:crs:" prefix rendered as "urn:ogc:def:crs:EPSG:4326" — an +invalid OGC URN: the version field between authority and code is missing, +so there is a single colon where the standard (OGC 07-092r1) requires two +("urn:ogc:def:crs:EPSG::4326"). This script keeps the prefix but doubles +the first colon of srs_name at render time, producing the valid +"urn:ogc:def:crs:EPSG::4326" form. + +This script edits exactly that one line in the installed template and +leaves every other upstream change in place. It is applied at build time +(see the Dockerfile) rather than shipping a full copy of the template, so a +MapProxy upgrade that reworks these lines aborts the build loudly instead of +silently reverting unrelated template improvements. + +Usage: wmts_capabilities_compat.py +""" + +import sys + +# (description, exact text in the installed template, replacement, expected hits) +EDITS = ( + ( + 'SupportedCRS urn missing version colon', + 'urn:ogc:def:crs:{{tile_matrix_set.srs_name}}', + "urn:ogc:def:crs:" + "{{tile_matrix_set.srs_name.replace(':', '::', 1)}}" + '', + 1, + ), +) + +# Text that must not survive the rewrite, whatever form the template took. +RESIDUE = ('urn:ogc:def:crs:{{tile_matrix_set.srs_name}}',) + + +def main(argv): + if len(argv) != 2: + sys.exit('usage: %s ' % argv[0]) + + path = argv[1] + with open(path, encoding='utf-8') as fh: + template = fh.read() + + for description, old, new, expected in EDITS: + found = template.count(old) + if found != expected: + sys.exit( + '[patch] %s: expected %d occurrence(s) of %r in %s, found %d — ' + 'upstream template changed, review the patch against this ' + 'MapProxy version' % (description, expected, old, path, found) + ) + template = template.replace(old, new) + + for residue in RESIDUE: + if residue in template: + sys.exit( + '[patch] %r still present in %s after rewrite' % (residue, path) + ) + + with open(path, 'w', encoding='utf-8') as fh: + fh.write(template) + + print('[patch] wmts100capabilities.xml: fixed SupportedCRS urn to the ' + 'valid urn:ogc:def:crs:EPSG:: form') + + +if __name__ == '__main__': + main(sys.argv) diff --git a/readme.md b/readme.md index a231a32..7a581b3 100644 --- a/readme.md +++ b/readme.md @@ -22,6 +22,7 @@ MapProxy running under **uWSGI**, instrumented with **OpenTelemetry** (traces, m - [uWSGI Tuning](#uwsgi-tuning) - [Health Check](#health-check) - [Building the Image Locally](#building-the-image-locally) + - [MapProxy Patches](#mapproxy-patches) - [Exposed Ports](#exposed-ports) - [Volumes](#volumes) - [OpenShift / Arbitrary UID](#openshift--arbitrary-uid) @@ -412,6 +413,36 @@ The image uses a **multi-stage build**: all compile-time dependencies (GCC, GDAL `MAPPROXY_VERSION` is the single source of truth — it is used in `pip install`, the OCI image labels, and the `SERVICE_VERSION` env var read by `app.py` at runtime. +### MapProxy Patches + +The builder stage applies three patches to the installed MapProxy under +`config/patch/`, each bind-mounted so the patch source never lands in an image +layer. Every patch verifies itself and **aborts the build** on failure, so a +MapProxy upgrade that invalidates one is caught at build time rather than in +production. Build with `--build-arg PATCH_FILES=false` to skip all three and get +stock upstream behaviour (useful for upstream-compat testing). + +| Patch | Target | Why | +| -------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `redis.py` | `mapproxy/cache/redis.py` | Redis resilience — short timeouts, retries, TLS (see the Redis env vars) | +| `s3.py` | `mapproxy/cache/s3.py` | S3/MinIO fixes, including signing `use_http_get` reads so private buckets work | +| `wmts_capabilities_compat.py` | `mapproxy/service/templates/wmts100capabilities.xml` | Fixes the invalid `` urn MapProxy emits since 6.0.0 | + +The WMTS one corrects exactly one element — changed by upstream MapProxy +commit `b8f8949b`: + +| Element | MapProxy 6.x (stock) | This image | +| ---------------------- | ------------------------------------------------- | -------------------------------- | +| `` | `urn:ogc:def:crs:EPSG:4326` | `urn:ogc:def:crs:EPSG::4326` | + +The stock `SupportedCRS` urn is invalid per OGC 07-092r1: the version field +between authority and code is empty, so the urn must carry a double colon +(`EPSG::4326`), not the single colon MapProxy renders. + +Unlike the other two, it rewrites the one line in place instead of shipping a +full copy of the template, so an upstream rework of that line fails the build +loudly rather than silently reverting unrelated template changes. + --- ## Exposed Ports