Skip to content

Commit 362996c

Browse files
committed
fix(container): make sqlite e2e runner work under rootless podman
Bring the SQLite end-to-end runner up against `podman 5.8.2` / `podman-compose 1.5.0` without regressing the Docker-Compose-v2 path. The five issues fixed here all surface only when the harness is driven by something other than the upstream Docker daemon, which is why they slipped past Phase 8's bring-up against the operator workflow. Containerfile: - Copy `entry_script_lib_sh` into the release runtime base alongside `entry.sh`. The file is sourced unconditionally at start-up; without it the container exits immediately with `can't open /usr/local/lib/torrust/entry_script_lib_sh`. The debug runtime base already pulled it directly from the build context — the release base routed everything via the `runtime_assets` collector and this one path was missed. E2E `e2e-env-up.sh` (public + private SQLite modes): - Export `BUILDAH_FORMAT=docker` so the Containerfile's `HEALTHCHECK` directive survives the build under podman. The default OCI format silently drops it, leaving `torrust-index-1` permanently un-healthy and tripping the wait-for-healthy gate downstream. - Pre-create the `./storage/{index,tracker}/{lib/database,log,etc}` bind-mount sources. Docker auto-creates missing host paths; podman does not, and combined with `:Z` SELinux relabel the failure mode is an opaque `getxattr ... no such file or directory` rather than the friendlier "bind source path does not exist". - Replace `docker compose up --pull always` with a standalone `docker compose pull || true` followed by a plain `up`. `podman-compose` rejects the Compose-v2 `--pull=always` syntax (it accepts a boolean `--pull` paired with `--pull-always`); `compose pull` works under both. The `|| true` keeps the script resilient to transient registry hiccups and to images that only exist locally (e.g. `localhost/torrust_index`). E2E `run-e2e-tests.sh`: - Guard against being run outside the repo root: require a git checkout, refuse if `pwd` is not the toplevel, and identify the repo by `Cargo.toml` package name rather than remote URL so forks and renamed clones still work. The script does destructive things (`rm -rf ./storage`, overwrites `.env`) that are only safe inside the project root. - Prepend `$HOME/.cargo/bin` to `PATH` so rustup-managed toolchains are visible regardless of the invoking shell's setup. `~/.cargo/env` is only sourced in interactive login shells, which CI runners and `bash -c` invocations are not. - Wipe `./storage` before each run, routing through `podman unshare rm -rf` when the active engine is podman. Files written from inside a rootless container are owned by a sub-UID from `/etc/subuid`, which a host-side `rm -rf` cannot remove. The engine is detected via `docker --version | grep -qi podman` so the `podman-docker` shim is handled correctly. - Skip `cargo install imdl` when `imdl` is already on PATH, and pass `--locked` when it is not — the install dominated the script's wall-clock time on every re-run. - Probe whether containers come up named with `-` (Compose v2) or `_` (podman-compose v1.x) and compose the `wait_for_container_to_be_healthy.sh` arguments from the detected separator. The probe runs once per phase and defaults to `-` when nothing matches. `wait_for_container_to_be_healthy.sh`: - Treat a `null` `.State.Health` (image declares no `HEALTHCHECK`) the same as `{}` and fall back to the plain `.State.Status == "running"` check. Without this the loop spins until the retry budget runs out for any image without a healthcheck — common for upstream images such as `docker.io/torrust/tracker`, and for any locally-built image whose layers ended up in OCI format. No changes to the application or to the production compose flow; this is purely the e2e-harness portability fix that unblocks the SQLite suite under podman.
1 parent b79e32d commit 362996c

5 files changed

Lines changed: 162 additions & 10 deletions

File tree

Containerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,13 @@ COPY --from=runtime_assets /etc/profile /etc/profile
258258
COPY --from=runtime_assets /bin/busybox /usr/bin/busybox
259259
COPY --from=runtime_assets /bin/su-exec /usr/bin/su-exec
260260
COPY --from=runtime_assets /usr/local/bin/entry.sh /usr/local/bin/entry.sh
261+
# `entry.sh` sources its pure-function library from this path
262+
# at start-up; without it the container exits immediately with
263+
# "can't open /usr/local/lib/torrust/entry_script_lib_sh".
264+
# The debug runtime base copies the file directly from the
265+
# build context (see further down); the release base goes via
266+
# `runtime_assets` to keep the per-path copy list explicit.
267+
COPY --from=runtime_assets /usr/local/lib/torrust/entry_script_lib_sh /usr/local/lib/torrust/entry_script_lib_sh
261268
COPY --from=preflight_gate /tmp/.adduser-ok /tmp/.preflight-sentinel
262269
# Pin PATH so a future base-image change cannot silently break
263270
# the entry script's bare-name lookups.

contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,26 @@
11
#!/bin/bash
22

3+
# See the public-mode counterpart for the rationale behind
4+
# `BUILDAH_FORMAT=docker` and `--pull=always` (rather than
5+
# `--pull always`).
6+
export BUILDAH_FORMAT=docker
7+
8+
# See the public-mode counterpart for why we pre-create these.
9+
mkdir -p \
10+
./storage/index/lib/database \
11+
./storage/index/log \
12+
./storage/index/etc \
13+
./storage/tracker/lib/database \
14+
./storage/tracker/log \
15+
./storage/tracker/etc
16+
317
TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.private.e2e.container.sqlite3.toml) \
418
docker compose build
519

20+
# See the public-mode counterpart for why we pull separately
21+
# rather than passing `--pull=always` to `docker compose up`.
22+
docker compose pull || true
23+
624
USER_ID=${USER_ID:-1000} \
725
TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.private.e2e.container.sqlite3.toml) \
826
TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \
@@ -13,4 +31,4 @@ USER_ID=${USER_ID:-1000} \
1331
TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \
1432
TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="sqlite3" \
1533
TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \
16-
docker compose up --detach --pull always --remove-orphans
34+
docker compose up --detach --remove-orphans

contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,39 @@
11
#!/bin/bash
22

3+
# `BUILDAH_FORMAT=docker` keeps the Containerfile's `HEALTHCHECK`
4+
# directive when the build runs under podman: the default OCI image
5+
# format silently drops it (`WARN ... HEALTHCHECK is not supported
6+
# for OCI image format and will be ignored`), which would leave
7+
# `torrust-index-1` permanently un-healthy and break the
8+
# `wait_for_container_to_be_healthy.sh` gate downstream.
9+
export BUILDAH_FORMAT=docker
10+
11+
# Pre-create the bind-mount source directories. Docker's daemon will
12+
# auto-create missing host paths, but podman does not — and with the
13+
# `:Z` SELinux relabel suffix in `compose.yaml`, a missing source path
14+
# fails the container start with `getxattr ... no such file or
15+
# directory` rather than the friendlier "bind source path does not
16+
# exist" error. Creating them up front keeps the script portable.
17+
mkdir -p \
18+
./storage/index/lib/database \
19+
./storage/index/log \
20+
./storage/index/etc \
21+
./storage/tracker/lib/database \
22+
./storage/tracker/log \
23+
./storage/tracker/etc
24+
325
TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.public.e2e.container.toml) \
426
docker compose build
527

