From 2e2a4b708b24f09b7297caa6da9ff1b8fcdb26ce Mon Sep 17 00:00:00 2001 From: Karib0u Date: Thu, 15 Oct 2026 16:24:19 +0200 Subject: [PATCH] ci: shorten the PR critical path - Move the Linux atomics into their own job. The Windows shards need no eBPF object, so they no longer wait ~37s on ebpf-build, and the Atomic (windows) roll-up now covers only the Windows shards. - Split the Windows atomics across three runners instead of two. - Give each ebpf-load leg its own cache. With one shared key the quicker 5.10 leg saved first, so the runner leg's debug build for the live eBPF tests was never cached and spent ~83s compiling before 49s of tests. --- .github/workflows/ci.yml | 164 ++++++++++++++++++++++++++------------- 1 file changed, 112 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5418669a..44315fed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,6 +142,10 @@ jobs: with: add-rust-environment-hash-key: false save-if: ${{ github.ref == 'refs/heads/main' }} + # One cache per leg: they build different things, and a shared key + # let the quicker 5.10 leg save first, so the runner leg's debug + # build for the live tests was never cached. + key: ${{ matrix.kernel }} - name: Download eBPF artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -390,26 +394,11 @@ jobs: test_binary="$(cargo test --locked --lib --no-run --message-format=json | jq -r 'select(.reason == "compiler-artifact" and .target.kind == ["lib"] and .executable != null) | .executable')" sudo "$test_binary" integration_group_ --ignored --test-threads=1 - atomic: - name: ${{ matrix.shard == '1/1' && format('Atomic ({0})', matrix.platform) || format('Atomic ({0} shard {1})', matrix.platform, matrix.shard) }} + atomic-linux: + name: Atomic (linux) if: ${{ !inputs.skip_atomics }} - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest needs: ebpf-build - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - platform: linux - shard: 1/1 - # Windows runs its ~75 atomics sequentially and is the long pole, so - # it is split across two runners. - - os: windows-latest - platform: windows - shard: 1/2 - - os: windows-latest - platform: windows - shard: 2/2 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: @@ -426,23 +415,14 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' }} - name: Download eBPF artifact - if: matrix.platform == 'linux' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: rustinel-ebpf-object path: ebpf/ - # Each shard builds its own binary. With a warm cache this is quicker - # than a shared build job plus an artifact hop, and the Linux leg does - # not wait on the Windows build. - name: Build PR binary run: cargo build --locked --release - - name: Run native capture contract (Windows) - if: matrix.platform == 'windows' && matrix.shard == '1/2' - run: python tests\native_capture_contract.py --binary "$env:GITHUB_WORKSPACE\target\release\rustinel.exe" - shell: pwsh - - name: Read shared atomic rules pin id: rules-pin shell: bash @@ -479,13 +459,7 @@ jobs: working-directory: rustinel-rules run: python tests/atomic/run_atomics.py --check-coverage --strict-essential - - name: Exclude workspace from Defender (Windows, for EICAR) - if: matrix.platform == 'windows' - run: Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction SilentlyContinue - shell: pwsh - - name: Run atomic tests (Linux) - if: matrix.platform == 'linux' working-directory: rustinel-rules run: | # The hosted runner leaves /opt writable by other accounts. @@ -497,12 +471,113 @@ jobs: --engine-bin "$GITHUB_WORKSPACE/scripts/atomic_managed_service.sh" \ --engine-dir /opt/rustinel \ --rules-dir "$GITHUB_WORKSPACE/rustinel-rules" \ - --pack auto \ - --shard "${{ matrix.shard }}" + --pack auto shell: bash + - name: Prepare atomic logs for upload (Unix) + if: always() + run: | + if [ -d /opt/rustinel/logs ]; then + sudo cp -a /opt/rustinel/logs rustinel-rules/tests/atomic/managed-logs + sudo chown -R "$(id -u):$(id -g)" rustinel-rules/tests/atomic/managed-logs + fi + if [ -f /opt/rustinel/engine.stdout.log ]; then + sudo cp /opt/rustinel/engine.stdout.log rustinel-rules/tests/atomic/engine.stdout.log + sudo chown "$(id -u):$(id -g)" rustinel-rules/tests/atomic/engine.stdout.log + fi + shell: bash + + - name: Upload atomic report and logs + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: atomic-linux + path: | + rustinel-rules/tests/atomic/report-*.json + rustinel-rules/tests/atomic/.engine/engine.stdout.log + rustinel-rules/tests/atomic/.engine/logs/ + rustinel-rules/tests/atomic/engine.stdout.log + rustinel-rules/tests/atomic/managed-logs/ + include-hidden-files: true + if-no-files-found: warn + + # Windows needs no eBPF object, so it starts at once instead of waiting on + # ebpf-build. Its ~75 atomics run sequentially and are the long pole, so + # they are split across three runners. + atomic: + name: Atomic (windows shard ${{ matrix.shard }}) + if: ${{ !inputs.skip_atomics }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + shard: [1/3, 2/3, 3/3] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - uses: actions-rust-lang/setup-rust-toolchain@ecabd13d1c56bd1345c230e542e9144811ad706f # v2.0.0 + with: + cache: false + rustflags: "" + + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + add-rust-environment-hash-key: false + save-if: ${{ github.ref == 'refs/heads/main' }} + + # Each shard builds its own binary: with a warm cache that beats a + # shared build job plus an artifact hop. + - name: Build PR binary + run: cargo build --locked --release + + - name: Run native capture contract (Windows) + if: matrix.shard == '1/3' + run: python tests\native_capture_contract.py --binary "$env:GITHUB_WORKSPACE\target\release\rustinel.exe" + shell: pwsh + + - name: Read shared atomic rules pin + id: rules-pin + shell: bash + run: | + ref="$(cat .github/atomic-rules-ref)" + if [[ ! "$ref" =~ ^[0-9a-f]{40}$ ]]; then + echo "Expected a full commit SHA in .github/atomic-rules-ref" >&2 + exit 1 + fi + echo "ref=$ref" >> "$GITHUB_OUTPUT" + + - name: Check out pinned rules + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + repository: Karib0u/rustinel-rules + ref: ${{ steps.rules-pin.outputs.ref }} + path: rustinel-rules + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + with: + enable-cache: false + + - name: Build pinned rules pack + working-directory: rustinel-rules + run: | + uv sync --frozen + # Validate a rules commit, not the enclosing engine release tag. + uv run python -c "import os, subprocess, sys; os.environ.pop('GITHUB_REF_TYPE', None); sys.exit(subprocess.call([sys.executable, 'tools/validate.py']))" + uv run python tools/build_packs.py + + - name: Check atomic coverage metadata + working-directory: rustinel-rules + run: python tests/atomic/run_atomics.py --check-coverage --strict-essential + + - name: Exclude workspace from Defender (Windows, for EICAR) + run: Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction SilentlyContinue + shell: pwsh + - name: Run atomic tests (Windows) - if: matrix.platform == 'windows' working-directory: rustinel-rules run: | # The runner's D:\a grants Users write, and the agent refuses inputs @@ -527,30 +602,15 @@ jobs: --shard "${{ matrix.shard }}" shell: pwsh - - name: Prepare atomic logs for upload (Unix) - if: always() && matrix.platform != 'windows' - run: | - if [ -d /opt/rustinel/logs ]; then - sudo cp -a /opt/rustinel/logs rustinel-rules/tests/atomic/managed-logs - sudo chown -R "$(id -u):$(id -g)" rustinel-rules/tests/atomic/managed-logs - fi - if [ -f /opt/rustinel/engine.stdout.log ]; then - sudo cp /opt/rustinel/engine.stdout.log rustinel-rules/tests/atomic/engine.stdout.log - sudo chown "$(id -u):$(id -g)" rustinel-rules/tests/atomic/engine.stdout.log - fi - shell: bash - - name: Upload atomic report and logs if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: atomic-${{ matrix.platform }}-${{ strategy.job-index }} + name: atomic-windows-${{ strategy.job-index }} path: | rustinel-rules/tests/atomic/report-*.json rustinel-rules/tests/atomic/.engine/engine.stdout.log rustinel-rules/tests/atomic/.engine/logs/ - rustinel-rules/tests/atomic/engine.stdout.log - rustinel-rules/tests/atomic/managed-logs/ include-hidden-files: true if-no-files-found: warn