From 1666b9e36a87a6c7aa7e8582859a2a1268f8a5d6 Mon Sep 17 00:00:00 2001 From: Red Date: Mon, 7 Sep 2026 17:23:33 +0800 Subject: [PATCH 1/4] build(docker): allow third-party addon repos to be pinned by commit sha MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit download_module could only fetch a branch head: it hardcoded the /archive/refs/heads/.tar.gz URL, which 404s for a commit sha. The addon repositories were therefore an unpinned dependency, and two builds of the same OpenSPP commit could pick up different OCA code. Give the function a ref that may be a branch, a tag or a commit sha, and expose one ARG per downloaded repo so a deployment can pin without editing the Dockerfile: docker build --build-arg OCA_SERVER_UX_REF= ... A sha carries no ref namespace, so it is served from /archive/.tar.gz instead; 7 or more hex digits (short or full sha) selects that form and anything else keeps today's branch URL. Every default is 19.0, so a build that passes no new argument resolves exactly the same six URLs and tarball cache paths as before. No repository's default ref is changed here — which commits to pin is a deployment decision. The ARGs sit immediately above the RUN that consumes them so that changing a ref only invalidates the download layer, and because a global ARG declared before FROM is not in scope inside the stage. Signed-off-by: Red --- docker/Dockerfile | 39 +++++++++++++++++++++++++++++---------- docker/README.md | 27 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 4c1514b27..f9aeeeb79 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -98,28 +98,47 @@ RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \ mv /opt/odoo-src /opt/odoo/odoo; \ uv pip install --no-deps -e /opt/odoo/odoo +# Third-party addon refs. Each takes a branch, a tag or a commit sha, so a +# deployment can pin an exact commit instead of tracking a moving branch: +# docker build --build-arg OCA_SERVER_UX_REF= ... +# Declared here rather than at the top of the stage so that changing a ref +# only invalidates the download layer below. +ARG OCA_SERVER_UX_REF=19.0 +ARG OCA_SERVER_TOOLS_REF=19.0 +ARG ODOO_JOB_WORKER_REF=19.0 +ARG OCA_SERVER_BACKEND_REF=19.0 +ARG OCA_REST_FRAMEWORK_REF=19.0 +ARG MUK_IT_REF=19.0 + # Download OCA dependencies in builder (cache tarballs to avoid re-downloading) RUN --mount=type=cache,target=/tmp/downloads,sharing=locked \ set -eux; \ download_module() { \ local repo=$1; \ local dest=$2; \ - local branch=${3:-19.0}; \ - local tarball="/tmp/downloads/${dest}-${branch}.tar.gz"; \ + local ref=${3:-19.0}; \ + local tarball="/tmp/downloads/${dest}-${ref}.tar.gz"; \ + local url="/${repo}/archive/refs/heads/${ref}.tar.gz"; \ + # A commit sha sits under no ref namespace: GitHub serves it from + # /archive/.tar.gz and 404s on /archive/refs/heads/.tar.gz. + # 7+ hex digits is a sha (short or full); anything else is a branch. + case "$ref" in \ + *[!0-9a-fA-F]*) ;; \ + ???????*) url="/${repo}/archive/${ref}.tar.gz";; \ + esac; \ if [ ! -f "$tarball" ] || ! gzip -t "$tarball" 2>/dev/null; then \ rm -f "$tarball"; \ - curl -sSL -o "$tarball" \ - "/${repo}/archive/refs/heads/${branch}.tar.gz"; \ + curl -sSL -o "$tarball" "$url"; \ fi; \ mkdir -p /opt/extra-addons/${dest}; \ tar -xzf "$tarball" -C /opt/extra-addons/${dest} --strip-components=1; \ }; \ - download_module "OCA/server-ux" "server-ux"; \ - download_module "OCA/server-tools" "server-tools"; \ - download_module "OpenSPP/odoo-job-worker" "odoo-job-worker"; \ - download_module "OCA/server-backend" "server-backend"; \ - download_module "OCA/rest-framework" "rest-framework"; \ - download_module "muk-it/odoo-modules" "muk-it" + download_module "OCA/server-ux" "server-ux" "${OCA_SERVER_UX_REF}"; \ + download_module "OCA/server-tools" "server-tools" "${OCA_SERVER_TOOLS_REF}"; \ + download_module "OpenSPP/odoo-job-worker" "odoo-job-worker" "${ODOO_JOB_WORKER_REF}"; \ + download_module "OCA/server-backend" "server-backend" "${OCA_SERVER_BACKEND_REF}"; \ + download_module "OCA/rest-framework" "rest-framework" "${OCA_REST_FRAMEWORK_REF}"; \ + download_module "muk-it/odoo-modules" "muk-it" "${MUK_IT_REF}" # Precompile Python files (ignore errors from Python 2 syntax in some packages) RUN python -m compileall -q /opt/venv/lib/ || true diff --git a/docker/README.md b/docker/README.md index 5f6cef072..bbe45dedc 100644 --- a/docker/README.md +++ b/docker/README.md @@ -349,6 +349,33 @@ docker build -f docker/Dockerfile -t openspp . docker build --build-arg INSTALL_DEV=1 -f docker/Dockerfile -t openspp:dev . ``` +### Pinning third-party addons + +The OCA and third-party addon repositories are downloaded during the build. Each one has +a `*_REF` build argument that accepts a branch, a tag or a commit sha and defaults to +`19.0`: + +| Build arg | Repository | +| ------------------------ | ------------------------- | +| `OCA_SERVER_UX_REF` | `OCA/server-ux` | +| `OCA_SERVER_TOOLS_REF` | `OCA/server-tools` | +| `ODOO_JOB_WORKER_REF` | `OpenSPP/odoo-job-worker` | +| `OCA_SERVER_BACKEND_REF` | `OCA/server-backend` | +| `OCA_REST_FRAMEWORK_REF` | `OCA/rest-framework` | +| `MUK_IT_REF` | `muk-it/odoo-modules` | + +```bash +# Reproducible build: pin the addons to exact commits +docker build \ + --build-arg OCA_SERVER_UX_REF=8e5120600987969156c2a59c1ad86bec37318966 \ + --build-arg OCA_SERVER_TOOLS_REF=19.0 \ + -f docker/Dockerfile -t openspp . +``` + +Left unset, an argument follows the head of the `19.0` branch, so two builds of the same +OpenSPP commit can end up with different addon code. Pin the refs for builds that have +to be reproducible. + ## Health Check The container exposes a health endpoint at `/web/health` on port 8069. From 19659fc7621640a25415e7abef0c5ec546014f15 Mon Sep 17 00:00:00 2001 From: Red Date: Wed, 9 Sep 2026 12:08:34 +0800 Subject: [PATCH 2/4] docs(docker): a *_REF is a branch or a sha, not a tag Review finding 1 on #507. The Dockerfile comment and docker/README.md both advertised "a branch, a tag or a commit sha", but a tag name is non-hex and so takes the refs/heads route, which 404s: 404 /archive/refs/heads/2026.08.tar.gz 200 /archive/refs/tags/2026.08.tar.gz (OpenSPP/odoo-job-worker, checked live) A deployer following the README as written got a curl 404 body and a later "not in gzip format" failure from tar. Narrow the prose to what the code does and point at the tag's commit sha, which is the reproducible pin anyway. No behaviour change. Signed-off-by: Red --- docker/Dockerfile | 6 ++++-- docker/README.md | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index f9aeeeb79..57e8fd2b8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -98,8 +98,10 @@ RUN --mount=type=cache,target=/root/.cache/uv,sharing=locked \ mv /opt/odoo-src /opt/odoo/odoo; \ uv pip install --no-deps -e /opt/odoo/odoo -# Third-party addon refs. Each takes a branch, a tag or a commit sha, so a -# deployment can pin an exact commit instead of tracking a moving branch: +# Third-party addon refs. Each takes a branch or a commit sha, so a deployment +# can pin an exact commit instead of tracking a moving branch. A tag name is not +# accepted (it resolves under refs/tags, which this does not build) - pass the +# commit sha the tag points at: # docker build --build-arg OCA_SERVER_UX_REF= ... # Declared here rather than at the top of the stage so that changing a ref # only invalidates the download layer below. diff --git a/docker/README.md b/docker/README.md index bbe45dedc..7fbdee0c6 100644 --- a/docker/README.md +++ b/docker/README.md @@ -352,8 +352,8 @@ docker build --build-arg INSTALL_DEV=1 -f docker/Dockerfile -t openspp:dev . ### Pinning third-party addons The OCA and third-party addon repositories are downloaded during the build. Each one has -a `*_REF` build argument that accepts a branch, a tag or a commit sha and defaults to -`19.0`: +a `*_REF` build argument that accepts a branch or a commit sha and defaults to `19.0`. +A tag name is not accepted - resolve it to the commit sha it points at and pin that: | Build arg | Repository | | ------------------------ | ------------------------- | From 13c03094f0229f5976a2ed6d953750e5217ed03c Mon Sep 17 00:00:00 2001 From: Red Date: Wed, 9 Sep 2026 12:11:07 +0800 Subject: [PATCH 3/4] build(docker): fail the addon download at curl, not later at tar Review suggestion 2 on #507. Without -f, a 404 is written to the cache as a 14-byte "Not Found" body and the build dies further down in tar with "not in gzip format", which hides the cause. Replayed under /bin/dash in python:3.13-slim-bookworm with a deliberately bad ref: before: gzip: stdin: not in gzip format / tar: Child returned status 1 RUN exit 2, and a 14-byte tarball left in the /tmp/downloads cache after: curl: (22) The requested URL returned error: 404 RUN exit 22, and nothing cached Pre-existing, but sha pinning makes a mistyped ref likelier, so it is worth having here. Defaults are untouched: the six call sites replayed old-vs-new under dash record byte-identical (tarball, url) pairs, so an existing build cache stays valid. Signed-off-by: Red --- docker/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 57e8fd2b8..6d15f10c8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -130,7 +130,9 @@ RUN --mount=type=cache,target=/tmp/downloads,sharing=locked \ esac; \ if [ ! -f "$tarball" ] || ! gzip -t "$tarball" 2>/dev/null; then \ rm -f "$tarball"; \ - curl -sSL -o "$tarball" "$url"; \ + # -f so a bad ref fails here with the 404, not later in tar with + # "not in gzip format" over a 14-byte error body. + curl -fsSL -o "$tarball" "$url"; \ fi; \ mkdir -p /opt/extra-addons/${dest}; \ tar -xzf "$tarball" -C /opt/extra-addons/${dest} --strip-components=1; \ From b89680618ef1943314f20260227b9df1ce1fe78d Mon Sep 17 00:00:00 2001 From: Red Date: Wed, 9 Sep 2026 12:11:32 +0800 Subject: [PATCH 4/4] docs(docker): the pinning example must actually pin Review suggestion 3 on #507. Under "pin the addons to exact commits" the second line read OCA_SERVER_TOOLS_REF=19.0, which is the default branch and so demonstrates the opposite of the sentence above it. Use a real OCA/server-tools commit sha (028b450b, 19.0 head at the time of writing; /archive/.tar.gz checked 200). Signed-off-by: Red --- docker/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/README.md b/docker/README.md index 7fbdee0c6..ccf3d3003 100644 --- a/docker/README.md +++ b/docker/README.md @@ -352,8 +352,8 @@ docker build --build-arg INSTALL_DEV=1 -f docker/Dockerfile -t openspp:dev . ### Pinning third-party addons The OCA and third-party addon repositories are downloaded during the build. Each one has -a `*_REF` build argument that accepts a branch or a commit sha and defaults to `19.0`. -A tag name is not accepted - resolve it to the commit sha it points at and pin that: +a `*_REF` build argument that accepts a branch or a commit sha and defaults to `19.0`. A +tag name is not accepted - resolve it to the commit sha it points at and pin that: | Build arg | Repository | | ------------------------ | ------------------------- | @@ -368,7 +368,7 @@ A tag name is not accepted - resolve it to the commit sha it points at and pin t # Reproducible build: pin the addons to exact commits docker build \ --build-arg OCA_SERVER_UX_REF=8e5120600987969156c2a59c1ad86bec37318966 \ - --build-arg OCA_SERVER_TOOLS_REF=19.0 \ + --build-arg OCA_SERVER_TOOLS_REF=028b450b06ebd70424534cbd89f37ed134f7200d \ -f docker/Dockerfile -t openspp . ```