blob: 352491fbb8e47f9a8bcf01c896aa70a91c3161e1 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
name: "Start and Prepare DinD"
description: "Launch, verify, and prepare a Docker-in-Docker environment."
inputs:
# --- Core DinD Config ---
container-name:
description: "Name for the DinD container."
default: dind-daemon
bind-address:
default: 127.0.0.1
port:
default: "2375"
storage-volume:
default: dind-storage
execroot-volume:
default: dind-execroot
ephemeral-volumes:
description: "Generate unique per-run volume names (recommended)."
default: "true"
auto-prune-dangling:
description: "Prune dangling ephemeral DinD volumes from previous runs."
default: "true"
tmpfs-run-size:
default: 1g
tmpfs-varrun-size:
default: 1g
storage-driver:
default: overlay2
additional-dockerd-args:
default: "--tls=false"
use-host-network:
description: "Run DinD with --network host instead of publishing a TCP port."
default: "false"
# --- Health & Wait Config ---
health-interval:
default: 2s
health-retries:
default: "60"
health-start-period:
default: 10s
wait-timeout:
default: "180"
dind-image:
description: "DinD image. Use a fixed version tag to avoid issues."
default: "docker:27-dind"
# --- NEW: Optional Setup & Verification Steps ---
cleanup-dind-on-start:
description: "Run 'docker system prune' inside DinD immediately after it starts."
default: "true"
smoke-test-port-mapping:
description: "Run a quick test to ensure port mapping from DinD is working."
default: "true"
prime-testcontainers:
description: "Start and stop a small container via the testcontainers library to prime Ryuk."
default: "false"
# --- Output Config ---
export-gh-env:
description: "Also write DOCKER_HOST and DIND_IP to $GITHUB_ENV for the rest of the job."
default: "false"
outputs:
docker-host:
description: "The TCP address for the DinD daemon (e.g., tcp://127.0.0.1:2375)."
value: ${{ steps.set-output.outputs.docker-host }}
dind-ip:
description: "The discovered bridge IP address of the DinD container."
value: ${{ steps.discover-ip.outputs.dind-ip }}
container-name:
description: "The name of the running DinD container."
value: ${{ inputs.container-name || 'dind-daemon' }}
storage-volume:
value: ${{ steps.set-output.outputs.storage_volume }}
execroot-volume:
value: ${{ steps.set-output.outputs.execroot_volume }}
runs:
using: "composite"
steps:
- name: Prune old dangling ephemeral DinD volumes
if: ${{ inputs.auto-prune-dangling == 'true' }}
shell: bash
run: |
docker volume ls -q \
--filter "label=com.github.dind=1" \
--filter "label=com.github.repo=${GITHUB_REPOSITORY}" \
--filter "dangling=true" | xargs -r docker volume rm || true
- name: Start docker:dind
shell: bash
run: |
# (Your original 'Start docker:dind' script is perfect here - no changes needed)
set -euo pipefail
NAME="${{ inputs.container-name || 'dind-daemon' }}"
BIND="${{ inputs.bind-address || '127.0.0.1' }}"
PORT="${{ inputs.port || '2375' }}"
SD="${{ inputs.storage-driver || 'overlay2' }}"
TRS="${{ inputs.tmpfs-run-size || '1g' }}"
TVRS="${{ inputs.tmpfs-varrun-size || '1g' }}"
HI="${{ inputs.health-interval || '2s' }}"
HR="${{ inputs.health-retries || '60' }}"
HSP="${{ inputs.health-start-period || '10s' }}"
EXTRA="${{ inputs.additional-dockerd-args }}"
USE_HOST_NET="${{ inputs.use-host-network || 'false' }}"
if [[ "${{ inputs.ephemeral-volumes }}" == "true" ]]; then
SUFFIX="${GITHUB_RUN_ID:-0}-${GITHUB_RUN_ATTEMPT:-0}-${GITHUB_JOB:-job}"
STORAGE_VOL="dind-storage-${SUFFIX}"
EXECROOT_VOL="dind-execroot-${SUFFIX}"
else
STORAGE_VOL="${{ inputs.storage-volume || 'dind-storage' }}"
EXECROOT_VOL="${{ inputs.execroot-volume || 'dind-execroot' }}"
fi
docker volume create --name "${STORAGE_VOL}" --label "com.github.dind=1" --label "com.github.repo=${GITHUB_REPOSITORY}" >/dev/null
docker volume create --name "${EXECROOT_VOL}" --label "com.github.dind=1" --label "com.github.repo=${GITHUB_REPOSITORY}" >/dev/null
# Clean up any existing DinD containers
docker ps -a -q --filter "label=com.github.dind=1" | xargs -r docker rm -f -v 2>/dev/null || true
docker rm -f -v "$NAME" 2>/dev/null || true
sleep 2
NET_ARGS=""
PUBLISH_ARGS="-p ${BIND}:${PORT}:${PORT}"
if [[ "${USE_HOST_NET}" == "true" ]]; then
NET_ARGS="--network host"
PUBLISH_ARGS=""
fi
IMAGE="${{ inputs.dind-image || 'docker:27-dind' }}"
docker run -d --privileged --name "$NAME" \
--cgroupns=host \
-e DOCKER_TLS_CERTDIR= \
${NET_ARGS} \
${PUBLISH_ARGS} \
-v "${STORAGE_VOL}:/var/lib/docker" \
-v "${EXECROOT_VOL}:/execroot" \
--tmpfs /run:rw,exec,size=${TRS} \
--tmpfs /var/run:rw,exec,size=${TVRS} \
--label "com.github.dind=1" \
--health-cmd='docker info > /dev/null' \
--health-interval=${HI} \
--health-retries=${HR} \
--health-start-period=${HSP} \
"${IMAGE}" \
--host=tcp://0.0.0.0:${PORT} \
--host=unix:///var/run/docker.sock \
--storage-driver=${SD} \
--iptables=false \
--exec-root=/execroot ${EXTRA}
{
echo "STORAGE_VOL=${STORAGE_VOL}"
echo "EXECROOT_VOL=${EXECROOT_VOL}"
} >> "$GITHUB_ENV"
- name: Wait for DinD daemon
shell: bash
run: |
set -euo pipefail
NAME="${{ inputs.container-name || 'dind-daemon' }}"
HOST="${{ inputs.bind-address || '127.0.0.1' }}"
PORT="${{ inputs.port || '2375' }}"
TIMEOUT="${{ inputs.wait-timeout || '180' }}"
echo "Waiting for Docker-in-Docker to be ready..."
if ! timeout ${TIMEOUT}s bash -c 'until docker -H "tcp://'"${HOST}"':'"${PORT}"'" info >/dev/null 2>&1; do sleep 2; done'; then
echo "::error::DinD failed to start within ${TIMEOUT}s."
docker logs "$NAME" || true
exit 1
fi
echo "DinD is ready."
docker -H "tcp://${HOST}:${PORT}" info --format 'Daemon OK → OS={{.OperatingSystem}} Version={{.ServerVersion}}'
- id: set-output
shell: bash
run: |
HOST="${{ inputs.bind-address || '127.0.0.1' }}"
PORT="${{ inputs.port || '2375' }}"
echo "docker-host=tcp://${HOST}:${PORT}" >> "$GITHUB_OUTPUT"
echo "storage_volume=${STORAGE_VOL:-}" >> "$GITHUB_OUTPUT"
echo "execroot_volume=${EXECROOT_VOL:-}" >> "$GITHUB_OUTPUT"
# --- NEW: Integrated Setup & Verification Steps ---
- name: Cleanup DinD Environment
if: ${{ inputs.cleanup-dind-on-start == 'true' }}
shell: bash
run: |
echo "Performing initial cleanup of DinD environment..."
DIND_HOST="${{ steps.set-output.outputs.docker-host }}"
docker -H "${DIND_HOST}" system prune -af --volumes
docker -H "${DIND_HOST}" image prune -af
- id: discover-ip
name: Discover DinD Container IP
shell: bash
run: |
set -euo pipefail
NAME="${{ inputs.container-name || 'dind-daemon' }}"
# Use host daemon to inspect the DinD container
nm=$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$NAME")
echo "DinD NetworkMode=${nm}"
# Try to find the bridge network IP
ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$NAME" || true)
# If still empty, likely host networking -> use loopback
if [[ -z "${ip}" || "${nm}" == "host" ]]; then
echo "No bridge IP found or using host network. Falling back to 127.0.0.1."
ip="127.0.0.1"
fi
echo "Discovered DinD IP: ${ip}"
echo "dind-ip=${ip}" >> "$GITHUB_OUTPUT"
- name: Smoke Test Port Mapping
if: ${{ inputs.smoke-test-port-mapping == 'true' }}
env:
DOCKER_HOST: ${{ steps.set-output.outputs.docker-host }}
DIND_IP: ${{ steps.discover-ip.outputs.dind-ip }}
shell: bash
run: |
set -euo pipefail
echo "Running port mapping smoke test..."
docker pull redis:7.2-alpine
cid=$(docker run -d -p 0:6379 --name redis-smoke redis:7-alpine)
hostport=$(docker port redis-smoke 6379/tcp | sed 's/.*://')
echo "Redis container started, mapped to host port ${hostport}"
echo "Probing connection to ${DIND_IP}:${hostport} ..."
timeout 5 bash -c 'exec 3<>/dev/tcp/$DIND_IP/'"$hostport"
if [[ $? -eq 0 ]]; then
echo "TCP connection successful. Port mapping is working."
else
echo "::error::Failed to connect to mapped port on ${DIND_IP}:${hostport}"
docker logs redis-smoke
exit 1
fi
docker rm -f "$cid"
- name: Prime Testcontainers (Ryuk)
if: ${{ inputs.prime-testcontainers == 'true' }}
env:
DOCKER_HOST: ${{ steps.set-output.outputs.docker-host }}
TESTCONTAINERS_HOST_OVERRIDE: ${{ steps.discover-ip.outputs.dind-ip }}
shell: bash
run: |
echo "Priming Testcontainers/Ryuk..."
python -m pip install -q --upgrade pip testcontainers
# Use a tiny image for a fast and stable prime
docker pull alpine:3.19
python - <<'PY'
from testcontainers.core.container import DockerContainer
c = DockerContainer("alpine:3.19").with_command("true")
c.start()
c.stop()
print("Ryuk primed and ready.")
PY
- name: Export Environment Variables
if: ${{ inputs.export-gh-env == 'true' }}
shell: bash
run: |
echo "DOCKER_HOST=${{ steps.set-output.outputs.docker-host }}" >> "$GITHUB_ENV"
echo "DIND_IP=${{ steps.discover-ip.outputs.dind-ip }}" >> "$GITHUB_ENV"