Skip to content

feat(ci): add issue and PR management automation #89

feat(ci): add issue and PR management automation

feat(ci): add issue and PR management automation #89

Workflow file for this run

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
#
# 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.
# Repo-wide linting and link-check.
#
# This workflow has NO path filter — it runs on every pull_request and
# every push to main. That guarantees a `ci-gate` check is always posted
# on a PR, even doc-only or label-only changes that wouldn't otherwise
# trigger operator-ci or agent-ci. operator-ci and agent-ci also publish
# checks named `ci-gate`; GitHub composes same-named required checks so
# every ci-gate that posts must pass.
name: Lint CI
on:
workflow_dispatch: {}
pull_request: {}
push:
branches:
- main
env:
# Opt all JS actions into Node 24 ahead of GitHub's Node 20 phase-out.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
# Pin third-party tooling versions and binary checksums. NVIDIA's enterprise
# restricts which GitHub Actions can be used, so actionlint and lychee are
# installed as binaries directly rather than via wrapper actions; SHA256
# pins protect against tarball replacement on the upstream release.
ACTIONLINT_VERSION: 1.7.7
ACTIONLINT_SHA256: 023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757
LYCHEE_VERSION: 0.21.0
LYCHEE_SHA256: a06547250f10021dcafc6ed5bb20fca75835b65711745b63cfdda34c29ff6a73
YAMLLINT_VERSION: 1.38.0
jobs:
meta-lint:
name: meta-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: actionlint (workflow files)
if: always()
run: |
set -euo pipefail
curl -sSfL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" -o /tmp/actionlint.tgz
echo "${ACTIONLINT_SHA256} /tmp/actionlint.tgz" | sha256sum -c -
tar -xzf /tmp/actionlint.tgz -C /tmp actionlint
# -shellcheck= disables the shellcheck integration. Existing
# workflows have many SC2086/SC2155/etc. warnings that are
# tracked separately; this PR's scope is structural lint only.
/tmp/actionlint -color -shellcheck=
- name: yamllint
if: always()
run: |
pip install --user --quiet "yamllint==${YAMLLINT_VERSION}"
yamllint -c ci/yamllint.yaml .
- name: markdownlint
if: always()
# SHA pinned to satisfy NVIDIA's enterprise allow-list.
uses: DavidAnson/markdownlint-cli2-action@07035fd053f7be764496c0f8d8f9f41f98305101
with:
config: ci/.markdownlint-cli2.yaml
globs: |
**/*.md
!**/vendor/**
!**/node_modules/**
# Generated by 'make notices'; license texts contain formatting
# we don't control (multiple blanks, etc.). Even with the generator's
# blank-collapse step the per-license bodies vary widely.
!THIRD_PARTY_NOTICES.md
!agent/THIRD_PARTY_NOTICES.md
!operator/THIRD_PARTY_NOTICES.md
# Generated by git-cliff on every release; format intrinsically
# violates MD032/MD024/MD009. Track separately if changelog
# style ever needs enforcement.
!**/CHANGELOG.md
# NVIDIA-managed template; not for us to lint.
!SECURITY.md
- name: Install lychee
if: always()
run: |
set -euo pipefail
curl -sSfL "https://github.com/lycheeverse/lychee/releases/download/lychee-v${LYCHEE_VERSION}/lychee-x86_64-unknown-linux-gnu.tar.gz" -o /tmp/lychee.tgz
echo "${LYCHEE_SHA256} /tmp/lychee.tgz" | sha256sum -c -
tar -xzf /tmp/lychee.tgz -C /tmp lychee
# Persist lychee's URL cache across runs so transient external
# failures don't re-fail every PR. Cache is keyed weekly so dead
# links still surface within ~7 days.
- name: Restore lychee cache
if: always()
uses: actions/cache@v4
with:
path: .lycheecache
key: lychee-${{ github.run_id }}
restore-keys: |
lychee-
# Pass 1: internal links only (relative paths, anchors, file://).
# These can never be flaky — if this fails, the PR really did break
# a link. --offline tells lychee to skip http(s) entirely.
- name: lychee — internal links (blocking)
if: always()
run: |
/tmp/lychee \
--no-progress \
--offline \
--exclude-file ci/lycheeignore \
--exclude-path THIRD_PARTY_NOTICES.md \
--exclude-path agent/THIRD_PARTY_NOTICES.md \
--exclude-path agent/vendor \
--exclude-path operator/THIRD_PARTY_NOTICES.md \
--exclude-path operator/vendor \
'./**/*.md'
# Pass 2: external links. Non-blocking — a `kubernetes.io` blip or
# GitHub rate-limit shouldn't block a docs PR. 4xx/5xx/429 are
# accepted as "not definitively broken" since they're commonly
# transient. Real dead links surface as ❌ in the step log; ci-gate
# below ignores this step's result.
- name: lychee — external links (advisory)
if: always()
continue-on-error: true
# `--retry-wait-time 5` is a constant minimum delay between retries
# (exponential backoff in lychee is reserved for HTTP 429); with
# `--max-retries 8` that's roughly 8×5s = 40s per failing URL. The
# step timeout is a runaway guard for the case where many URLs all
# retry concurrently and/or are stuck timing out at `--timeout 20`.
timeout-minutes: 15
env:
# GitHub token avoids unauthenticated rate-limiting on github.com URLs.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
/tmp/lychee \
--no-progress \
--cache \
--max-cache-age 7d \
--max-retries 8 \
--retry-wait-time 5 \
--timeout 20 \
--accept "200..=299,403,429,500..=599" \
--exclude-file ci/lycheeignore \
--exclude-path THIRD_PARTY_NOTICES.md \
--exclude-path agent/THIRD_PARTY_NOTICES.md \
--exclude-path agent/vendor \
--exclude-path operator/THIRD_PARTY_NOTICES.md \
--exclude-path operator/vendor \
'./**/*.md'
# See operator-ci.yaml's ci-gate for rationale.
ci-gate:
name: ci-gate
needs: [meta-lint]
if: always()
runs-on: ubuntu-latest
steps:
- name: Verify all required jobs passed
run: |
results='${{ toJSON(needs) }}'
echo "$results"
echo "$results" | jq -e 'to_entries | all(.value.result == "success")'