From e081205d95bc8813b30cb47a7888f924336be08f Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Wed, 15 Jul 2026 22:19:15 +0300 Subject: [PATCH] fix(ci): run docker-dependent tests in the CI image via docker run --- .github/actions/install-risc0/action.yml | 10 -- .github/actions/run-in-ci-image/action.yml | 54 +++++++++ .github/docker/ci.Dockerfile | 12 +- .github/workflows/ci.yml | 131 +++++++++++++-------- 4 files changed, 148 insertions(+), 59 deletions(-) delete mode 100644 .github/actions/install-risc0/action.yml create mode 100644 .github/actions/run-in-ci-image/action.yml diff --git a/.github/actions/install-risc0/action.yml b/.github/actions/install-risc0/action.yml deleted file mode 100644 index fef3a467..00000000 --- a/.github/actions/install-risc0/action.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Install risc0 -description: Installs risc0 in the environment -runs: - using: "composite" - steps: - - name: Install risc0 - run: | - curl -L https://risczero.com/install | bash - /home/runner/.risc0/bin/rzup install - shell: bash diff --git a/.github/actions/run-in-ci-image/action.yml b/.github/actions/run-in-ci-image/action.yml new file mode 100644 index 00000000..8d994ed6 --- /dev/null +++ b/.github/actions/run-in-ci-image/action.yml @@ -0,0 +1,54 @@ +name: Run in CI image +description: > + Runs a command inside the CI image on a plain runner, for jobs that need the + host's Docker daemon. `container:` cannot work for those: it puts the + workspace on a path the host daemon cannot resolve, and it forbids + `--network host`. + +inputs: + image: + description: CI image reference, from the ci-image workflow's output. + required: true + run: + description: Command to run inside the image. + required: true + env: + description: Newline-separated NAME=VALUE pairs to pass into the container. + required: false + default: "" + +runs: + using: "composite" + steps: + - name: Run in CI image + shell: bash + env: + INPUT_IMAGE: ${{ inputs.image }} + INPUT_RUN: ${{ inputs.run }} + INPUT_ENV: ${{ inputs.env }} + run: | + set -euo pipefail + + env_args=() + while IFS= read -r pair; do + [[ -z "$pair" ]] && continue + env_args+=(--env "$pair") + done <<< "$INPUT_ENV" + + # Cargo's registry is the only part of CARGO_HOME worth sharing with the + # host: mounting all of it would shadow the tools baked into the image. + mkdir -p "$HOME/.cargo/registry" + + # Mounting the workspace on its own path is what makes this work. The + # daemon resolves compose bind mounts against the host, and the test + # binaries bake CARGO_MANIFEST_DIR in at compile time, so the path has + # to mean the same thing inside and outside the container. + docker run --rm \ + --network host \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + --volume "$PWD:$PWD" \ + --volume "$HOME/.cargo/registry:/usr/local/cargo/registry" \ + --workdir "$PWD" \ + "${env_args[@]}" \ + "$INPUT_IMAGE" \ + bash -o pipefail -c "$INPUT_RUN" diff --git a/.github/docker/ci.Dockerfile b/.github/docker/ci.Dockerfile index 303c0328..0557bd12 100644 --- a/.github/docker/ci.Dockerfile +++ b/.github/docker/ci.Dockerfile @@ -47,6 +47,13 @@ RUN curl -L https://risczero.com/install | bash \ && rm -rf /root/.risc0 \ && rzup install +# Copying docker. Static Go binaries, so the alpine-built ones run fine here. +COPY --from=docker:29-cli /usr/local/bin/docker /usr/local/bin/docker +COPY --from=docker:29-cli /usr/local/libexec/docker/cli-plugins/docker-compose \ + /usr/local/libexec/docker/cli-plugins/docker-compose +COPY --from=docker:29-cli /usr/local/libexec/docker/cli-plugins/docker-buildx \ + /usr/local/libexec/docker/cli-plugins/docker-buildx + # Prebuilt binaries; compiling these from source would dominate the image build. RUN curl -L --proto '=https' --tlsv1.2 -sSf \ https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash \ @@ -70,4 +77,7 @@ RUN cargo --version \ && taplo --version \ && cargo machete --version \ && cargo deny --version \ - && just --version + && just --version \ + && docker --version \ + && docker compose version \ + && docker buildx version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dba7722..ff9b4a8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,20 +150,24 @@ jobs: RUST_LOG: "info" run: cargo nextest run --workspace --exclude integration_tests --exclude test_fixtures --all-features + # Not a `container:` job: the tests drive the host's Docker daemon through + # testcontainers, so they run in the CI image via `run-in-ci-image` instead. test-fixtures-tests: needs: ci-image runs-on: ubuntu-latest - container: - image: ${{ needs.ci-image.outputs.image }} - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 60 steps: - uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha || github.head_ref }} + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Restore Rust cache uses: Swatinem/rust-cache@v2 with: @@ -171,19 +175,20 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - name: Run test_fixtures tests - env: - RISC0_DEV_MODE: "1" - RUST_LOG: "info" - run: cargo nextest run -p test_fixtures + uses: ./.github/actions/run-in-ci-image + with: + image: ${{ needs.ci-image.outputs.image }} + env: | + RISC0_DEV_MODE=1 + RUST_LOG=info + run: cargo nextest run -p test_fixtures + # Not a `container:` job: the test binaries bake CARGO_MANIFEST_DIR in at + # compile time, so the archive has to be built on the same path the matrix + # jobs later run it from. integration-tests-prebuild: needs: ci-image runs-on: ubuntu-latest - container: - image: ${{ needs.ci-image.outputs.image }} - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} outputs: targets: ${{ steps.discover-targets.outputs.targets }} steps: @@ -191,6 +196,13 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.head_ref }} + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Restore Rust cache uses: Swatinem/rust-cache@v2 with: @@ -198,9 +210,11 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - name: Build integration test archive - env: - RISC0_DEV_MODE: "1" - run: cargo nextest archive -p integration_tests --archive-file integration-tests.tar.zst --no-pager + uses: ./.github/actions/run-in-ci-image + with: + image: ${{ needs.ci-image.outputs.image }} + env: RISC0_DEV_MODE=1 + run: cargo nextest archive -p integration_tests --archive-file integration-tests.tar.zst --no-pager - name: Upload integration test archive uses: actions/upload-artifact@v4 @@ -208,15 +222,22 @@ jobs: name: integration-tests-archive path: integration-tests.tar.zst + - name: List integration test binaries + uses: ./.github/actions/run-in-ci-image + with: + image: ${{ needs.ci-image.outputs.image }} + run: | + cargo nextest list \ + --archive-file integration-tests.tar.zst \ + --list-type binaries-only \ + --message-format json \ + --no-pager > integration-tests-binaries.json + + # $GITHUB_OUTPUT is outside the mounted workspace, so the container cannot + # write to it. jq is on the runner, so do this pass here. - name: Discover integration test targets from archive id: discover-targets run: | - cargo nextest list \ - --archive-file integration-tests.tar.zst \ - --list-type binaries-only \ - --message-format json \ - --no-pager > integration-tests-binaries.json - targets_json="$(jq -c '[."rust-binaries" | to_entries[] | select(.value.kind == "test" and .value."binary-name" != "tps") | .value."binary-name"] | sort | unique' integration-tests-binaries.json)" if [[ "$targets_json" == "[]" ]]; then @@ -230,11 +251,6 @@ jobs: integration-tests: needs: [ci-image, test-fixtures-tests, integration-tests-prebuild] runs-on: ubuntu-latest - container: - image: ${{ needs.ci-image.outputs.image }} - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 90 strategy: fail-fast: false @@ -246,31 +262,43 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.head_ref }} + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Download integration test archive uses: actions/download-artifact@v4 with: name: integration-tests-archive - name: Run tests - env: - RISC0_DEV_MODE: "1" - RUST_LOG: "info" - run: cargo nextest run --archive-file integration-tests.tar.zst -E "binary(${{ matrix.target }})" + uses: ./.github/actions/run-in-ci-image + with: + image: ${{ needs.ci-image.outputs.image }} + env: | + RISC0_DEV_MODE=1 + RUST_LOG=info + run: cargo nextest run --archive-file integration-tests.tar.zst -E "binary(${{ matrix.target }})" valid-proof-test: needs: ci-image runs-on: ubuntu-latest - container: - image: ${{ needs.ci-image.outputs.image }} - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} timeout-minutes: 90 steps: - uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.head.sha || github.head_ref }} + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Restore Rust cache uses: Swatinem/rust-cache@v2 with: @@ -278,14 +306,16 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - name: Test valid proof - env: - RUST_LOG: "info" - run: cargo test -p integration_tests -- --exact private::private_transfer_to_owned_account + uses: ./.github/actions/run-in-ci-image + with: + image: ${{ needs.ci-image.outputs.image }} + env: RUST_LOG=info + run: cargo test -p integration_tests -- --exact private::private_transfer_to_owned_account - # Not containerised: `just build-artifacts` shells out to `cargo risczero - # build`, which runs the guest build in Docker and bind-mounts the workspace. - # From inside a container job the workspace path wouldn't match the host's. + # `just build-artifacts` drives the host's Docker daemon via `cargo risczero + # build`, so it goes through the image rather than `container:`. artifacts: + needs: ci-image runs-on: ubuntu-latest timeout-minutes: 60 @@ -295,7 +325,12 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha || github.head_ref }} - - uses: ./.github/actions/install-risc0 + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Restore Rust cache uses: Swatinem/rust-cache@v2 @@ -303,11 +338,11 @@ jobs: shared-key: ci-rust-cache save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - - name: Install just - run: cargo install --locked just - - name: Build artifacts - run: just build-artifacts + uses: ./.github/actions/run-in-ci-image + with: + image: ${{ needs.ci-image.outputs.image }} + run: just build-artifacts - name: Check if artifacts match repository run: |