28+
# Pull externally-sourced service images up front. We deliberately
29+
# avoid `docker compose up --pull=always`: under podman-compose,
30+
# `--pull` is a boolean flag (paired with a separate `--pull-always`),
31+
# so the Compose-v2 `--pull=always` syntax is rejected. A standalone
32+
# `docker compose pull` is accepted by both implementations; the
33+
# `|| true` keeps the script resilient to transient registry hiccups
34+
# and to images that only exist locally (e.g. `localhost/torrust_index`).
35+
docker compose pull || true
36+
637
USER_ID=${USER_ID:-1000} \
738
TORRUST_INDEX_CONFIG_TOML=$(cat ./share/default/config/index.public.e2e.container.toml) \
839
TORRUST_INDEX_DATABASE="e2e_testing_sqlite3" \
@@ -13,4 +44,4 @@ USER_ID=${USER_ID:-1000} \
1344
TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3" \
1445
TORRUST_TRACKER_CONFIG_OVERRIDE_CORE__DATABASE__DRIVER="sqlite3" \
1546
TORRUST_TRACKER_CONFIG_OVERRIDE_HTTP_API__ACCESS_TOKENS__ADMIN="MyAccessToken" \
16-
docker compose up --detach --pull always --remove-orphans
47+
docker compose up --detach --remove-orphans

