diff --git a/CHANGELOG.md b/CHANGELOG.md index ac122ad9bc8..72cf706b260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,62 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). to [Semantic Versioning](https://semver.org). When adding a new entry, please use the entries below as a guide. +## [Unreleased] + +- **Podman / Fedora 44 / RHEL 9+ support for `make test`** + + Docker CE is no longer installable or functional on Fedora 38+ and RHEL 9+ from the official + Docker repository. Podman (with `podman-docker` and `podman-compose`) is the supported + replacement on those platforms. This release makes the entire test infrastructure + runtime-agnostic so that `make test` works with either Docker or Podman transparently. + + Changes by area: + + - **`testutil/runtime.go`** (new): central runtime detection; auto-detects whether Docker or + Podman is in use (including `podman-docker` shims), sets `DOCKER_HOST` to the Podman socket + so the Docker SDK can communicate with it, and exposes `ContainerRuntime()` and + `ContainerComposeCmdPrefix()` helpers used everywhere else + - **`t/t.go` and `testutil/bulk.go`**: all hardcoded `docker compose --compatibility` + invocations replaced with `ContainerComposeCmdPrefix()`; all `docker logs`, `docker cp`, and + `docker exec` calls replaced with `ContainerRuntime()` + - **`contrib/Dockerfile`**: pre-creates all `/data/zero*`, `/data/alpha*`, `/data/dg1`, and + `/data/dg0.1` directories; Podman's `crun` OCI runtime does not auto-create a container's + `working_dir` at startup (unlike Docker's `runc`), which left containers stuck in "Created" + state + - **`t/Makefile`**: new `install-deps-docker-linux-dnf` target installs + `podman podman-docker podman-compose` and enables the Podman socket instead of attempting the + broken Docker CE install path on Fedora/RHEL + - **`t/scripts/check-deps-docker.sh`**: rewritten to detect and validate Podman on + `dnf`-based systems instead of requiring Docker CE + - **`t/scripts/check-docker-available-memory.sh`**: reads `/proc/meminfo` directly on Linux + (works for both Docker and Podman) rather than calling `docker info` + - **`Makefile`** (root): new `$(DOCKER)` variable falls back to `podman` when `docker` is + absent, used for image build targets + +- **Fixed** + - fix(build): detect jemalloc under `/usr/lib64` so a dnf-installed `jemalloc-devel` is found on + Fedora/RHEL multilib systems + - fix(build): compile the bundled jemalloc 5.3.1 with `--disable-cxx` to avoid the + `std::__throw_bad_alloc` build failure on GCC 13+ (e.g. Fedora 44 / GCC 15); dgraph only links + jemalloc's C API, so the C++ layer is not needed + - fix(build): chain the jemalloc build recipe with `&&` (was `;`) so a failed compile no longer + falls through to `make install`, and clear any stale `/tmp/jemalloc-temp` before rebuilding + - fix(test): add the Docker CE repo via a downloaded `.repo` file instead of + `dnf config-manager --add-repo`, which was removed in DNF5 (Fedora 41+) and broke + `make setup` on Fedora + +- **Added** + - test: add `contrib/smoke-test.sh` to bring up a local zero+alpha and verify + schema/mutation/query against a freshly built binary + +- **Changed** + - build: `linux-dependency` now auto-detects the package manager (apt/dnf/pacman) and installs the + C/C++ toolchain + protobuf-compiler, so build prerequisites work on Fedora/RHEL and Arch in + addition to Debian/Ubuntu + +- **Docs** + - docs(contributing): document building Dgraph on Fedora/RHEL + ## [v25.3.4] - 2026-05-11 [v25.3.4]: https://github.com/dgraph-io/dgraph/compare/v25.3.3...v25.3.4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e38d3c739f7..fddf0c9cfd2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,6 +126,28 @@ Licensed variously under the Apache Public License 2.0 and Dgraph Community Lice See the [README](t/README.md) in the [_t_](t) folder for instructions on building Dgraph on non-Linux machines. +#### Building Dgraph on Fedora / RHEL + +On Fedora, RHEL, and other `dnf`-based distributions, install the build prerequisites with: + +```bash +make linux-dependency # auto-detects apt / dnf / pacman; installs gcc, make, protobuf-compiler, etc. +make install +``` + +Dgraph statically links jemalloc. Fedora's `jemalloc-devel` package ships only the shared library +(not the static `libjemalloc.a` that Dgraph links against), so `make` compiles jemalloc 5.3.1 from +source the first time and installs it to `/usr/local/lib` (this step needs `sudo`). The build +configures jemalloc with `--disable-cxx`, which avoids a `std::__throw_bad_alloc` compile error on +GCC 13+ (e.g. Fedora 44 / GCC 15); Dgraph uses only jemalloc's C API, so the C++ layer is not +needed. Once the static library exists, subsequent builds detect it and skip the rebuild. + +After building, you can verify the binary with a quick single-node smoke test: + +```bash +contrib/smoke-test.sh # brings up a local zero+alpha, runs schema/mutation/query, tears down +``` + ### Build Docker Image ```sh diff --git a/Makefile b/Makefile index 0905537cf08..87fe88e3ff5 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,9 @@ export GOPATH ?= $(shell go env GOPATH) GOHOSTOS := $(shell go env GOHOSTOS) GOHOSTARCH := $(shell go env GOHOSTARCH) +# Container runtime: prefer docker, fall back to podman (Fedora/RHEL default). +DOCKER ?= $(shell command -v docker 2>/dev/null || command -v podman 2>/dev/null || echo docker) + # Guard against empty GOPATH, which would resolve paths to root (e.g. /bin) ifeq ($(GOPATH),) $(error GOPATH is not set. Please set it explicitly, e.g. export GOPATH=$$HOME/go) @@ -190,7 +193,7 @@ else endif @mkdir -p linux @mv ./dgraph/dgraph ./linux/dgraph - @docker build -f contrib/Dockerfile -t dgraph/dgraph:local . + @$(DOCKER) build -f contrib/Dockerfile -t dgraph/dgraph:local . @rm -r linux .PHONY: image-local @@ -207,7 +210,7 @@ clean: ## Clean build artifacts docker-image: dgraph ## Build Docker image (dgraph/dgraph:$VERSION) @mkdir -p linux @cp ./dgraph/dgraph ./linux/dgraph - docker build -f contrib/Dockerfile -t dgraph/dgraph:$(DGRAPH_VERSION) . + $(DOCKER) build -f contrib/Dockerfile -t dgraph/dgraph:$(DGRAPH_VERSION) . .PHONY: docker-image-standalone docker-image-standalone: dgraph docker-image @@ -219,19 +222,23 @@ docker-image-standalone: dgraph docker-image coverage-docker-image: dgraph-coverage @mkdir -p linux @cp ./dgraph/dgraph ./linux/dgraph - docker build -f contrib/Dockerfile -t dgraph/dgraph:$(DGRAPH_VERSION) . + $(DOCKER) build -f contrib/Dockerfile -t dgraph/dgraph:$(DGRAPH_VERSION) . -# build and run dependencies for ubuntu linux +# build and run dependencies for linux (auto-detects apt / dnf / pacman) +# The bundled jemalloc is compiled from source (see dgraph/Makefile), so a C/C++ +# toolchain plus curl/bzip2/tar are required even though libjemalloc itself is built locally. .PHONY: linux-dependency linux-dependency: - sudo apt-get update - sudo apt-get -y upgrade - sudo apt-get -y install ca-certificates - sudo apt-get -y install curl - sudo apt-get -y install gnupg - sudo apt-get -y install lsb-release - sudo apt-get -y install build-essential - sudo apt-get -y install protobuf-compiler + @if command -v apt-get >/dev/null 2>&1; then \ + sudo apt-get update && \ + sudo apt-get install -y ca-certificates curl gnupg lsb-release build-essential protobuf-compiler bzip2; \ + elif command -v dnf >/dev/null 2>&1; then \ + sudo dnf install -y ca-certificates curl gnupg gcc gcc-c++ make bzip2 tar protobuf-compiler; \ + elif command -v pacman >/dev/null 2>&1; then \ + sudo pacman -S --noconfirm --needed ca-certificates curl gnupg base-devel protobuf; \ + else \ + echo "ERROR: No supported package manager found (tried: apt, dnf, pacman)"; exit 1; \ + fi .PHONY: help help: ## Show available targets and variables diff --git a/contrib/Dockerfile b/contrib/Dockerfile index 95a177527ea..b708fad9752 100644 --- a/contrib/Dockerfile +++ b/contrib/Dockerfile @@ -41,7 +41,13 @@ RUN set -eu; \ ADD linux /usr/local/bin -RUN mkdir /dgraph +RUN mkdir /dgraph && \ + mkdir -p \ + /data/zero1 /data/zero2 /data/zero3 /data/zero4 \ + /data/zero5 /data/zero6 /data/zero7 /data/zero8 \ + /data/alpha1 /data/alpha2 /data/alpha3 /data/alpha4 \ + /data/alpha5 /data/alpha6 /data/alpha7 /data/alpha8 \ + /data/dg1 /data/dg0.1 WORKDIR /dgraph ENV GODEBUG=madvdontneed=1 diff --git a/contrib/smoke-test.sh b/contrib/smoke-test.sh new file mode 100755 index 00000000000..36cf7659220 --- /dev/null +++ b/contrib/smoke-test.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: © 2017-2025 Istari Digital, Inc. +# SPDX-License-Identifier: Apache-2.0 +# +# Smoke test for a locally-built dgraph binary. Brings up a single-node cluster +# (one zero + one alpha), exercises schema / mutation / query over the HTTP API, +# verifies the result, then tears everything down. +# +# Usage: +# contrib/smoke-test.sh +# +# Environment overrides: +# BIN path to the dgraph binary (default: ./dgraph/dgraph, else `dgraph` on PATH) +# ROOT working dir for cluster data (default: a fresh mktemp dir, removed on exit) + +set -u + +# Resolve the dgraph binary: prefer a local build, fall back to PATH. +if [ -z "${BIN:-}" ]; then + if [ -x "./dgraph/dgraph" ]; then + BIN="./dgraph/dgraph" + elif command -v dgraph >/dev/null 2>&1; then + BIN="dgraph" + else + echo "error: could not find dgraph binary (build it with 'make dgraph' or set BIN=...)" >&2 + exit 1 + fi +fi + +# Resolve a path-form BIN to an absolute path (we cd into per-node data dirs below); +# leave a bare command name (found on PATH) untouched. +case "$BIN" in + */*) BIN="$(cd "$(dirname "$BIN")" && pwd)/$(basename "$BIN")" ;; +esac + +ROOT="${ROOT:-$(mktemp -d)}" +PASS=0; FAIL=0 +ZERO_PID=""; ALPHA_PID="" + +say() { printf '\n=== %s ===\n' "$*"; } +ok() { echo "PASS: $*"; PASS=$((PASS + 1)); } +bad() { echo "FAIL: $*"; FAIL=$((FAIL + 1)); } + +cleanup() { + say "Tearing down" + [ -n "$ALPHA_PID" ] && kill "$ALPHA_PID" 2>/dev/null + [ -n "$ZERO_PID" ] && kill "$ZERO_PID" 2>/dev/null + wait 2>/dev/null + rm -rf "$ROOT" +} +trap cleanup EXIT + +mkdir -p "$ROOT/zero" "$ROOT/alpha" + +say "dgraph version" +"$BIN" version 2>&1 | sed -n '1,12p' + +say "Starting zero" +( cd "$ROOT/zero" && exec "$BIN" zero --my localhost:5080 ) >"$ROOT/zero.log" 2>&1 & +ZERO_PID=$! + +say "Starting alpha" +( cd "$ROOT/alpha" && exec "$BIN" alpha --my localhost:7080 --zero localhost:5080 ) \ + >"$ROOT/alpha.log" 2>&1 & +ALPHA_PID=$! + +say "Waiting for alpha /health" +healthy=0 +for _ in $(seq 1 60); do + if curl -sf localhost:8080/health >/dev/null 2>&1; then healthy=1; break; fi + sleep 2 +done +if [ "$healthy" = 1 ]; then + ok "alpha healthy" +else + bad "alpha did not become healthy" + echo "--- alpha.log (tail) ---"; tail -30 "$ROOT/alpha.log" + echo "--- zero.log (tail) ---"; tail -30 "$ROOT/zero.log" + exit 1 +fi + +say "Set schema" +curl -sf -X POST localhost:8080/alter -d 'name: string @index(exact) .' >/dev/null \ + && ok "alter" || bad "alter" + +say "Mutation" +# N-Quads must be newline-terminated, so embed real newlines via $'...'. +MUT=$(curl -s -X POST 'localhost:8080/mutate?commitNow=true' \ + -H 'Content-Type: application/rdf' \ + -d $'{\n set {\n _:alice "Alice" .\n _:bob "Bob" .\n }\n}') +echo "$MUT" +echo "$MUT" | grep -q '"code":"Success"' && ok "mutate" || bad "mutate" + +say "Query" +RES=$(curl -s -X POST localhost:8080/query \ + -H 'Content-Type: application/dql' \ + -d '{ q(func: eq(name, "Alice")) { name } }') +echo "$RES" +echo "$RES" | grep -q '"name":"Alice"' && ok "query returned Alice" || bad "query did not return Alice" + +say "Result: PASS=$PASS FAIL=$FAIL" +[ "$FAIL" = 0 ] diff --git a/dgraph/Makefile b/dgraph/Makefile index 1c2b103c5d7..5047f689776 100644 --- a/dgraph/Makefile +++ b/dgraph/Makefile @@ -52,9 +52,10 @@ endif # jemalloc stuff # Check common package-manager install paths: /usr/local/lib (make -# install from source), /usr/lib (apt-get libjemalloc-dev), and -# /usr/lib/libjemalloc_pic.a (apk add jemalloc-dev on Alpine/Wolfi). -HAS_JEMALLOC = $(shell test -f /usr/local/lib/libjemalloc.a -o -f /usr/lib/libjemalloc.a -o -f /usr/lib/libjemalloc_pic.a && echo "jemalloc") +# install from source), /usr/lib (apt-get libjemalloc-dev), +# /usr/lib/libjemalloc_pic.a (apk add jemalloc-dev on Alpine/Wolfi), and +# /usr/lib64 (dnf jemalloc-devel on Fedora/RHEL multilib systems). +HAS_JEMALLOC = $(shell test -f /usr/local/lib/libjemalloc.a -o -f /usr/local/lib64/libjemalloc.a -o -f /usr/lib/libjemalloc.a -o -f /usr/lib64/libjemalloc.a -o -f /usr/lib/libjemalloc_pic.a -o -f /usr/lib64/libjemalloc_pic.a && echo "jemalloc") JEMALLOC_URL = "https://github.com/jemalloc/jemalloc/releases/download/5.3.1/jemalloc-5.3.1.tar.bz2" # go install variables @@ -101,13 +102,13 @@ install: jemalloc jemalloc: @if [ -z "$(HAS_JEMALLOC)" ] ; then \ - mkdir -p /tmp/jemalloc-temp && cd /tmp/jemalloc-temp ; \ - echo "Downloading jemalloc" ; \ - curl -f -s -L ${JEMALLOC_URL} -o jemalloc.tar.bz2 ; \ - tar xjf ./jemalloc.tar.bz2 ; \ - cd jemalloc-5.3.1 ; \ - ./configure --with-jemalloc-prefix='je_' --with-malloc-conf='background_thread:true,metadata_thp:auto'; \ - make ; \ + rm -rf /tmp/jemalloc-temp && mkdir -p /tmp/jemalloc-temp && cd /tmp/jemalloc-temp && \ + echo "Downloading jemalloc" && \ + curl -f -s -L ${JEMALLOC_URL} -o jemalloc.tar.bz2 && \ + tar xjf ./jemalloc.tar.bz2 && \ + cd jemalloc-5.3.1 && \ + ./configure --with-jemalloc-prefix='je_' --with-malloc-conf='background_thread:true,metadata_thp:auto' --disable-cxx && \ + make && \ if [ "$(USER_ID)" = "0" ]; then \ make install ; \ else \ diff --git a/dgraph/cmd/root.go b/dgraph/cmd/root.go index 20082d8780b..29b10309800 100644 --- a/dgraph/cmd/root.go +++ b/dgraph/cmd/root.go @@ -64,6 +64,17 @@ cluster. func Execute() { initCmds() + // podman-compose 1.5.0 expands an unset ${COVERAGE_OUTPUT} as a literal + // empty-string token, which shlex passes to us as os.Args[1]=="". Strip + // any empty-string arguments before cobra sees them; they are never valid. + filtered := os.Args[:1] + for _, a := range os.Args[1:] { + if a != "" { + filtered = append(filtered, a) + } + } + os.Args = filtered + // Convinces glog that Parse() has been called to avoid noisy logs. // https://github.com/kubernetes/kubernetes/issues/17162#issuecomment-225596212 x.Check(flag.CommandLine.Parse([]string{})) diff --git a/dgraph/docker-compose.yml b/dgraph/docker-compose.yml index 8bf64de8798..cb68c14d553 100644 --- a/dgraph/docker-compose.yml +++ b/dgraph/docker-compose.yml @@ -238,7 +238,7 @@ services: "secret-file=/dgraph-acl/hmac-secret; access-ttl=20s;" minio: - image: minio/minio:latest + image: docker.io/minio/minio:latest env_file: - ./minio.env working_dir: /data/minio diff --git a/graphql/e2e/directives/docker-compose.yml b/graphql/e2e/directives/docker-compose.yml index fb1ba6a92b9..2fe93792a0f 100644 --- a/graphql/e2e/directives/docker-compose.yml +++ b/graphql/e2e/directives/docker-compose.yml @@ -38,7 +38,7 @@ services: --graphql "lambda-url=http://lambda:8686/graphql-worker; debug=true;" --trace "ratio=1.0;" lambda: - image: dgraph/dgraph-lambda:latest + image: docker.io/dgraph/dgraph-lambda:latest labels: cluster: test ports: diff --git a/graphql/e2e/normal/docker-compose.yml b/graphql/e2e/normal/docker-compose.yml index fb1ba6a92b9..2fe93792a0f 100644 --- a/graphql/e2e/normal/docker-compose.yml +++ b/graphql/e2e/normal/docker-compose.yml @@ -38,7 +38,7 @@ services: --graphql "lambda-url=http://lambda:8686/graphql-worker; debug=true;" --trace "ratio=1.0;" lambda: - image: dgraph/dgraph-lambda:latest + image: docker.io/dgraph/dgraph-lambda:latest labels: cluster: test ports: diff --git a/ocagent/docker-compose.yml b/ocagent/docker-compose.yml index 7d8a797a03b..895b9be1b60 100644 --- a/ocagent/docker-compose.yml +++ b/ocagent/docker-compose.yml @@ -36,7 +36,7 @@ services: --my=zero1:5180 --replicas=3 --logtostderr -v=2 --bindall --trace "jaeger=http://ocagent:14268;" ocagent: - image: omnition/opencensus-agent:1.0.15 + image: docker.io/omnition/opencensus-agent:1.0.15 container_name: ocagent labels: cluster: test @@ -51,7 +51,7 @@ services: read_only: true command: --config /conf/ocagent-config.yaml datadog: - image: datadog/agent:latest + image: docker.io/datadog/agent:latest container_name: datadog working_dir: /working/datadog volumes: diff --git a/systest/backup/encryption/docker-compose.yml b/systest/backup/encryption/docker-compose.yml index 8a6646e2451..d5c39b99ab2 100644 --- a/systest/backup/encryption/docker-compose.yml +++ b/systest/backup/encryption/docker-compose.yml @@ -110,7 +110,7 @@ services: server-cert=/dgraph-tls/node.crt; server-key=/dgraph-tls/node.key; internal-port=true; client-cert=/dgraph-tls/client.zero1.crt; client-key=/dgraph-tls/client.zero1.key;" minio: - image: minio/minio:latest + image: docker.io/minio/minio:latest env_file: - ../../backup.env ports: diff --git a/systest/backup/minio-large/docker-compose.yml b/systest/backup/minio-large/docker-compose.yml index c48fe625ad0..d04dfca2bc8 100644 --- a/systest/backup/minio-large/docker-compose.yml +++ b/systest/backup/minio-large/docker-compose.yml @@ -75,7 +75,7 @@ services: server-cert=/dgraph-tls/node.crt; server-key=/dgraph-tls/node.key; internal-port=true; client-cert=/dgraph-tls/client.alpha3.crt; client-key=/dgraph-tls/client.alpha3.key;" minio: - image: minio/minio:${MINIO_IMAGE_ARCH:-RELEASE.2020-11-13T20-10-18Z} + image: docker.io/minio/minio:${MINIO_IMAGE_ARCH:-RELEASE.2020-11-13T20-10-18Z} env_file: - ../../backup.env ports: diff --git a/systest/backup/minio/docker-compose.yml b/systest/backup/minio/docker-compose.yml index 6e8674bbb49..c3c8feb0e71 100644 --- a/systest/backup/minio/docker-compose.yml +++ b/systest/backup/minio/docker-compose.yml @@ -100,7 +100,7 @@ services: server-cert=/dgraph-tls/node.crt; server-key=/dgraph-tls/node.key; internal-port=true; client-cert=/dgraph-tls/client.zero1.crt; client-key=/dgraph-tls/client.zero1.key;" minio: - image: minio/minio:${MINIO_IMAGE_ARCH:-RELEASE.2020-11-13T20-10-18Z} + image: docker.io/minio/minio:${MINIO_IMAGE_ARCH:-RELEASE.2020-11-13T20-10-18Z} env_file: - ../../backup.env ports: diff --git a/systest/backup/nfs-backup/docker-compose.yml b/systest/backup/nfs-backup/docker-compose.yml index 991557641bc..ddd1462be5b 100644 --- a/systest/backup/nfs-backup/docker-compose.yml +++ b/systest/backup/nfs-backup/docker-compose.yml @@ -405,7 +405,7 @@ services: /gobin/dgraph ${COVERAGE_OUTPUT} alpha --my=alpha8_restore_clust_non_ha:7080 --zero=zero8_restore_clust_non_ha:5080 --logtostderr -v=2 --security "whitelist=0.0.0.0/0;" nfs: - image: itsthenetwork/nfs-server-alpine:${NFS_SERVER_IMAGE_ARCH:-12} + image: docker.io/itsthenetwork/nfs-server-alpine:${NFS_SERVER_IMAGE_ARCH:-12} restart: unless-stopped privileged: true diff --git a/systest/bulk_live/bulk/docker-compose.yml b/systest/bulk_live/bulk/docker-compose.yml index 59d11ecccba..90b8f57150b 100644 --- a/systest/bulk_live/bulk/docker-compose.yml +++ b/systest/bulk_live/bulk/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.5" services: minio: - image: minio/minio:RELEASE.2020-11-13T20-10-18Z + image: docker.io/minio/minio:RELEASE.2020-11-13T20-10-18Z ports: - "9001" env_file: diff --git a/systest/bulk_live/live/docker-compose.yml b/systest/bulk_live/live/docker-compose.yml index 8f238f83617..d9b2dcedbdf 100644 --- a/systest/bulk_live/live/docker-compose.yml +++ b/systest/bulk_live/live/docker-compose.yml @@ -23,7 +23,7 @@ services: limits: memory: 32G minio: - image: minio/minio:RELEASE.2020-11-13T20-10-18Z + image: docker.io/minio/minio:RELEASE.2020-11-13T20-10-18Z ports: - "9001" env_file: diff --git a/systest/cloud/docker-compose.yml b/systest/cloud/docker-compose.yml index 97b23e045ac..b7244f05439 100644 --- a/systest/cloud/docker-compose.yml +++ b/systest/cloud/docker-compose.yml @@ -44,7 +44,7 @@ services: --acl "secret-file=/dgraph-acl/hmac-secret; access-ttl=20s;" --limit "shared-instance=true" minio: - image: minio/minio:latest + image: docker.io/minio/minio:latest env_file: - ./../../dgraph/minio.env working_dir: /data/minio diff --git a/systest/export/docker-compose.yml b/systest/export/docker-compose.yml index d61f05ea29e..cfae0a943d7 100644 --- a/systest/export/docker-compose.yml +++ b/systest/export/docker-compose.yml @@ -69,7 +69,7 @@ services: /gobin/dgraph ${COVERAGE_OUTPUT} alpha --my=alpha3:7080 --zero=zero1:5080 --logtostderr -v=2 --security "whitelist=0.0.0.0/0;" minio: - image: minio/minio:${MINIO_IMAGE_ARCH:-RELEASE.2020-11-13T20-10-18Z} + image: docker.io/minio/minio:${MINIO_IMAGE_ARCH:-RELEASE.2020-11-13T20-10-18Z} env_file: - export.env ports: diff --git a/systest/tracing/jaeger1/docker-compose.yml b/systest/tracing/jaeger1/docker-compose.yml index 7d3d19cd6fc..6763dd27abc 100644 --- a/systest/tracing/jaeger1/docker-compose.yml +++ b/systest/tracing/jaeger1/docker-compose.yml @@ -23,7 +23,7 @@ services: service=alpha1;" --my=alpha1:7080 --zero=zero1:5080 --logtostderr -v=2 --security "whitelist=0.0.0.0/0;" jaeger: - image: jaegertracing/all-in-one:1.60 + image: docker.io/jaegertracing/all-in-one:1.60 environment: - SPAN_STORAGE_TYPE=memory ports: diff --git a/systest/tracing/jaeger2/docker-compose.yml b/systest/tracing/jaeger2/docker-compose.yml index f007fe92c6d..05146561082 100644 --- a/systest/tracing/jaeger2/docker-compose.yml +++ b/systest/tracing/jaeger2/docker-compose.yml @@ -24,7 +24,7 @@ services: service=alpha1;" --my=alpha1:7080 --zero=zero1:5080 --logtostderr -v=2 --security "whitelist=0.0.0.0/0;" jaeger: - image: jaegertracing/jaeger:latest + image: docker.io/jaegertracing/jaeger:latest environment: - JAEGER_LISTEN_HOST=0.0.0.0 ports: diff --git a/t/Makefile b/t/Makefile index 5f7c8be1ac1..6fef878597f 100644 --- a/t/Makefile +++ b/t/Makefile @@ -216,9 +216,10 @@ install-deps-docker-linux-apt: .PHONY: install-deps-docker-linux-dnf install-deps-docker-linux-dnf: - @sudo dnf -y install dnf-plugins-core && \ - sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \ - sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + @# Fedora 38+ and RHEL 9+ do not support Docker CE from the official Docker repo. + @# Use Podman with its Docker compatibility layer (podman-docker + podman-compose) instead. + @sudo dnf install -y podman podman-docker podman-compose && \ + systemctl --user enable --now podman.socket 2>/dev/null || true .PHONY: install-deps-docker-linux-pacman install-deps-docker-linux-pacman: diff --git a/t/scripts/check-deps-docker.sh b/t/scripts/check-deps-docker.sh index 62bfcfb40a8..50dd9f339ce 100755 --- a/t/scripts/check-deps-docker.sh +++ b/t/scripts/check-deps-docker.sh @@ -5,13 +5,16 @@ set -euo pipefail # shellcheck source=checkhelper.sh source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" -# ---- thresholds ---- +# ---- version thresholds ---- MIN_DOCKER_MAJOR=29 MIN_COMPOSE_MAJOR=2 MIN_COMPOSE_MINOR=40 MIN_COMPOSE_PATCH=0 +MIN_PODMAN_MAJOR=4 +MIN_PODMAN_MINOR=0 + MIN_MEM_MB=4 REC_MEM_MB=8 @@ -25,23 +28,42 @@ semver_ge() { ((aPat >= bPat)) } -# Find docker binary find_docker() { command -v docker 2>/dev/null } +find_podman() { + command -v podman 2>/dev/null +} + +# Returns "docker", "podman", or "none". +detect_runtime() { + if find_docker &>/dev/null; then + local ver + ver="$(docker --version 2>/dev/null | tr '[:upper:]' '[:lower:]')" + if [[ "${ver}" == *"podman"* ]]; then + echo "podman" + else + echo "docker" + fi + elif find_podman &>/dev/null; then + echo "podman" + else + echo "none" + fi +} + +# ---- Docker installation helpers ---- + install_docker_linux() { if command -v apt-get &>/dev/null; then - # Install prerequisites sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg - # Add Docker's official GPG key sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg - # Detect distro (works for Ubuntu and Debian) local distro if [[ -f /etc/os-release ]]; then # shellcheck source=/dev/null @@ -51,25 +73,21 @@ install_docker_linux() { distro="ubuntu" fi - # Add the repository echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${distro} \ - $(. /etc/os-release && echo "${VERSION_CODENAME:-$(lsb_release -cs)}") stable" | + $(. /etc/os-release && echo "${VERSION_CODENAME:-$(lsb_release -cs)}") stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - # Start and enable Docker service sudo systemctl start docker || true sudo systemctl enable docker || true elif command -v dnf &>/dev/null; then - sudo dnf -y install dnf-plugins-core - sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo - sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - sudo systemctl start docker || true - sudo systemctl enable docker || true + # Fedora 38+ and RHEL 9+ do not support Docker CE from the official + # Docker repo. Use Podman with the Docker compatibility layer instead. + install_podman_linux elif command -v yum &>/dev/null; then sudo yum install -y yum-utils @@ -88,7 +106,6 @@ install_docker_linux() { exit 1 fi - # Add current user to docker group to avoid needing sudo if [[ -n ${SUDO_USER-} ]]; then sudo usermod -aG docker "${SUDO_USER}" || true elif [[ -n ${USER-} ]] && [[ ${USER} != "root" ]]; then @@ -105,7 +122,43 @@ install_docker() { run_os_installer install_docker_linux install_docker_macos } -print_install_instructions() { +# ---- Podman installation helpers ---- + +install_podman_linux() { + if command -v dnf &>/dev/null; then + # Fedora / RHEL: podman-docker provides the docker CLI shim, + # podman-compose provides docker-compose-compatible functionality. + sudo dnf install -y podman podman-docker podman-compose + # Enable the Podman socket so the Docker SDK can connect to it. + systemctl --user enable --now podman.socket 2>/dev/null || true + + elif command -v apt-get &>/dev/null; then + sudo apt-get update + sudo apt-get install -y podman podman-compose + systemctl --user enable --now podman.socket 2>/dev/null || true + + elif command -v pacman &>/dev/null; then + sudo pacman -S --noconfirm podman podman-compose + systemctl --user enable --now podman.socket 2>/dev/null || true + + else + err "no supported package manager found (tried: dnf, apt, pacman)" + exit 1 + fi +} + +install_podman_macos() { + ensure_brew || exit 1 + brew install podman podman-compose +} + +install_podman() { + run_os_installer install_podman_linux install_podman_macos +} + +# ---- Install instructions ---- + +print_docker_install_instructions() { echo "" err "Docker is not installed" echo "" @@ -114,7 +167,7 @@ print_install_instructions() { case "$(get_os)" in Linux) err " apt (Ubuntu/Debian): see https://docs.docker.com/engine/install/ubuntu/" - err " dnf (Fedora): see https://docs.docker.com/engine/install/fedora/" + err " dnf (Fedora/RHEL): install Podman instead — see print_podman_install_instructions" err " yum (CentOS/RHEL): see https://docs.docker.com/engine/install/centos/" err " pacman (Arch): sudo pacman -S docker docker-compose" ;; @@ -130,28 +183,50 @@ print_install_instructions() { print_auto_install_hint } +print_podman_install_instructions() { + echo "" + err "Podman is not installed (required on Fedora/RHEL where Docker CE is unavailable)" + echo "" + err "Please install Podman manually:" + + case "$(get_os)" in + Linux) + err " dnf (Fedora/RHEL): sudo dnf install -y podman podman-docker podman-compose" + err " systemctl --user enable --now podman.socket" + err " apt (Ubuntu/Debian): sudo apt-get install -y podman podman-compose" + err " pacman (Arch): sudo pacman -S podman podman-compose" + ;; + Darwin) + err " brew install podman podman-compose" + ;; + *) + err " See: https://podman.io/getting-started/installation" + ;; + esac + + print_auto_install_hint +} + +# ---- Docker requirement checks ---- + check_docker_requirements() { - # Check jq dependency for version parsing if ! command -v jq &>/dev/null; then err "jq not found in PATH (required for version parsing)" exit 1 fi - # Check Docker daemon is running if ! docker info &>/dev/null; then err "Docker daemon is not running" err "Please start Docker Desktop or the Docker service" exit 1 fi - # Fetch all info in one call local docker_info if ! docker_info="$(docker info --format '{{json .}}' 2>&1)"; then err "failed to get docker info: ${docker_info}" exit 1 fi - # 1) Parse and check Docker version local docker_ver docker_maj docker_min docker_pat docker_ver="$(jq -r '.ServerVersion // empty' <<<"${docker_info}")" if [[ -z ${docker_ver} ]]; then @@ -166,7 +241,6 @@ check_docker_requirements() { exit 1 fi - # 2) Parse and check Docker Compose version local compose_ver compose_maj compose_min compose_pat compose_ver="$(jq -r '.ClientInfo.Plugins[] | select(.Name == "compose") | .Version // empty' <<<"${docker_info}" | sed 's/^v//')" if [[ -z ${compose_ver} ]]; then @@ -182,7 +256,6 @@ check_docker_requirements() { exit 1 fi - # 3) Check memory local mem_bytes mem_mb mem_bytes="$(jq -r '.MemTotal // empty' <<<"${docker_info}")" if [[ -z ${mem_bytes} || ! ${mem_bytes} =~ ^[0-9]+$ ]]; then @@ -200,37 +273,98 @@ check_docker_requirements() { fi } -main() { - # Check if docker is already available - if find_docker &>/dev/null; then - check_docker_requirements - exit 0 +# ---- Podman requirement checks ---- + +check_podman_requirements() { + # Determine the actual podman binary (might be via docker shim or directly). + local podman_bin="podman" + if ! command -v podman &>/dev/null; then + # docker is a podman shim but podman binary itself isn't in PATH — still OK. + if ! docker --version &>/dev/null; then + err "Neither podman nor docker (podman shim) found in PATH" + exit 1 + fi + podman_bin="docker" + fi + + local ver major minor + ver="$("${podman_bin}" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)" + if [[ -z ${ver} ]]; then + err "Could not determine Podman version" + exit 1 + fi + IFS='.' read -r major minor _ <<<"${ver}" + + if ! semver_ge "${major}" "${minor}" "0" \ + "${MIN_PODMAN_MAJOR}" "${MIN_PODMAN_MINOR}" "0"; then + err "Podman ${ver} is below minimum (need >= ${MIN_PODMAN_MAJOR}.${MIN_PODMAN_MINOR})" + exit 1 + fi + + # Check that compose support is available. + if ! podman compose version &>/dev/null 2>&1 && + ! command -v podman-compose &>/dev/null; then + err "No Podman compose support found." + err "Install podman-compose: sudo dnf install -y podman-compose" + err " or: pip3 install podman-compose" + exit 1 + fi + + # Check memory using /proc/meminfo (Podman uses host memory on Linux). + local mem_kb mem_mb + if [[ -r /proc/meminfo ]]; then + mem_kb="$(awk '/MemTotal/ {print $2}' /proc/meminfo)" + mem_mb=$((mem_kb / 1024)) + if ((mem_mb < MIN_MEM_MB * 1024)); then + err "System memory ${mem_mb}MB is below minimum (need >= $((MIN_MEM_MB * 1024))MB)" + exit 1 + fi + if ((mem_mb < REC_MEM_MB * 1024)); then + warn "System memory ${mem_mb}MB is below recommended (>= $((REC_MEM_MB * 1024))MB)" + fi fi +} - # Docker not found - if [[ ${AUTO_INSTALL-} == "true" ]]; then - install_docker +main() { + local runtime + runtime="$(detect_runtime)" - # Re-check after install - if find_docker &>/dev/null; then - # On macOS, the daemon might not be running yet after cask install - if docker info &>/dev/null; then - check_docker_requirements + case "${runtime}" in + docker) + check_docker_requirements + exit 0 + ;; + podman) + check_podman_requirements + exit 0 + ;; + none) + # Neither docker nor podman found. + if [[ ${AUTO_INSTALL-} == "true" ]]; then + if command -v dnf &>/dev/null; then + # Fedora/RHEL: use Podman. + install_podman + check_podman_requirements else - err "Docker installed but daemon not running yet" - err "Please start Docker Desktop and re-run this script" - exit 1 + install_docker + if find_docker &>/dev/null && docker info &>/dev/null; then + check_docker_requirements + else + err "docker check still failing after installation" + exit 1 + fi fi exit 0 + fi + # No auto-install: decide which instructions to show. + if command -v dnf &>/dev/null; then + print_podman_install_instructions else - err "docker check still failing after installation" - exit 1 + print_docker_install_instructions fi - fi - - # No auto-install, fail with instructions - print_install_instructions - exit 1 + exit 1 + ;; + esac } main "$@" diff --git a/t/scripts/check-docker-available-memory.sh b/t/scripts/check-docker-available-memory.sh index 65017b82126..95713ff6671 100755 --- a/t/scripts/check-docker-available-memory.sh +++ b/t/scripts/check-docker-available-memory.sh @@ -5,35 +5,49 @@ set -euo pipefail # shellcheck source=checkhelper.sh source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" -# Minimum recommended Docker memory in MB +# Minimum recommended memory in MB REC_MEM_MB=8192 main() { - # Query Docker memory (Docker must be running — guaranteed by Make prereq on check-deps-docker) - local mem_bytes - mem_bytes="$(docker info --format '{{.MemTotal}}' 2>/dev/null)" || { - warn "could not query Docker memory (is Docker running?)" - exit 0 - } + local os mem_bytes mem_mb + os="$(uname -s)" + + if [[ ${os} == "Linux" ]]; then + # On Linux both Docker and Podman use host memory directly. + # Read from /proc/meminfo instead of querying the container daemon. + if [[ ! -r /proc/meminfo ]]; then + warn "could not read /proc/meminfo" + exit 0 + fi + local mem_kb + mem_kb="$(awk '/MemTotal/ {print $2}' /proc/meminfo 2>/dev/null)" || { + warn "could not parse /proc/meminfo" + exit 0 + } + mem_bytes=$((mem_kb * 1024)) + else + # macOS (Docker Desktop): memory is VM-limited; query via docker info. + mem_bytes="$(docker info --format '{{.MemTotal}}' 2>/dev/null)" || { + warn "could not query Docker memory (is Docker running?)" + exit 0 + } + fi if [[ -z ${mem_bytes} || ! ${mem_bytes} =~ ^[0-9]+$ ]]; then - warn "could not parse Docker memory info" + warn "could not parse memory info" exit 0 fi - local mem_mb=$((mem_bytes / 1024 / 1024)) + mem_mb=$((mem_bytes / 1024 / 1024)) if ((mem_mb >= REC_MEM_MB)); then exit 0 fi # Memory is below recommended - warn "Docker memory ${mem_mb}MB is below recommended ${REC_MEM_MB}MB" + warn "Available memory ${mem_mb}MB is below recommended ${REC_MEM_MB}MB" warn "Some tests may fail with OOM errors" - local os - os="$(uname -s)" - if [[ ${os} == "Darwin" ]]; then local settings_file="${HOME}/Library/Group Containers/group.com.docker/settings-store.json" @@ -58,7 +72,6 @@ main() { warn "Please increase memory via: Docker Desktop → Settings → Resources → Memory" fi else - # Linux — Docker uses host memory directly warn "Consider adding more RAM to this machine or reducing test parallelism" fi diff --git a/t/t.go b/t/t.go index 93458ef319c..96ab6272b87 100644 --- a/t/t.go +++ b/t/t.go @@ -156,6 +156,11 @@ func commandWithContext(ctx context.Context, args ...string) *exec.Cmd { if *runCoverage { cmd.Env = append(cmd.Env, "COVERAGE_OUTPUT=--test.coverprofile=coverage.out") } + // Always ensure COVERAGE_OUTPUT is set. Podman-compose expands an *unset* + // ${COVERAGE_OUTPUT} as the literal '""', which shlex then passes to dgraph + // as an empty-string argument before the subcommand. An explicit empty + // value is substituted as nothing (no token), matching Docker Compose. + cmd.Env = testutil.EnsureCoverageOutput(cmd.Env) if runtime.GOARCH == "arm64" { cmd.Env = append(cmd.Env, "MINIO_IMAGE_ARCH=RELEASE.2020-11-13T20-10-18Z-arm64") cmd.Env = append(cmd.Env, "NFS_SERVER_IMAGE_ARCH=11-arm") @@ -188,6 +193,7 @@ func ensureGoPathLinuxBinEnvVarSet() { gopath := os.Getenv("GOPATH") if gopath == "" { gopath = filepath.Join(os.Getenv("HOME"), "go") + os.Setenv("GOPATH", gopath) // export the default so child processes inherit it } var gopathLinuxBin string @@ -238,7 +244,8 @@ func startCluster(composeFile, prefix string) error { // + --project-directory so relative bind-mount sources resolve against the // pristine test package dir. composeArgs := ComposeFileArgs(composeFile, *baseDir) - upArgs := append([]string{"docker", "compose", "--compatibility"}, + composePfx := testutil.ContainerComposeCmdPrefix() + upArgs := append(composePfx, append(composeArgs, "-p", prefix, "up", "--force-recreate", "--build", "--remove-orphans", "--detach")...) // docker compose `up` on a shared named volume can race on initial @@ -247,7 +254,16 @@ func startCluster(composeFile, prefix string) error { // Retry once after a full `down -v` — the second attempt finds a fresh // volume and succeeds. One retry is enough in practice for this class // of race; anything persistent is a real configuration error. - const upAttempts = 3 + // Podman rootless mode uses a per-container rootlessport daemon that + // independently binds random host ports; simultaneous container starts + // can race and produce "address already in use". 5 attempts with a + // 5-second drain window is enough for the port daemon to release bindings. + upAttempts := 3 + retryWait := 2 * time.Second + if testutil.ContainerRuntime() == "podman" { + upAttempts = 5 + retryWait = 5 * time.Second + } var lastErr error var cmdStderr strings.Builder fmt.Printf("Bringing up cluster %s for package: %s ...\n", prefix, composeFile) @@ -273,12 +289,12 @@ func startCluster(composeFile, prefix string) error { // before the next `up` tries to populate a fresh volume. // Without the sleep, the retry can hit the same volume- // init race the first attempt did. - downArgs := append([]string{"docker", "compose", "--compatibility"}, + downArgs := append(testutil.ContainerComposeCmdPrefix(), append(composeArgs, "-p", prefix, "down", "-v")...) downCmd := command(downArgs...) downCmd.Stderr = nil _ = downCmd.Run() - time.Sleep(2 * time.Second) + time.Sleep(retryWait) fmt.Printf("Retrying cluster bring-up (attempt %d/%d) after `down -v`...\n", attempt+1, upAttempts) } } @@ -343,7 +359,7 @@ func outputLogs(prefix string) { if c == nil { return } - logCmd := exec.Command("docker", "logs", c.ID) + logCmd := exec.Command(testutil.ContainerRuntime(), "logs", c.ID) out, err := logCmd.CombinedOutput() if err != nil { fmt.Printf("error fetching docker logs for %s: %v\n", c.ID, err) @@ -384,7 +400,7 @@ func stopCluster(composeFile, prefix string, wg *sync.WaitGroup, err error) { if err != nil { outputLogs(prefix) } - stopArgs := append([]string{"docker", "compose", "--compatibility"}, + stopArgs := append(testutil.ContainerComposeCmdPrefix(), append(composeArgs, "-p", prefix, "stop")...) cmd := command(stopArgs...) cmd.Stderr = nil @@ -430,7 +446,7 @@ func stopCluster(composeFile, prefix string, wg *sync.WaitGroup, err error) { } } - downArgs := append([]string{"docker", "compose", "--compatibility"}, + downArgs := append(testutil.ContainerComposeCmdPrefix(), append(composeArgs, "-p", prefix, "down", "-v")...) cmd = command(downArgs...) if err := cmd.Run(); err != nil { @@ -696,7 +712,7 @@ func runTests(taskCh chan task, closer *z.Closer) error { if !started || stopped { return } - pauseArgs := append([]string{"docker", "compose", "--compatibility"}, + pauseArgs := append(testutil.ContainerComposeCmdPrefix(), append(ComposeFileArgs(defaultCompose, *baseDir), "-p", prefix, "stop")...) cmd := command(pauseArgs...) cmd.Stderr = nil @@ -717,7 +733,7 @@ func runTests(taskCh chan task, closer *z.Closer) error { if !defaultPaused { return nil // already running } - resumeArgs := append([]string{"docker", "compose", "--compatibility"}, + resumeArgs := append(testutil.ContainerComposeCmdPrefix(), append(ComposeFileArgs(defaultCompose, *baseDir), "-p", prefix, "start")...) cmd := command(resumeArgs...) cmd.Stderr = nil @@ -1409,6 +1425,9 @@ func run() error { fmt.Printf("Found Teamcity: %s\n", tc) isTeamcity = true } + // Initialize container runtime early so DOCKER_HOST is configured for + // Podman before any Docker API SDK calls (e.g. AllContainers). + fmt.Printf("Container runtime: %s\n", testutil.ContainerRuntime()) if *clear { removeAllTestContainers() return nil diff --git a/testutil/bulk.go b/testutil/bulk.go index c06ef2691bd..f611041e5f4 100644 --- a/testutil/bulk.go +++ b/testutil/bulk.go @@ -158,9 +158,10 @@ func freePort(port int) int { func StartAlphas(compose string) error { composeArgs := ComposeArgs(compose) - cmd := exec.Command("docker", append([]string{"compose", "--compatibility"}, - append(composeArgs, "-p", DockerPrefix, "up", "-d", "--force-recreate")...)...) - cmd.Env = append(os.Environ(), EnvForCompose()...) + pfx := ContainerComposeCmdPrefix() + allArgs := append(pfx[1:], append(composeArgs, "-p", DockerPrefix, "up", "-d", "--force-recreate")...) + cmd := exec.Command(pfx[0], allArgs...) + cmd.Env = EnsureCoverageOutput(append(os.Environ(), EnvForCompose()...)) fmt.Println("Starting alphas with: ", cmd.String()) @@ -184,10 +185,10 @@ func StartAlphas(compose string) error { func StopAlphasForCoverage(composeFile string) { composeArgs := ComposeArgs(composeFile) - args := append([]string{"compose", "--compatibility"}, - append(composeArgs, "-p", DockerPrefix, "stop")...) - cmd := exec.CommandContext(context.Background(), "docker", args...) - cmd.Env = append(os.Environ(), EnvForCompose()...) + pfx := ContainerComposeCmdPrefix() + args := append(pfx[1:], append(composeArgs, "-p", DockerPrefix, "stop")...) + cmd := exec.CommandContext(context.Background(), pfx[0], args...) + cmd.Env = EnsureCoverageOutput(append(os.Environ(), EnvForCompose()...)) fmt.Printf("Running: %s with %s\n", cmd, DockerPrefix) if err := cmd.Run(); err != nil { fmt.Printf("Error while bringing down cluster. Prefix: %s. Error: %v\n", DockerPrefix, err) @@ -196,9 +197,11 @@ func StopAlphasForCoverage(composeFile string) { func StopAlphasAndDetectRace(alphas []string) (raceDetected bool) { raceDetected = DetectRaceInAlphas(DockerPrefix) - args := []string{"compose", "-p", DockerPrefix, "rm", "-f", "-s", "-v"} + pfx := ContainerComposeCmdPrefix() + args := append(pfx[1:], "-p", DockerPrefix, "rm", "-f", "-s", "-v") args = append(args, alphas...) - cmd := exec.CommandContext(context.Background(), "docker", args...) + cmd := exec.CommandContext(context.Background(), pfx[0], args...) + cmd.Env = EnsureCoverageOutput(os.Environ()) fmt.Printf("Running: %s with %s\n", cmd, DockerPrefix) if err := cmd.Run(); err != nil { fmt.Printf("Error while bringing down cluster. Prefix: %s. Error: %v\n", DockerPrefix, err) diff --git a/testutil/docker.go b/testutil/docker.go index 1f53262cd66..72dbd5d6867 100644 --- a/testutil/docker.go +++ b/testutil/docker.go @@ -262,7 +262,7 @@ func DockerRun(instance string, op int) error { // MARKED FOR DEPRECATION: DockerCp copies from/to a container. Paths inside a container have the format // container_name:path. func DockerCp(srcPath, dstPath string) error { - argv := []string{"docker", "cp", srcPath, dstPath} + argv := []string{ContainerRuntime(), "cp", srcPath, dstPath} return Exec(argv...) } @@ -293,7 +293,7 @@ func DockerExec(instance string, cmd ...string) error { glog.Fatalf("Unable to find container: %s\n", instance) return nil } - argv := []string{"docker", "exec", "--user", "root", c.ID} + argv := []string{ContainerRuntime(), "exec", "--user", "root", c.ID} argv = append(argv, cmd...) return Exec(argv...) } diff --git a/testutil/exec.go b/testutil/exec.go index fa4c56a0194..d86a60435f6 100644 --- a/testutil/exec.go +++ b/testutil/exec.go @@ -153,7 +153,7 @@ func DetectIfRaceViolation(instance ContainerInstance) bool { return false } - logCmd := exec.Command("docker", "logs", c.ID) + logCmd := exec.Command(ContainerRuntime(), "logs", c.ID) out, err := logCmd.CombinedOutput() if err != nil { fmt.Printf("Error: while getting docker logs %v\n", err) diff --git a/testutil/runtime.go b/testutil/runtime.go new file mode 100644 index 00000000000..fe7c3495c36 --- /dev/null +++ b/testutil/runtime.go @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +package testutil + +import ( + "fmt" + "os" + "os/exec" + "strings" + "sync" +) + +var ( + runtimeOnce sync.Once + runtimeName string +) + +// ContainerRuntime returns the container runtime CLI to use: "docker" or "podman". +// Detection order: +// 1. CONTAINER_RUNTIME env var override +// 2. "docker" binary that reports itself as Podman → "podman" +// 3. "docker" binary (real Docker) → "docker" +// 4. "podman" binary (no docker) → "podman" +// 5. fallback → "docker" +// +// When Podman is the runtime, DOCKER_HOST is automatically set to the +// Podman socket so the Docker API SDK can communicate with it. +func ContainerRuntime() string { + runtimeOnce.Do(func() { + runtimeName = detectRuntime() + if runtimeName == "podman" { + ensurePodmanSocket() + } + }) + return runtimeName +} + +// ContainerComposeCmdPrefix returns the base command-line slice for compose +// operations. Callers append their subcommand and flags: +// +// prefix := ContainerComposeCmdPrefix() +// cmd := exec.Command(prefix[0], append(prefix[1:], "-p", project, "up", "-d")...) +// +// Docker: ["docker", "compose", "--compatibility"] +// Podman: ["podman", "compose"] +func ContainerComposeCmdPrefix() []string { + if ContainerRuntime() == "podman" { + return []string{"podman", "compose"} + } + return []string{"docker", "compose", "--compatibility"} +} + +func detectRuntime() string { + if rt := os.Getenv("CONTAINER_RUNTIME"); rt != "" { + return rt + } + if _, err := exec.LookPath("docker"); err == nil { + out, _ := exec.Command("docker", "--version").Output() + if strings.Contains(strings.ToLower(string(out)), "podman") { + return "podman" + } + return "docker" + } + if _, err := exec.LookPath("podman"); err == nil { + return "podman" + } + return "docker" +} + +// EnsureCoverageOutput returns env with COVERAGE_OUTPUT guaranteed to be +// present. When the variable is already set (coverage mode active) it is left +// unchanged. When it is missing, it is added as an empty string. +// +// Podman-compose expands an *unset* ${COVERAGE_OUTPUT} as the literal two- +// character string '""', which shlex then passes to dgraph as an empty-string +// argument before the subcommand ("dgraph" + ["", "zero", ...]). Docker Compose +// silently collapses the extra whitespace; podman-compose does not. Setting the +// variable to the empty string forces podman-compose to use the actual value +// (empty → no token) instead of the quoted placeholder. +func EnsureCoverageOutput(env []string) []string { + for _, e := range env { + if strings.HasPrefix(e, "COVERAGE_OUTPUT=") { + return env // already set; leave it alone + } + } + return append(env, "COVERAGE_OUTPUT=") +} + +// ensurePodmanSocket sets DOCKER_HOST to the Podman socket so the Docker +// SDK (github.com/docker/docker/client) can connect to Podman's API server. +// It is a no-op when DOCKER_HOST is already set or the socket does not exist. +func ensurePodmanSocket() { + if os.Getenv("DOCKER_HOST") != "" { + return + } + xdgRuntime := os.Getenv("XDG_RUNTIME_DIR") + if xdgRuntime == "" { + xdgRuntime = fmt.Sprintf("/run/user/%d", os.Getuid()) + } + sock := xdgRuntime + "/podman/podman.sock" + if _, err := os.Stat(sock); err == nil { + os.Setenv("DOCKER_HOST", "unix://"+sock) + } +} diff --git a/worker/docker-compose.yml b/worker/docker-compose.yml index d7aa26ae61a..f59a9b0852b 100644 --- a/worker/docker-compose.yml +++ b/worker/docker-compose.yml @@ -105,7 +105,7 @@ services: --zero=zero1:5080,zero2:5080,zero3:5080 --logtostderr -v=2 --raft "idx=6; group=2; snapshot-after-entries=100; snapshot-after-duration=1m" --security "whitelist=0.0.0.0/0;" jaeger: - image: jaegertracing/all-in-one:1.60 + image: docker.io/jaegertracing/all-in-one:1.60 environment: - SPAN_STORAGE_TYPE=badger ports: