Fix Cedar upstream-claim evaluation on VirtualMCPServer #21
Workflow file for this run
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: API Compatibility | |
| # This workflow guards the stability of the v1beta1 operator API surface. | |
| # | |
| # Phase 1 (current): advisory only. `crd-schema-check` runs with | |
| # continue-on-error: true so breaking-change findings are surfaced via the | |
| # job's step summary but do not block merges. Remove continue-on-error in | |
| # Phase 2 to start enforcing. | |
| on: | |
| pull_request: | |
| paths: | |
| - 'cmd/thv-operator/api/**' | |
| # files/crds is the source of truth — controller-gen emits here, and | |
| # crd-helm-wrapper copies from here into templates/. Any drift in | |
| # templates/ is caught by operator-ci.yml's generate-crds job, so | |
| # watching templates/ would be redundant. values.yaml and the | |
| # crd-helm-wrapper only affect Helm conditionals and annotations the | |
| # checker ignores; the workflow's explicit --set flags force every | |
| # CRD to render regardless of those files. | |
| - 'deploy/charts/operator-crds/files/crds/**' | |
| # Self-exercise the workflow when the workflow itself changes. | |
| - '.github/workflows/api-compat.yml' | |
| permissions: | |
| contents: read | |
| jobs: | |
| crd-schema-check: | |
| name: CRD Schema Compatibility | |
| runs-on: ubuntu-latest | |
| # Phase 1: advisory only. Remove in Phase 2 to enforce. | |
| continue-on-error: true | |
| # Expected runtime is ~3 minutes (checkout + go/helm setup + helm pull + | |
| # go install + two renders + checker). 10 minutes is a cheap upper bound | |
| # that protects against a hung helm pull or go install without being | |
| # disruptive to legitimate slow runs. | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout PR HEAD | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 | |
| with: | |
| go-version: 'stable' | |
| cache: true | |
| - name: Set up Helm | |
| uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 | |
| - name: Resolve baseline source | |
| id: baseline | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| # Baseline is always the most recent published release chart from | |
| # OCI. If the pull fails (no releases, OCI outage, or the chart | |
| # for the most recent tag is not yet published), the job fails — | |
| # an API-touching PR must be checked against a real released | |
| # baseline or not at all. Falling back to origin/main was | |
| # rejected in review: it can silently compare against an | |
| # already-broken baseline once a break lands on main. | |
| LATEST_TAG="$(gh release list --repo "$GITHUB_REPOSITORY" --limit 1 --json tagName --jq '.[0].tagName')" | |
| if [ -z "$LATEST_TAG" ]; then | |
| echo "::error::No releases found for $GITHUB_REPOSITORY; cannot establish an API compatibility baseline." | |
| exit 1 | |
| fi | |
| LATEST_VERSION="${LATEST_TAG#v}" | |
| echo "Pulling published chart for $LATEST_TAG..." | |
| mkdir -p /tmp/baseline-release | |
| helm pull "oci://ghcr.io/stacklok/toolhive/toolhive-operator-crds" \ | |
| --version "$LATEST_VERSION" \ | |
| --untar --untardir /tmp/baseline-release | |
| echo "source=release $LATEST_TAG" >> "$GITHUB_OUTPUT" | |
| echo "chart-dir=/tmp/baseline-release/toolhive-operator-crds" >> "$GITHUB_OUTPUT" | |
| - name: Render baseline CRDs | |
| env: | |
| # Route step outputs through env vars so bash quotes them instead | |
| # of the runner substituting them directly into the script body. | |
| # Current values are hardcoded literals, so not injectable today — | |
| # this is defense-in-depth against a future edit that routes a | |
| # PR-controlled string through these outputs. | |
| CHART_DIR: ${{ steps.baseline.outputs.chart-dir }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/baseline-crds | |
| # All three feature flags must be set so the Helm wrapper emits every | |
| # CRD (see deploy/charts/operator-crds/crd-helm-wrapper). Missing a | |
| # flag silently drops CRDs from the comparison. | |
| # The yq filter is defensive — the chart currently only templates | |
| # CRDs, but this keeps the invariant explicit if non-CRD resources | |
| # are ever added. | |
| helm template toolhive-operator-crds "$CHART_DIR" \ | |
| --set crds.install.server=true \ | |
| --set crds.install.registry=true \ | |
| --set crds.install.virtualMcp=true \ | |
| | yq 'select(.kind == "CustomResourceDefinition")' \ | |
| > /tmp/baseline-crds/all.yaml | |
| echo "Rendered $(grep -c '^kind: CustomResourceDefinition' /tmp/baseline-crds/all.yaml || echo 0) baseline CRDs" | |
| - name: Render HEAD CRDs | |
| run: | | |
| set -euo pipefail | |
| mkdir -p /tmp/head-crds | |
| helm template toolhive-operator-crds deploy/charts/operator-crds \ | |
| --set crds.install.server=true \ | |
| --set crds.install.registry=true \ | |
| --set crds.install.virtualMcp=true \ | |
| | yq 'select(.kind == "CustomResourceDefinition")' \ | |
| > /tmp/head-crds/all.yaml | |
| echo "Rendered $(grep -c '^kind: CustomResourceDefinition' /tmp/head-crds/all.yaml || echo 0) HEAD CRDs" | |
| - name: Install crd-schema-checker | |
| # SHA-pinned: openshift/crd-schema-checker has no release tags at the | |
| # time of writing, so @latest is the only other option. Pinning makes | |
| # CI deterministic and mitigates supply-chain risk (upstream compromise | |
| # would otherwise execute attacker code on the runner with GITHUB_TOKEN | |
| # in env). Bump via a deliberate PR after verifying the new output | |
| # locally. SHA pinned on 2026-04-21. | |
| run: go install github.com/openshift/crd-schema-checker/cmd/crd-schema-checker@3fee146022bfe6f4adf84998de35d7267b864bef | |
| - name: Check CRD schema compatibility | |
| id: checker | |
| env: | |
| # See Render baseline CRDs step for rationale on env-based interpolation. | |
| BASELINE_SOURCE: ${{ steps.baseline.outputs.source }} | |
| run: | | |
| # NoBools and NoMaps are OpenShift API-style conventions, not | |
| # compat-breaking rules. They fire on fields we legitimately use | |
| # (e.g. embeddingservers.spec.modelCache.enabled) and drown out | |
| # real findings. Re-enable only if upstream clarifies breaking- | |
| # change semantics for them. | |
| set +e | |
| crd-schema-checker check-manifests \ | |
| --existing-crd-filename /tmp/baseline-crds/all.yaml \ | |
| --new-crd-filename /tmp/head-crds/all.yaml \ | |
| --disabled-validators=NoBools,NoMaps \ | |
| 2>&1 | tee /tmp/checker-output.txt | |
| CHECK_EXIT=${PIPESTATUS[0]} | |
| set -e | |
| if [ "$CHECK_EXIT" -eq 0 ]; then | |
| STATUS="Compatible" | |
| else | |
| STATUS="Incompatible or Unknown" | |
| fi | |
| { | |
| echo "## API Compatibility — CRD Schema Check (Phase 1: advisory)" | |
| echo "" | |
| echo "**Baseline source**: $BASELINE_SOURCE" | |
| echo "**Status**: $STATUS" | |
| echo "" | |
| echo "<details><summary>crd-schema-checker output</summary>" | |
| echo "" | |
| echo '```' | |
| cat /tmp/checker-output.txt | |
| echo '```' | |
| echo "" | |
| echo "</details>" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| exit "$CHECK_EXIT" |