mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-10-29 03:16:43 +08:00
Without a docker daemon six `act/runner` tests failed instead of passing. A dry run still created and removed job networks, the job teardown dropped the dry-run flag along with every other context value, and the container mocks asked the daemon for the runner arch. This also speeds up the tests and CI without dropping assertions. Most of the time went to git and docker round trips, which are now pinned, batched or coalesced, and the dind and e2e steps cache or overlap more of their setup. On CI, `act/runner` went from 77s to 55s and `test-dind` from 108s to 55s. Locally, `act/runner` without docker went from 53s to 7s. Split out of https://gitea.com/gitea/runner/pulls/1348, which adds Windows CI on top. The go cache changes moved to https://gitea.com/gitea/runner/pulls/1350. Written by Claude Code. --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1349 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <me@silverwind.io>
180 lines
8.2 KiB
Bash
Executable File
180 lines
8.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Generic docker-in-docker test harness.
|
|
#
|
|
# Builds a dind image variant from the repo Dockerfile, starts its docker daemon over a
|
|
# local TCP port, and runs a Go test command against that daemon via DOCKER_HOST. This
|
|
# validates the actual docker version and behaviour shipped in the dind image, so any
|
|
# daemon-level regression surfaces here (e.g. the "docker cp" break in gitea/runner#981).
|
|
# It is deliberately generic: point it at any package/test to exercise the dind daemon.
|
|
#
|
|
# Usage: scripts/test-dind.sh [target] [-- go-test-args...]
|
|
# target: dind (default), dind-rootless, or podman to run PODMAN_TEST_IMAGE's API service instead
|
|
# go-test-args: passed verbatim to `go test`. Defaults cover image platform detection,
|
|
# symlink copying, a mounted Docker job using cached images and the
|
|
# image's s6 supervision, or the Docker proxy probe for podman.
|
|
#
|
|
# Env:
|
|
# DIND_TEST_PORT host port for the daemon (default 32375)
|
|
# DIND_TEST_IMAGE skip the build and use this prebuilt image instead
|
|
# DIND_TEST_PRELOAD space-separated images to copy from the host daemon into the fresh one
|
|
set -euo pipefail
|
|
|
|
target="dind"
|
|
case "${1:-}" in
|
|
dind|dind-rootless|podman) target="$1"; shift ;;
|
|
esac
|
|
[ "${1:-}" = "--" ] && shift
|
|
default_tests=false
|
|
|
|
port="${DIND_TEST_PORT:-32375}"
|
|
name="gitea-runner-dind-test-$$"
|
|
# The host daemon endpoint, captured before DOCKER_HOST is pointed at the fresh dind daemon.
|
|
host_docker="${DOCKER_HOST:-$(docker context inspect --format '{{.Endpoints.docker.Host}}')}"
|
|
test_dir=""
|
|
|
|
cleanup() {
|
|
docker -H "$host_docker" rm -fv "$name" "$name-s6-failure" "$name-s6-stop" >/dev/null 2>&1 || true
|
|
if [ -n "$test_dir" ]; then
|
|
rm -rf "$test_dir"
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
if [ "$target" = podman ]; then
|
|
image="${PODMAN_TEST_IMAGE:?}"
|
|
daemon_args=("$image" podman system service --time=0 tcp://0.0.0.0:2375)
|
|
[ $# -gt 0 ] || set -- -count=1 -race -run '^TestDockerProxyWithDaemon$' ./act/container/
|
|
else
|
|
image="${DIND_TEST_IMAGE:-gitea-runner-${target}:dind-test}"
|
|
if [ -z "${DIND_TEST_IMAGE:-}" ]; then
|
|
echo "==> Building ${target} image"
|
|
docker build --target "$target" -t "$image" .
|
|
fi
|
|
# Override the image entrypoint (s6) and run only dockerd, exposed over insecure TCP.
|
|
# We are testing the daemon the image ships, not the runner supervision tree.
|
|
daemon_args=(-e DOCKER_TLS_CERTDIR= --entrypoint dockerd-entrypoint.sh "$image" --host=tcp://0.0.0.0:2375 --tls=false)
|
|
if [ $# -eq 0 ]; then
|
|
default_tests=true
|
|
set -- -count=1 -race -run '^TestImageExistsLocally$' ./act/container/
|
|
fi
|
|
fi
|
|
|
|
# How the test process reaches the daemon depends on where it runs:
|
|
# - plain host: publish 2375 on loopback and connect to 127.0.0.1.
|
|
# - inside a container (CI), the daemon is a sibling container, so its published port is on
|
|
# the host, not our loopback; instead attach it to our own network and reach it by name.
|
|
self_container=""
|
|
if [ -f /.dockerenv ]; then
|
|
self_container="$(cat /proc/sys/kernel/hostname 2>/dev/null || cat /etc/hostname)"
|
|
fi
|
|
self_network=""
|
|
if [ -n "$self_container" ]; then
|
|
self_network="$(docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{"\n"}}{{end}}' "$self_container" 2>/dev/null | head -1)"
|
|
fi
|
|
|
|
# The two cases differ only in how the daemon is exposed and addressed; everything else
|
|
# (privileged, name, daemon_args) is shared, so collect just the differing run args and the
|
|
# resulting DOCKER_HOST here.
|
|
if [ -n "$self_network" ]; then
|
|
echo "==> Starting ${target} daemon on network ${self_network} (reached as ${name}:2375)"
|
|
run_args=(--network "$self_network")
|
|
daemon_host="tcp://${name}:2375"
|
|
else
|
|
echo "==> Starting ${target} daemon on tcp://127.0.0.1:${port}"
|
|
run_args=(-p "127.0.0.1:${port}:2375")
|
|
daemon_host="tcp://127.0.0.1:${port}"
|
|
fi
|
|
# Create the dind container on the host daemon first, then repoint DOCKER_HOST at it: exporting
|
|
# DOCKER_HOST before `docker run` would make this `docker run` target the not-yet-existent dind.
|
|
docker -H "$host_docker" run -d --privileged --name "$name" "${run_args[@]}" "${daemon_args[@]}" >/dev/null
|
|
if [ "$default_tests" = true ]; then
|
|
echo "==> Starting ${target} supervision checks"
|
|
docker -H "$host_docker" run -d --privileged --name "$name-s6-failure" -e DOCKER_TLS_CERTDIR= -e RUNNER_STATE_FILE=/etc/hostname "$image" >/dev/null
|
|
docker -H "$host_docker" run -d --privileged --name "$name-s6-stop" -e DOCKER_TLS_CERTDIR= "$image" >/dev/null
|
|
fi
|
|
export DOCKER_HOST="$daemon_host"
|
|
|
|
echo "==> Waiting for daemon"
|
|
for _ in $(seq 1 60); do
|
|
docker version --format 'server docker {{.Server.Version}}' 2>/dev/null && break
|
|
sleep 1
|
|
done
|
|
if ! docker version --format 'server docker {{.Server.Version}}'; then
|
|
docker -H "$host_docker" logs "$name" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Seed the fresh daemon with images the host already has (the CI job pulls them in the
|
|
# preceding `make test`), so the daemon-facing tests run without registry access.
|
|
echo "==> Seeding daemon with cached host images"
|
|
preload="${DIND_TEST_PRELOAD:-alpine:latest}"
|
|
job_image="${ACT_TEST_IMAGE:-node:24-bookworm-slim}"
|
|
if [ "$default_tests" = true ]; then
|
|
preload="$preload $job_image"
|
|
fi
|
|
for img in $preload; do
|
|
if docker -H "$host_docker" image inspect "$img" >/dev/null 2>&1; then
|
|
docker -H "$host_docker" save "$img" | docker load >/dev/null 2>&1 && echo " loaded $img" || true
|
|
fi
|
|
done
|
|
|
|
echo "==> Running tests against ${target} daemon"
|
|
go test "$@"
|
|
|
|
if [ "$default_tests" = true ]; then
|
|
if ! docker image inspect "$job_image" >/dev/null 2>&1; then
|
|
echo "mounted Docker test requires ${job_image}, pull it on the host before running this harness" >&2
|
|
exit 1
|
|
fi
|
|
test_dir="$(mktemp -d)"
|
|
echo "==> Building mounted Docker test for the dind container"
|
|
CGO_ENABLED=0 GOOS=linux GOARCH="$(docker -H "$host_docker" image inspect "$image" --format '{{.Architecture}}')" \
|
|
go test -c -o "$test_dir/runner.test" ./act/runner/
|
|
docker -H "$host_docker" exec "$name" mkdir -p /tmp/gitea-runner-proxy-test/testdata
|
|
tar -C "$test_dir" -cf - runner.test -C "$PWD/act/runner" testdata/docker-proxy | \
|
|
docker -H "$host_docker" exec -i "$name" tar -x -C /tmp/gitea-runner-proxy-test
|
|
socket="unix:///var/run/docker.sock"
|
|
users=(0 1000:2375)
|
|
if [ "$target" = dind-rootless ]; then
|
|
socket="unix:///run/user/1000/docker.sock"
|
|
users=(1000 0)
|
|
fi
|
|
for user in "${users[@]}"; do
|
|
proxy_mode="proxy"
|
|
if [ "$user" = 1000:2375 ]; then
|
|
proxy_mode="direct"
|
|
fi
|
|
echo "==> Running mounted Docker job inside ${target} as UID ${user}, expecting ${proxy_mode} access"
|
|
docker -H "$host_docker" exec --user "$user" -w /tmp/gitea-runner-proxy-test \
|
|
-e DOCKER_HOST="$socket" -e ACT_TEST_DOCKER_PROXY="$proxy_mode" -e ACT_TEST_IMAGE="$job_image" \
|
|
"$name" ./runner.test -test.v -test.run '^TestDockerProxyMountedJob$' -test.timeout 3m
|
|
done
|
|
echo "==> Running mounted Docker job in a container given the ${target} socket, expecting proxy access"
|
|
docker -H "$host_docker" exec -e DOCKER_HOST="$socket" "$name" docker run --rm -v "${socket#unix://}:/var/run/docker.sock" -v /tmp/gitea-runner-proxy-test:/data -w /data \
|
|
-e ACT_TEST_DOCKER_PROXY=proxy -e ACT_TEST_IMAGE="$job_image" "$job_image" ./runner.test -test.v -test.run '^TestDockerProxyMountedJob$' -test.timeout 3m
|
|
|
|
supervised_exit_is() {
|
|
if [ "$(docker -H "$host_docker" inspect -f '{{.State.ExitCode}}' "$supervised")" != "$1" ] ||
|
|
! docker -H "$host_docker" logs "$supervised" 2>&1 | grep -F "$2" >/dev/null; then
|
|
docker -H "$host_docker" logs "$supervised" >&2
|
|
echo "${target} supervision: expected exit $1 after \"$2\"" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
echo "==> Checking that a runner failure sets the ${target} container exit code"
|
|
supervised="$name-s6-failure"
|
|
if ! timeout 120 docker -H "$host_docker" wait "$supervised" >/dev/null; then
|
|
docker -H "$host_docker" logs "$supervised" >&2
|
|
exit 1
|
|
fi
|
|
supervised_exit_is 1 'registration file not found'
|
|
echo "==> Checking that stopping a started ${target} runner exits 0"
|
|
supervised="$name-s6-stop"
|
|
for _ in $(seq 1 120); do
|
|
docker -H "$host_docker" logs "$supervised" 2>&1 | grep -F 'is missing or not a regular file' >/dev/null && break
|
|
sleep 1
|
|
done
|
|
docker -H "$host_docker" stop "$supervised" >/dev/null
|
|
supervised_exit_is 0 'is missing or not a regular file'
|
|
fi
|