ci: verify the client's wire shapes against the committed openapi.json #31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main, dev] | |
| pull_request: | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt, clippy | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: fmt | |
| run: cargo fmt --all --check | |
| - name: clippy | |
| run: cargo clippy --all-targets -- -D warnings | |
| - name: build | |
| run: cargo build --locked --verbose | |
| # Includes tests/openapi_conformance.rs, the SHAPE gate: it loads the committed openapi.json | |
| # and checks every serde type in src/client.rs against its schema (field set, required vs | |
| # nullable, round trip, endpoint wiring). The spec-drift job below only compares a version | |
| # STRING, so this is the check that fails when a struct drifts from the spec in the same | |
| # commit. Keep `cargo test` in this workflow: it is the only thing running that gate. | |
| - name: test | |
| run: cargo test --locked --verbose | |
| integration: | |
| # BIDIRECTIONAL TESTING (matches how the plugin repos test themselves against busbar): download | |
| # the latest RELEASED busbar, boot it, and drive EVERY busbar-admin command against it, asserting | |
| # real effects. Catches client drift a `cargo build` never would — a renamed field, a changed | |
| # status code, a broken auth header. (busbar core's own dev-gate runs the SAME scripts/ | |
| # integration.sh against the freshly-built engine, the reverse direction.) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Build busbar-admin | |
| run: cargo build --release --locked | |
| - name: Download the latest released busbar (linux x86_64) | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| gh release download --repo GetBusbar/busbar \ | |
| --pattern 'busbar-x86_64-unknown-linux-gnu.tar.gz' --dir /tmp/busbar-dl | |
| tar -xzf /tmp/busbar-dl/busbar-x86_64-unknown-linux-gnu.tar.gz -C /tmp/busbar-dl | |
| chmod +x /tmp/busbar-dl/busbar | |
| /tmp/busbar-dl/busbar --version | |
| - name: Drive every command against the real gateway | |
| run: scripts/integration.sh /tmp/busbar-dl/busbar target/release/busbar-admin | |
| spec-mirror: | |
| # THE SHAPE OF THE SPEC ITSELF, which nothing else in this repo checks. | |
| # | |
| # Three questions, three different gates, and it is worth being explicit about which is which: | |
| # 1. "Does src/client.rs match the committed openapi.json?" -> tests/openapi_conformance.rs | |
| # (in the build job). Rust vs its OWN copy of the spec. | |
| # 2. "Is the committed spec's VERSION STRING current?" -> the spec-drift job below. | |
| # 3. "Is the committed spec the same DOCUMENT as core's?" -> this job. Nothing answered it. | |
| # | |
| # Question 2 is not a substitute for question 3, and assuming it was is what shipped: a mirror | |
| # can carry core's exact info.version and still be missing properties, because the version | |
| # string is copied along with everything else and says nothing about what was left behind. So | |
| # this job never looks at the version. It walks components.schemas property-by-property and | |
| # paths method-by-method and names every schema and property that differs. | |
| # | |
| # And it FAILS CLOSED. The spec-drift job below prints ::warning:: and exits 0 when the GitHub | |
| # API is unreachable, which means a rate-limited runner reports "no drift" without having | |
| # looked. This job exits non-zero instead. An unknown is not a pass. | |
| # | |
| # REF: latest-release, not a branch. This client is built and integration-tested against the | |
| # latest RELEASED busbar (see the integration job, which downloads exactly that), so the spec | |
| # it commits must be the released engine's spec. Tracking a branch here would make the client | |
| # document endpoints and fields no released busbar serves. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| # SELF-TEST FIRST, then the verdict. Fleet convention, and the whole reason this job exists: | |
| # an assertion nobody has watched fail is indistinguishable from `exit 0`. This proves the | |
| # gate goes RED on a missing schema, a missing property, an extra property, a changed | |
| # required set, a changed enum, a changed type, and a core spec it could not fetch or parse. | |
| - name: spec-mirror gate self-test (red before green) | |
| run: python3 scripts/spec_mirror_gate.py --selftest | |
| - name: committed openapi.json is the same document as busbar core's | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: python3 scripts/spec_mirror_gate.py --mirror openapi.json --busbar-ref latest-release | |
| spec-drift: | |
| # The admin client is hand-rolled against openapi.json (committed at the repo root). | |
| # This job answers "is the committed spec CURRENT?" only. Whether src/client.rs still MATCHES | |
| # that spec is a separate question, answered by tests/openapi_conformance.rs in the build job. | |
| # Fail when the latest busbar release ships a NEWER spec version than the one committed, | |
| # so drift is visible instead of silent. Degrades to a warning if the GitHub API is | |
| # unavailable or rate-limited (keeps the check non-flaky). | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: compare committed spec version to the latest busbar release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -u | |
| committed=$(jq -r .info.version openapi.json) | |
| echo "committed spec version: $committed" | |
| resp=$(curl -sS --max-time 30 \ | |
| -H "Authorization: Bearer $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| https://api.github.com/repos/GetBusbar/busbar/releases/latest) || { | |
| echo "::warning::could not reach the GitHub API to check the latest busbar release; skipping spec-drift check" | |
| exit 0 | |
| } | |
| latest=$(echo "$resp" | jq -r '.tag_name // empty' | sed 's/^v//') | |
| if [ -z "$latest" ]; then | |
| echo "::warning::GitHub API returned no usable release tag (rate-limited?); skipping spec-drift check" | |
| echo "$resp" | head -c 500 | |
| exit 0 | |
| fi | |
| echo "latest busbar release: $latest" | |
| if [ "$committed" != "$latest" ]; then | |
| echo "::error::committed openapi.json is v$committed but the latest busbar release is v$latest — refresh the spec and re-audit src/client.rs" | |
| exit 1 | |
| fi | |
| echo "spec is current (v$committed)" |