contrib/dev-tools/container/e2e/sqlite/run-e2e-tests.sh

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,105 @@
11
#!/bin/bash
22

3+
# Guard: refuse to run unless we are at the top of a `torrust-index`
4+
# git checkout. The script does destructive things (wipes `./storage`,
5+
# overwrites `.env`, builds container images tagged `torrust_index`)
6+
# that are only safe inside the project root, and the bind-mount
7+
# paths in `compose.yaml` are all relative to it.
8+
if ! command -v git >/dev/null 2>&1; then
9+
echo "error: 'git' is required to verify the working directory" >&2
10+
exit 1
11+
fi
12+
GIT_TOPLEVEL="$(git rev-parse --show-toplevel 2>/dev/null)" || {
13+
echo "error: not inside a git repository" >&2
14+
exit 1
15+
}
16+
if [ "$GIT_TOPLEVEL" != "$PWD" ]; then
17+
echo "error: must be run from the repository root ($GIT_TOPLEVEL), not $PWD" >&2
18+
exit 1
19+
fi
20+
# Identify the repo by the presence of the root crate's Cargo.toml
21+
# rather than by remote URL — that keeps forks and local clones
22+
# (with arbitrary remote names) working.
23+
if ! grep -qE '^name = "torrust-index"$' Cargo.toml 2>/dev/null; then
24+
echo "error: this does not look like the torrust-index repository (Cargo.toml package name mismatch)" >&2
25+
exit 1
26+
fi
27+
328
CURRENT_USER_NAME=$(whoami)
429
CURRENT_USER_ID=$(id -u)
530
echo "User name: $CURRENT_USER_NAME"
631
echo "User id: $CURRENT_USER_ID"
732

33+
# Make rustup-managed toolchains visible. Distros that ship rust as
34+
# a system package put `cargo` on the default PATH; rustup installs
35+
# (the upstream-recommended path) put it under `~/.cargo/bin`, which
36+
# is only added to PATH by `~/.cargo/env` — and that's only sourced
37+
# in interactive login shells. Prepend it unconditionally so the
38+
# script works regardless of the invoking shell's setup.
39+
if [ -d "$HOME/.cargo/bin" ]; then
40+
PATH="$HOME/.cargo/bin:$PATH"
41+
export PATH
42+
fi
43+
if ! command -v cargo >/dev/null 2>&1; then
44+
echo "error: 'cargo' not found on PATH (looked in \$PATH and \$HOME/.cargo/bin)" >&2
45+
exit 1
46+
fi
47+
848
USER_ID=$CURRENT_USER_ID
949
export USER_ID
1050

1151
export TORRUST_INDEX_DATABASE="e2e_testing_sqlite3"
1252
export TORRUST_TRACKER_DATABASE="e2e_testing_sqlite3"
1353

54+
# Wipe any storage left over from a previous run.
55+
#
56+
# Under rootless podman, files written from inside a container are
57+
# owned by a sub-UID from /etc/subuid (e.g. host-UID 525287 for
58+
# container-UID 1000), which a plain `rm -rf` cannot remove from the
59+
# host shell. `podman unshare` re-enters that user namespace where
60+
# we *are* root and can delete freely. Detect the actual provider
61+
# behind the `docker` CLI rather than trusting the binary name —
62+
# `podman-docker` ships a `docker` shim that wraps podman.
63+
if [ -d ./storage ]; then
64+
if command -v podman >/dev/null 2>&1 && \
65+
{ ! command -v docker >/dev/null 2>&1 || \
66+
docker --version 2>/dev/null | grep -qi podman; }; then
67+
echo "Cleaning ./storage via 'podman unshare' (rootless userns)"
68+
podman unshare rm -rf ./storage || exit 1
69+
else
70+
echo "Cleaning ./storage"
71+
rm -rf ./storage || exit 1
72+
fi
73+
fi
74+
1475
# Install tool to create torrent files.
1576
# It's needed by some tests to generate and parse test torrent files.
16-
cargo install imdl || exit 1
77+
# Skip the (slow) `cargo install` when `imdl` is already on PATH.
78+
if ! command -v imdl >/dev/null 2>&1; then
79+
cargo install --locked imdl || exit 1
80+
fi
1781

1882
# Install app (no docker) that will run the test suite against the E2E testing
1983
# environment (in docker).
2084
cp .env.local .env || exit 1
2185
./contrib/dev-tools/container/e2e/sqlite/install.sh || exit 1
2286

87+
# Compose v2 (`docker compose`) names containers with hyphens
88+
# (`torrust-mysql-1`); `podman-compose` v1.x preserves the legacy
89+
# `_` separator (`torrust_mysql_1`). Probe once and let downstream
90+
# wait-for-healthy calls reuse the result.
91+
detect_container_name_sep() {
92+
local svc=mysql
93+
if docker ps --format '{{.Names}}' | grep -q "^torrust-${svc}-1$"; then
94+
echo '-'
95+
elif docker ps --format '{{.Names}}' | grep -q "^torrust_${svc}_1$"; then
96+
echo '_'
97+
else
98+
# Sensible default for Compose v2.
99+
echo '-'
100+
fi
101+
}
102+
23103
# TEST USING SQLITE
24104
echo "Running E2E tests using SQLite ..."
25105

@@ -29,10 +109,12 @@ echo "Running E2E tests with a public tracker ..."
29109
# Start E2E testing environment
30110
./contrib/dev-tools/container/e2e/sqlite/mode/public/e2e-env-up.sh || exit 1
31111

112+
SEP=$(detect_container_name_sep)
113+
32114
# Wait for conatiners to be healthy
33-
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1
34-
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 || exit 1
35-
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1
115+
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh "torrust${SEP}mysql${SEP}1" 10 3 || exit 1
116+
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh "torrust${SEP}tracker${SEP}1" 10 3 || exit 1
117+
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh "torrust${SEP}index${SEP}1" 10 3 || exit 1
36118

37119
# Just to make sure that everything is up and running
38120
docker ps
@@ -63,10 +145,12 @@ echo "Running E2E tests with a private tracker ..."
63145
# Start E2E testing environment
64146
./contrib/dev-tools/container/e2e/sqlite/mode/private/e2e-env-up.sh || exit 1
65147

148+
SEP=$(detect_container_name_sep)
149+
66150
# Wait for conatiners to be healthy
67-
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-mysql-1 10 3 || exit 1
68-
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-tracker-1 10 3 || exit 1
69-
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh torrust-index-1 10 3 || exit 1
151+
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh "torrust${SEP}mysql${SEP}1" 10 3 || exit 1
152+
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh "torrust${SEP}tracker${SEP}1" 10 3 || exit 1
153+
./contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh "torrust${SEP}index${SEP}1" 10 3 || exit 1
70154

71155
# Just to make sure that everything is up and running
72156
docker ps

contrib/dev-tools/container/functions/wait_for_container_to_be_healthy.sh

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,24 @@ wait_for_container_to_be_healthy() {
88

99
while [ $retry_count -lt "$max_retries" ]; do
1010
container_health="$(docker inspect --format='{{json .State.Health}}' "$container_name")"
11-
if [ "$container_health" != "{}" ]; then
11+
if [ "$container_health" != "{}" ] && [ "$container_health" != "null" ]; then
1212
container_status="$(echo "$container_health" | jq -r '.Status')"
1313
if [ "$container_status" == "healthy" ]; then
1414
echo "Container $container_name is healthy"
1515
return 0
1616
fi
17+
else
18+
# The image declares no HEALTHCHECK (common for upstream
19+
# images such as `docker.io/torrust/tracker`, and for any
20+
# locally-built image when podman emits OCI-format layers
21+
# — OCI silently drops the directive). Fall back to the
22+
# plain runtime status so we don't loop forever waiting
23+
# on a probe that will never report.
24+
container_status="$(docker inspect --format='{{.State.Status}}' "$container_name")"
25+
if [ "$container_status" == "running" ]; then
26+
echo "Container $container_name is running (no healthcheck declared)"
27+
return 0
28+
fi
1729
fi
1830

1931
retry_count=$((retry_count + 1))

0 commit comments

Comments
 (0)