diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index d98ad63c6..e9ab3f434 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -2,7 +2,7 @@ name: Rust CI on: push: - branches: [main] + branches: [build/kvm-ci-runner] # Documentation-only edits do not affect the Rust artifact graph; skipping # avoids spinning ~30min nextest lanes and cancelling in-flight runs via # extra pushes (see paths-ignore semantics for bundled PR diffs). @@ -41,9 +41,20 @@ env: OTEL_EXPORTER_OTLP_ENDPOINT: "" jobs: + # Wakes the self-hosted GCP runner (auto-stops after idle). All kvm-gcp + # jobs gate on this transitively via `changes`. + # id-token: write is required for the reusable workflow's WIF auth; it + # has to be granted at the caller side too (intersection semantics). + start-runner: + permissions: + id-token: write + contents: read + uses: ./.github/workflows/start-kvm-runner.yml + changes: name: Detect sandbox lane changes - runs-on: arc-runner-set + runs-on: kvm-gcp + needs: start-runner outputs: sandbox_changed: ${{ steps.filter.outputs.sandbox }} steps: @@ -66,7 +77,7 @@ jobs: doctor: name: Workspace integrity (doctor) - runs-on: arc-runner-set + runs-on: kvm-gcp needs: changes steps: - uses: actions/checkout@v4 @@ -104,7 +115,7 @@ jobs: nextest: name: Nextest (workspace) - runs-on: arc-runner-set + runs-on: kvm-gcp needs: changes steps: - uses: actions/checkout@v4 @@ -312,7 +323,7 @@ jobs: sandbox-adapter: name: Sandbox adapter (stdio E2E + protocol) - runs-on: arc-runner-set + runs-on: kvm-gcp needs: [changes, nextest] if: needs.changes.outputs.sandbox_changed == 'true' steps: @@ -386,7 +397,7 @@ jobs: sandbox-adapter-image: name: Sandbox adapter image (no-publish) - runs-on: arc-runner-set + runs-on: kvm-gcp needs: [changes, nextest] if: needs.changes.outputs.sandbox_changed == 'true' env: @@ -575,7 +586,7 @@ jobs: sandbox-adapter-image-handoff: name: Sandbox image metadata handoff validation - runs-on: arc-runner-set + runs-on: kvm-gcp needs: [changes, nextest, sandbox-adapter-image] if: needs.changes.outputs.sandbox_changed == 'true' steps: diff --git a/.github/workflows/sandbox-e2e-kvm.yml b/.github/workflows/sandbox-e2e-kvm.yml index 9dcbecf66..df4b2db01 100644 --- a/.github/workflows/sandbox-e2e-kvm.yml +++ b/.github/workflows/sandbox-e2e-kvm.yml @@ -18,9 +18,16 @@ env: ROOTFS_TAR: sandbox-echo-rootfs.tar jobs: + start-runner: + permissions: + id-token: write + contents: read + uses: ./.github/workflows/start-kvm-runner.yml + prepare-fixture: name: Prepare local sandbox fixture bind rootfs artifact - runs-on: kvm-enabled + runs-on: kvm-gcp + needs: start-runner steps: - uses: actions/checkout@v4 @@ -127,7 +134,7 @@ jobs: kvm-e2e: name: KVM E2E (baml-rt-tools) needs: prepare-fixture - runs-on: kvm-enabled + runs-on: kvm-gcp steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/start-kvm-runner.yml b/.github/workflows/start-kvm-runner.yml new file mode 100644 index 000000000..3f2e7f306 --- /dev/null +++ b/.github/workflows/start-kvm-runner.yml @@ -0,0 +1,85 @@ +name: Start KVM Runner + +# Reusable workflow that wakes the self-hosted GCP runner (auto-stopped after +# 20m idle) before jobs that need `runs-on: kvm-gcp`. Authenticates to GCP via +# Workload Identity Federation — no long-lived keys. +# +# Repo-level vars consumed (set via `gh variable set`): +# GCP_WIF_PROVIDER e.g. projects/123456789/locations/global/workloadIdentityPools/github/providers/github-oidc +# GCP_RUNNER_SA e.g. ci-agentium-os-ci-1@agentium-os.iam.gserviceaccount.com + +on: + workflow_call: + inputs: + instance: + description: GCE instance name + type: string + default: agentium-os-ci-1 + zone: + description: GCE zone + type: string + default: us-central1-a + project: + description: GCP project ID + type: string + default: agentium-os + wait_seconds: + description: Seconds to wait after `start` returns, so the actions-runner service has time to register with GitHub. + type: number + default: 60 + workflow_dispatch: + inputs: + instance: + type: string + default: agentium-os-ci-1 + zone: + type: string + default: us-central1-a + project: + type: string + default: agentium-os + +permissions: + id-token: write + contents: read + +# Two concurrent CI runs both call this — the second blocks on the first +# rather than racing the gcloud start API. +concurrency: + group: start-kvm-runner-${{ inputs.instance }} + cancel-in-progress: false + +jobs: + start: + name: Start ${{ inputs.instance }} + runs-on: ubuntu-latest + steps: + - name: Authenticate to GCP + uses: google-github-actions/auth@v2 + with: + workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }} + service_account: ${{ vars.GCP_RUNNER_SA }} + + - name: Set up gcloud + uses: google-github-actions/setup-gcloud@v2 + + - name: Start instance if not running + run: | + set -euo pipefail + status=$(gcloud compute instances describe "${{ inputs.instance }}" \ + --zone="${{ inputs.zone }}" --project="${{ inputs.project }}" \ + --format='value(status)') + echo "current status: $status" + if [ "$status" = "RUNNING" ]; then + echo "instance already running, nothing to do" + exit 0 + fi + gcloud compute instances start "${{ inputs.instance }}" \ + --zone="${{ inputs.zone }}" --project="${{ inputs.project }}" + + - name: Wait for actions-runner to register + # Cold-boot + idempotent startup-script + runner service ≈ 60s on + # resume. Listing self-hosted runners would need admin perms on the + # repo; instead we sleep and let GitHub queue dependent jobs until + # the runner re-registers itself. + run: sleep ${{ inputs.wait_seconds }}