fix: overlap Kind cluster creation with host Go builds in E2E #5103
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
| # Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # Aggregate merge gate — the ONLY required status check in the main branch | |
| # ruleset. Every PR-gating CI check is either run or explicitly skipped here, | |
| # and the final "gate" job aggregates all results into a single pass/fail. | |
| # | |
| # To add a new gating check: | |
| # 1. Add a real job + companion -skip job with inverse if: conditions | |
| # 2. Add both job names to the gate job's needs: list | |
| # 3. No ruleset change needed — "gate" is the only required check. | |
| # | |
| # GPU tests are intentionally excluded (advisory only) until flake rate is | |
| # assessed. They can be added later with the same real/skip pattern. | |
| name: Merge Gate | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # Path classification — determines which checks run vs skip | |
| # --------------------------------------------------------------------------- | |
| check-paths: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| outputs: | |
| code: ${{ steps.code.outputs.code }} | |
| actions: ${{ steps.changes.outputs.actions }} | |
| deps: ${{ steps.changes.outputs.deps }} | |
| renovate: ${{ steps.changes.outputs.renovate }} | |
| bom: ${{ steps.changes.outputs.bom }} | |
| tuning: ${{ steps.changes.outputs.tuning }} | |
| notices: ${{ steps.changes.outputs.notices }} | |
| coverage: ${{ steps.changes.outputs.coverage }} | |
| docs: ${{ steps.changes.outputs.docs }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # Detect targeted path categories (default: any match = true) | |
| - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 | |
| id: changes | |
| with: | |
| filters: | | |
| actions: | |
| - '.github/workflows/**' | |
| - '.github/actions/**' | |
| deps: | |
| # Inputs to the verify-licenses gate, which runs `make | |
| # license-check` over the vendored dependency graph. Watch the | |
| # dependency manifests and the vendor tree, plus every input that | |
| # defines the check itself, so a change to the gate cannot skip | |
| # its own gate: the Makefile recipe is the check's contract, the | |
| # install-go-licenses action supplies the binary it runs (that | |
| # action clears GOFLAGS, without which -trimpath strips GOROOT and | |
| # the check passes while inspecting zero packages), and this | |
| # workflow defines the job. The entry replaced here named | |
| # '.github/workflows/verify-licenses.yaml', which was deleted in | |
| # 3c9f6ec5 when the job moved into this workflow, so the filter | |
| # had stopped watching anything the job actually consumes. | |
| # | |
| # The toolchain pins are watched too, because the job resolves | |
| # them through load-versions before running the check: .go-version | |
| # supplies the Go toolchain (which determines the `go list std` | |
| # ignore set) and .settings.yaml supplies linting.go_licenses (the | |
| # go-licenses build that classifies packages). Both reach the gate | |
| # only via that action, so all three paths must be watched or a | |
| # version bump silently skips the gate it changes — #814 bumped | |
| # go-licenses touching only .settings.yaml, and #1204 bumped Go | |
| # touching only .go-version. | |
| - 'go.mod' | |
| - 'go.sum' | |
| - 'vendor/**' | |
| - 'Makefile' | |
| - '.go-version' | |
| - '.settings.yaml' | |
| - '.github/actions/install-go-licenses/**' | |
| - '.github/actions/load-versions/**' | |
| - '.github/workflows/merge-gate.yaml' | |
| renovate: | |
| # Watch every input the verify-renovate job consumes — including | |
| # the Makefile target that runs the validator — so changes to the | |
| # Make target or its pinned image trigger the gate. | |
| - '.github/renovate.json5' | |
| - 'Makefile' | |
| bom: | |
| # Inputs to the committed-BOM version-freshness check. The full | |
| # `tests` job already runs it for code changes, but a docs-only | |
| # PR editing the committed BOM (or a registry change that somehow | |
| # lands as a docs-only diff) yields code=false and skips `tests`, | |
| # so gate the freshness test on the doc + registry directly. The | |
| # generator sources are included so a generator change also | |
| # re-runs the check independently of the full suite. | |
| - 'docs/user/container-images.md' | |
| - 'recipes/registry.yaml' | |
| - 'tools/bom/**' | |
| - 'pkg/bom/**' | |
| tuning: | |
| # Inputs that can change the committed tuning-status table | |
| # WITHOUT tripping pkg/tuning's TestCompute_Structure — which runs | |
| # in the `tests` job on any non-docs change and already asserts | |
| # every row's service, accelerator, profile, setup-name and | |
| # tuning-name. That test is version-agnostic, so the only table | |
| # changes it misses are a manifest pin version bump, a | |
| # renderer/extractor change, or a hand edit of the committed doc. | |
| # Structural changes driven by overlays, mixins, registry or | |
| # pkg/recipe already fail that test, so they are deliberately NOT | |
| # listed here (keeping this gate off unrelated recipe PRs). | |
| - 'docs/integrator/components/nodewright.md' | |
| - 'recipes/components/nodewright-customizations/**' | |
| - 'pkg/tuning/**' | |
| - 'tools/tuning/**' | |
| notices: | |
| # Inputs to the committed THIRD_PARTY_NOTICES.md freshness check. | |
| # The file is the union of every vendored dependency's license | |
| # across the released OS/arch matrix, so any dependency-graph | |
| # change can alter it. Watch the dependency manifests, the vendor | |
| # tree, and the generator itself (plus the committed file, so a | |
| # hand edit is re-verified). .goreleaser.yaml is included because | |
| # the generator asserts its PLATFORMS matrix matches the release | |
| # targets defined there — a new goos/goarch must re-run this check. | |
| # Makefile is included because the `notices`/`notices-check` | |
| # recipes are the generation/check contract — a change to them | |
| # must not be able to skip its own gate. .settings.yaml is included | |
| # because the generator's output depends on its pins: a | |
| # linting.go_licenses bump can change license classification/URL | |
| # derivation, and a languages.go bump changes the `go list std` | |
| # ignore set. This is a standalone job — the full `tests` suite | |
| # does NOT regenerate the file. | |
| # The Python half of the notices file covers the aiperf-bench | |
| # image, whose dependency closure is an installed venv rather | |
| # than a vendored tree. It cannot be regenerated here (that needs | |
| # docker + PyPI), so the committed fragment is watched directly, | |
| # alongside the requirements.txt whose sha256 the generator | |
| # asserts the fragment still records — editing requirements | |
| # without a refresh must fail this gate rather than ship stale | |
| # attributions. | |
| - 'Makefile' | |
| - '.settings.yaml' | |
| - '.goreleaser.yaml' | |
| - 'go.mod' | |
| - 'go.sum' | |
| - 'vendor/**' | |
| - 'tools/generate-notices' | |
| - 'tools/generate-python-licenses' | |
| - 'validators/performance/requirements.txt' | |
| - 'validators/performance/licenses/**' | |
| - 'THIRD_PARTY_NOTICES.md' | |
| coverage: | |
| # Inputs to the committed CUJ/CLI coverage-matrix freshness check. | |
| # The matrix is derived, so a change to ANY of its signal sources | |
| # can move a row without touching the doc — and until #1977 the | |
| # only thing keeping the committed page accurate was that nobody | |
| # regenerated it. Watched here: | |
| # - the committed doc itself (catches a hand edit) | |
| # - the generator and pkg/cli (the verb registry it walks) | |
| # - infra/uat/reservations.yaml + pkg/uatbroker: which lanes are | |
| # nightly-enrolled, and for which intents | |
| # - the per-cloud uat-*.yaml pipelines: which CUJ phases are | |
| # actually enabled steps (a commented-out phase is not coverage) | |
| # - uat-nightly-batch.yaml: the previous_n version axis | |
| # - tests/uat, tests/chainsaw, demos: the scanned signal trees | |
| # - Makefile + this workflow: the generate/check contract and the | |
| # coverage-freshness job itself, so a change to either cannot | |
| # skip its own gate | |
| - 'docs/user/coverage-matrix.md' | |
| - 'tools/coverage/**' | |
| - 'pkg/cli/**' | |
| - 'pkg/uatbroker/**' | |
| - 'infra/uat/reservations.yaml' | |
| - '.github/workflows/uat-*.yaml' | |
| - 'tests/uat/**' | |
| - 'tests/chainsaw/**' | |
| - 'demos/**' | |
| - 'Makefile' | |
| - '.github/workflows/merge-gate.yaml' | |
| docs: | |
| # Inputs to the parser-level MDX gate. Only the Fern-published | |
| # trees matter — a change anywhere under them can introduce a | |
| # construct that parses as CommonMark but aborts | |
| # `fern generate --docs`. | |
| # | |
| # docs/index.yml is Fern's navigation manifest AND the source the | |
| # checkers derive their file list from, so it is both an input and | |
| # a trigger: adding a page there must run the gate over it. | |
| # docs/README.md is the published landing page — the earlier | |
| # per-subdirectory globs missed it entirely. | |
| # | |
| # Everything the gate is MADE of is listed too, so a change to the | |
| # gate cannot skip its own verification: the two checkers, the page | |
| # enumerator, the locked parser toolchain, this workflow, and the | |
| # Makefile — whose `check-docs-mdx-parse` target is what the job | |
| # actually invokes, matching why the `renovate` and `notices` | |
| # filters watch it. | |
| - 'docs/**' | |
| - 'tools/check-docs-mdx' | |
| - 'tools/check-docs-mdx-parse' | |
| - 'tools/docs-published-files' | |
| - 'tools/mdx/**' | |
| - 'Makefile' | |
| - '.github/workflows/merge-gate.yaml' | |
| # code = true iff at least one changed file is NOT docs/markdown/LICENSE. | |
| # | |
| # Exclusion idiom: dorny's `predicate-quantifier: 'every'` requires a file | |
| # to match EVERY pattern in the filter, so a file matches `code` only when | |
| # it matches '**' AND none of the docs patterns. The filter output is true | |
| # when ANY changed file is code — so a docs-only PR yields code=false. | |
| # | |
| # NOTE: 'every' does NOT mean "every changed file matches"; the prior | |
| # `only: [**.md, docs/**, LICENSE]` form required a single file to match | |
| # all three at once (impossible), so it was always false and code was | |
| # always true — docs-only PRs ran full qualification. '**' matches | |
| # dotfiles too (dorny sets picomatch dot:true), so '.golangci.yaml', | |
| # '.settings.yaml', etc. still count as code. | |
| - uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3 | |
| id: code | |
| with: | |
| predicate-quantifier: 'every' | |
| filters: | | |
| code: | |
| - '**' | |
| - '!**.md' | |
| - '!docs/**' | |
| - '!LICENSE' | |
| # --------------------------------------------------------------------------- | |
| # Qualification (Test, Lint, CLI E2E, E2E, Security Scan) | |
| # --------------------------------------------------------------------------- | |
| tests: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.code == 'true' | |
| uses: ./.github/workflows/qualification.yaml | |
| permissions: | |
| actions: read | |
| contents: read | |
| id-token: write | |
| security-events: write | |
| with: | |
| coverage_report: true | |
| tests-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.code != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "Docs/non-code change — qualification tests not required" | |
| # --------------------------------------------------------------------------- | |
| # CodeQL (analyze) | |
| # --------------------------------------------------------------------------- | |
| analyze: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| actions: read | |
| contents: read | |
| security-events: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/load-versions | |
| id: versions | |
| - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: ${{ steps.versions.outputs.go }} | |
| cache: false | |
| - uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 | |
| with: | |
| languages: go | |
| - run: go build ./... | |
| env: | |
| GOFLAGS: -mod=vendor | |
| - uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 | |
| analyze-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.code != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "Docs/non-code change — CodeQL analysis not required" | |
| # --------------------------------------------------------------------------- | |
| # Malware scan (ClamAV) | |
| # --------------------------------------------------------------------------- | |
| malware-scan: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| actions: read | |
| contents: read | |
| security-events: write | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/malware-scan | |
| with: | |
| scan_path: '.' | |
| category: 'clamav' | |
| malware-scan-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.code != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "Docs/non-code change — malware scan not required" | |
| # --------------------------------------------------------------------------- | |
| # Actionlint (GitHub Actions workflow linting) | |
| # --------------------------------------------------------------------------- | |
| actionlint: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.actions == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install actionlint | |
| # Pin actionlint to a specific version and verify the downloaded | |
| # binary's SHA256 against the upstream-published checksum file. | |
| # Replaces `bash <(curl … main …)`, which executed a mutable script | |
| # from the default branch and downloaded the binary with no | |
| # checksum verification (the upstream installer pipes | |
| # `curl … | tar xvz` directly). | |
| env: | |
| ACTIONLINT_VERSION: '1.7.11' | |
| run: | | |
| set -euo pipefail | |
| BASE="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}" | |
| TAR="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" | |
| TMP="$(mktemp -d)" | |
| curl -fsSL -o "${TMP}/${TAR}" "${BASE}/${TAR}" | |
| curl -fsSL -o "${TMP}/checksums.txt" "${BASE}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" | |
| EXPECTED=$(awk -v t="${TAR}" '$2 == t {print $1}' "${TMP}/checksums.txt") | |
| if [[ -z "${EXPECTED}" ]]; then | |
| echo "::error::no checksum entry for ${TAR} in upstream checksums.txt" | |
| exit 1 | |
| fi | |
| ACTUAL=$(sha256sum "${TMP}/${TAR}" | awk '{print $1}') | |
| if [[ "${ACTUAL}" != "${EXPECTED}" ]]; then | |
| echo "::error::actionlint checksum mismatch: expected ${EXPECTED}, got ${ACTUAL}" | |
| exit 1 | |
| fi | |
| tar -xzf "${TMP}/${TAR}" -C "${TMP}" actionlint | |
| mv "${TMP}/actionlint" "${PWD}/actionlint" | |
| rm -rf "${TMP}" | |
| echo "${PWD}" >> "$GITHUB_PATH" | |
| - name: Run actionlint | |
| run: actionlint -color -shellcheck= | |
| actionlint-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.actions != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "No workflow/action changes — actionlint not required" | |
| # --------------------------------------------------------------------------- | |
| # License verification | |
| # --------------------------------------------------------------------------- | |
| verify-licenses: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.deps == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/load-versions | |
| id: versions | |
| - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: ${{ steps.versions.outputs.go }} | |
| cache: true | |
| cache-dependency-path: | | |
| go.sum | |
| vendor/modules.txt | |
| - uses: ./.github/actions/install-go-licenses | |
| with: | |
| version: ${{ steps.versions.outputs.go_licenses }} | |
| - name: Report licenses | |
| env: | |
| GOFLAGS: -mod=vendor | |
| run: | | |
| echo "=== Dependency Licenses ===" | |
| go-licenses report ./... 2>/dev/null | sort -t',' -k3 | column -t -s',' | |
| echo "" | |
| echo "=== License Summary ===" | |
| go-licenses report ./... 2>/dev/null | cut -d',' -f3 | sort | uniq -c | sort -rn | |
| - name: Check licenses | |
| env: | |
| GOFLAGS: -mod=vendor | |
| run: make license-check | |
| verify-licenses-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.deps != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "No dependency changes — license check not required" | |
| # --------------------------------------------------------------------------- | |
| # Renovate config validation | |
| # --------------------------------------------------------------------------- | |
| verify-renovate: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.renovate == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Validate .github/renovate.json5 | |
| run: make lint-renovate | |
| verify-renovate-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.renovate != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "No Renovate config changes — validation not required" | |
| # --------------------------------------------------------------------------- | |
| # MDX docs gate — parses every Fern-published doc with the real MDX parser. | |
| # | |
| # `fern check` (Fern Docs CI) does not parse MDX, and the job that does | |
| # (`fern generate --docs --preview`) runs in a workflow_run companion whose | |
| # status never lands on the PR head SHA, so it can never be required. That | |
| # gap let a bare `<=` merge green and then fail every docs publish, including | |
| # a release tag (#2050). This job is the blocking replacement: hermetic, no | |
| # DOCS_FERN_TOKEN, no dependency on Fern's SaaS being up at merge time. | |
| # --------------------------------------------------------------------------- | |
| docs-mdx: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.docs == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 | |
| with: | |
| node-version: '20' | |
| # The bash approximation runs first: it is instant and its messages name | |
| # the specific hazard, so the common cases fail fast with better output. | |
| # The parser then decides — it is the authoritative check, and GITHUB_ACTIONS | |
| # makes a missing Node or a failed dependency install a hard error rather | |
| # than the local warn-and-skip. | |
| - name: Check MDX safety (pattern approximation) | |
| run: make check-docs-mdx | |
| - name: Validate docs with the real MDX parser | |
| run: make check-docs-mdx-parse | |
| docs-mdx-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.docs != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "No published-docs changes — MDX parse gate not required" | |
| # --------------------------------------------------------------------------- | |
| # BOM version freshness — ensures the committed container-images.md matches | |
| # the registry pins even on docs-only PRs (which skip the full `tests` job). | |
| # --------------------------------------------------------------------------- | |
| bom-freshness: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.bom == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/load-versions | |
| id: versions | |
| - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: ${{ steps.versions.outputs.go }} | |
| cache: false | |
| - name: Verify committed BOM versions and variants match the recipes | |
| env: | |
| GOFLAGS: -mod=vendor | |
| # `go test -run <name>` exits 0 with "no tests to run" if a test is | |
| # renamed or removed, which would let this gate silently no-op. Assert | |
| # each named test actually executed by requiring its PASS line — a | |
| # docs-only PR must not be able to forge either the Components table | |
| # or the Version variants table. | |
| run: | | |
| set -uo pipefail | |
| out=$(go test ./tools/bom/... -run '^(TestCommittedBOMVersionsMatchRegistry|TestCommittedBOMVariantsMatchRecipePins)$' -v -count=1 2>&1) && rc=0 || rc=$? | |
| echo "$out" | |
| if [ "$rc" -ne 0 ]; then | |
| exit "$rc" | |
| fi | |
| for t in TestCommittedBOMVersionsMatchRegistry TestCommittedBOMVariantsMatchRecipePins; do | |
| if ! grep -q "^--- PASS: $t" <<<"$out"; then | |
| echo "::error::$t did not execute (renamed or removed?); the BOM freshness gate would silently pass. Update this job to run the freshness check." | |
| exit 1 | |
| fi | |
| done | |
| bom-freshness-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.bom != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "No BOM/registry changes — BOM freshness check not required" | |
| # --------------------------------------------------------------------------- | |
| # Tuning-status doc freshness — ensures the committed tuning table in | |
| # docs/integrator/components/nodewright.md matches the recipe catalog and the | |
| # nodewright package pins, on PRs touching its inputs (mirrors bom-freshness). | |
| # --------------------------------------------------------------------------- | |
| tuning-freshness: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.tuning == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/load-versions | |
| id: versions | |
| - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: ${{ steps.versions.outputs.go }} | |
| cache: false | |
| - name: Verify committed tuning-status table is up to date | |
| env: | |
| GOFLAGS: -mod=vendor | |
| # `make tuning-check` regenerates the table into a temp dir and splices | |
| # it into the doc, then fails if the working tree differs — so a stale | |
| # committed table (or missing splice markers) fails closed here. | |
| run: make tuning-check | |
| tuning-freshness-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.tuning != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "No tuning-status inputs changed — freshness check not required" | |
| # --------------------------------------------------------------------------- | |
| # Third-party notices freshness — ensures the committed THIRD_PARTY_NOTICES.md | |
| # matches the vendored dependency graph across every released OS/arch target, | |
| # on PRs touching its inputs (go.mod/go.sum/vendor + the generator). | |
| # --------------------------------------------------------------------------- | |
| notices-freshness: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.notices == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/load-versions | |
| id: versions | |
| - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: ${{ steps.versions.outputs.go }} | |
| cache: true | |
| cache-dependency-path: | | |
| go.sum | |
| vendor/modules.txt | |
| - uses: ./.github/actions/install-go-licenses | |
| with: | |
| version: ${{ steps.versions.outputs.go_licenses }} | |
| - name: Verify committed THIRD_PARTY_NOTICES.md is up to date | |
| env: | |
| GOFLAGS: -mod=vendor | |
| # `make notices-check` regenerates the notices file (union across the | |
| # release OS/arch matrix, so it is host-independent) and fails if the | |
| # working tree differs — a stale committed file fails closed here. | |
| run: make notices-check | |
| notices-freshness-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.notices != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "No dependency/generator changes — notices freshness check not required" | |
| # --------------------------------------------------------------------------- | |
| # Coverage-matrix freshness — ensures the committed docs/user/coverage-matrix.md | |
| # matches the CLI registry, the nightly UAT wiring, and the in-repo test | |
| # signals, on PRs touching its inputs (mirrors bom-freshness). Before #1977 the | |
| # page was only accurate because contributors avoided regenerating it; this is | |
| # the check that makes regeneration mandatory rather than optional. | |
| # --------------------------------------------------------------------------- | |
| coverage-freshness: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.coverage == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - uses: ./.github/actions/load-versions | |
| id: versions | |
| - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| with: | |
| go-version: ${{ steps.versions.outputs.go }} | |
| cache: false | |
| - name: Verify committed coverage matrix is up to date | |
| env: | |
| GOFLAGS: -mod=vendor | |
| # `make coverage-check` regenerates the matrix in place and fails if the | |
| # working tree differs. The generator itself fails closed on an | |
| # unresolvable UAT wiring or version axis, so a moved registry or renamed | |
| # workflow input errors here instead of quietly reporting the affected | |
| # rows as uncovered. | |
| run: make coverage-check | |
| coverage-freshness-skip: | |
| needs: [check-paths] | |
| if: needs.check-paths.outputs.coverage != 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - run: echo "No coverage-matrix inputs changed — freshness check not required" | |
| # --------------------------------------------------------------------------- | |
| # Aggregate gate — the single required status check | |
| # --------------------------------------------------------------------------- | |
| gate: | |
| if: always() | |
| needs: | |
| - check-paths | |
| - tests | |
| - tests-skip | |
| - analyze | |
| - analyze-skip | |
| - malware-scan | |
| - malware-scan-skip | |
| - actionlint | |
| - actionlint-skip | |
| - verify-licenses | |
| - verify-licenses-skip | |
| - verify-renovate | |
| - verify-renovate-skip | |
| - docs-mdx | |
| - docs-mdx-skip | |
| - bom-freshness | |
| - bom-freshness-skip | |
| - tuning-freshness | |
| - tuning-freshness-skip | |
| - notices-freshness | |
| - notices-freshness-skip | |
| - coverage-freshness | |
| - coverage-freshness-skip | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| steps: | |
| - name: Check gate | |
| run: | | |
| echo "Gate results:" | |
| echo '${{ toJSON(needs) }}' | jq -r 'to_entries[] | "\(.key): \(.value.result)"' | |
| # check-paths must succeed — if it fails, all downstream jobs are | |
| # skipped (not failed), which would silently pass the gate. | |
| if [ "${{ needs.check-paths.result }}" != "success" ]; then | |
| echo "::error::check-paths did not succeed (result: ${{ needs.check-paths.result }})" | |
| exit 1 | |
| fi | |
| # Fail if ANY other dependency failed or was cancelled. | |
| # Skipped is OK — it means the inverse job ran instead. | |
| if echo '${{ toJSON(needs.*.result) }}' | jq -e 'map(select(. == "failure" or . == "cancelled")) | length > 0' > /dev/null; then | |
| echo "::error::One or more gating checks failed or were cancelled" | |
| exit 1 | |
| fi |