Skip to content

Accept renamed nvidia-smi banner fields #2972

Accept renamed nvidia-smi banner fields

Accept renamed nvidia-smi banner fields #2972

Workflow file for this run

# 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 }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# Detect targeted path categories (default: any match = true)
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: changes
with:
filters: |
actions:
- '.github/workflows/**'
- '.github/actions/**'
deps:
- 'go.mod'
- 'go.sum'
- 'vendor/**'
- '.github/workflows/verify-licenses.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.
- 'Makefile'
- '.settings.yaml'
- '.goreleaser.yaml'
- 'go.mod'
- 'go.sum'
- 'vendor/**'
- 'tools/generate-notices'
- 'THIRD_PARTY_NOTICES.md'
# 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@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: false
- uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
languages: go
- run: go build ./...
env:
GOFLAGS: -mod=vendor
- uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: true
cache-dependency-path: |
go.sum
vendor/modules.txt
- name: Install go-licenses
run: go install github.com/google/go-licenses/v2@${{ 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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"
# ---------------------------------------------------------------------------
# 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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: ./.github/actions/load-versions
id: versions
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: ${{ steps.versions.outputs.go }}
cache: true
cache-dependency-path: |
go.sum
vendor/modules.txt
- name: Install go-licenses
run: go install github.com/google/go-licenses/v2@${{ 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"
# ---------------------------------------------------------------------------
# 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
- bom-freshness
- bom-freshness-skip
- tuning-freshness
- tuning-freshness-skip
- notices-freshness
- notices-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