Skip to content

Auto Dependency Upgrade - Trigger #141

Auto Dependency Upgrade - Trigger

Auto Dependency Upgrade - Trigger #141

# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Bumping runtime_image_tag moves the runtime image out from under the
# baseline_sbom that the licenses stage subtracts from NOTICES. Subtraction
# keys on (name, version), so every package whose version moved in the new tag
# stops subtracting, reads as net-new, resolves to UNKNOWN, and fails the
# license gate. The baseline is therefore re-captured here, in the same commit
# as the version bump, and a capture that cannot complete pushes nothing.
name: Auto Dependency Upgrade - Trigger
on:
schedule:
- cron: '0 10 * * *'
workflow_dispatch:
inputs:
framework:
description: 'Framework to attempt upgrade for'
type: choice
options: [trtllm]
default: trtllm
dry_run:
description: 'Rehearse the baseline capture only; push nothing'
type: boolean
default: false
permissions:
contents: write
actions: write
# A capture holds a scarce runner for tens of minutes, so never run two at once
# for the same ref. Queue rather than cancel: a half-finished capture wastes the
# scan it already paid for, and the branch idempotency check makes the second
# run cheap anyway.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
# The matrix is single-entry, which is what makes the job outputs below
# well-defined: a matrix job's outputs are last-writer-wins across instances.
# Adding a second framework means extracting these three jobs into a
# workflow_call file and running the matrix over that instead.
detect:
name: ${{ matrix.framework }} / detect
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
framework: [trtllm]
outputs:
framework: ${{ matrix.framework }}
current: ${{ steps.detect.outputs.current }}
latest: ${{ steps.detect.outputs.latest }}
runtime_image: ${{ steps.detect.outputs.runtime_image }}
branch: ${{ steps.branch.outputs.name }}
proceed: ${{ steps.branch.outputs.proceed }}
steps:
# No explicit ref: checkout defaults to the ref that triggered the run.
# A scheduled run is always the default branch, so cron behaviour is
# unchanged, while a workflow_dispatch runs the pipeline code from the ref
# you dispatched -- which is what makes a change to it testable pre-merge.
#
# Because that ref is caller-chosen, this job must hold no write-capable
# credential: it runs repository Python, so persisting the bot PAT here
# would let anyone able to dispatch escalate to whatever that PAT can
# reach. Nothing here needs it -- the release lookup uses GITHUB_TOKEN and
# ls-remote only reads.
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'
- name: Detect latest version
id: detect
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
OUT=$(python3 .github/scripts/dep_upgrade/detect_latest_versions.py \
--framework ${{ matrix.framework }})
echo "$OUT"
{
echo "current=$(echo "$OUT" | jq -r .current)"
echo "latest=$(echo "$OUT" | jq -r .latest)"
echo "upgrade_needed=$(echo "$OUT" | jq -r .upgrade_needed)"
echo "runtime_image=$(echo "$OUT" | jq -r .runtime_image)"
} >> "$GITHUB_OUTPUT"
- name: Compute branch name + idempotency check
id: branch
env:
UPGRADE_NEEDED: ${{ steps.detect.outputs.upgrade_needed }}
run: |
BRANCH="deps/upgrade-${{ matrix.framework }}-v${{ steps.detect.outputs.latest }}"
echo "name=$BRANCH" >> "$GITHUB_OUTPUT"
if [ "$UPGRADE_NEEDED" != "true" ]; then
echo "No upgrade needed (current=${{ steps.detect.outputs.current }}, latest=${{ steps.detect.outputs.latest }})."
echo "proceed=false" >> "$GITHUB_OUTPUT"
elif git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
echo "Branch $BRANCH already exists on origin; skipping."
echo "proceed=false" >> "$GITHUB_OUTPUT"
else
echo "proceed=true" >> "$GITHUB_OUTPUT"
fi
# syft catalogs the full multi-GB runtime image. Runner history is recorded in
# shared-compliance-audit.yml: small-v2 and prod-default-v2 run out of disk and
# prod-builder-v3 OOMs, so this pool -- which runs these images for tests -- is
# the one with the headroom. Both architectures are captured from here: syft
# selects the platform via SYFT_PLATFORM and scans the registry, so the arm64
# pass needs neither an arm64 runner nor emulation.
capture:
name: ${{ needs.detect.outputs.framework }} / baseline capture
needs: detect
if: needs.detect.outputs.proceed == 'true'
# Fastchecker rather than prod-tester-amd-v2: that pool forbids containerless
# jobs and its admission rejects a push on a personal branch, so the pipeline
# could never be exercised before merge. Fastchecker runs the rust-tests /
# rust-clippy jobs containerless and admits this event, which keeps a change
# to this workflow testable.
runs-on:
group: Fastchecker
timeout-minutes: 90
permissions:
contents: read
outputs:
stem: ${{ steps.capture.outputs.stem }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install tooling
run: |
set -euo pipefail
docker buildx version
# container/compliance/overrides.py imports yaml.
python3 -c 'import yaml' 2>/dev/null \
|| python3 -m pip install --quiet --user --break-system-packages pyyaml \
|| python3 -m pip install --quiet --user pyyaml
mkdir -p "${RUNNER_TEMP}/bin"
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh \
| sh -s -- -b "${RUNNER_TEMP}/bin"
echo "${RUNNER_TEMP}/bin" >> "$GITHUB_PATH"
- name: Capture baseline SBOMs
id: capture
env:
REF: ${{ needs.detect.outputs.runtime_image }}:${{ needs.detect.outputs.latest }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
EXTRA=()
if [ "${DRY_RUN:-false}" = "true" ]; then EXTRA=(--dry-run); fi
# Sequential, not parallel: both passes read-modify-write the single
# manifest.json, so concurrent runs would drop one arch's entry.
for PLATFORM in linux/amd64 linux/arm64; do
echo "::group::capture ${PLATFORM}"
python3 container/compliance/base_sboms/capture_baseline_sbom.py \
--from "$REF" --baseline "$REF" \
--platform "$PLATFORM" --prune-superseded -v "${EXTRA[@]}"
echo "::endgroup::"
done
if [ "${DRY_RUN:-false}" = "true" ]; then
echo "Dry run: manifest not written, no stem to publish."
exit 0
fi
STEM=$(python3 .github/scripts/dep_upgrade/baseline_stem.py \
--image "${REF%:*}" --tag "${REF##*:}")
echo "Captured baseline stem: $STEM"
echo "stem=$STEM" >> "$GITHUB_OUTPUT"
- name: Upload refreshed baseline corpus
if: inputs.dry_run != true
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: baseline-refresh-${{ needs.detect.outputs.framework }}-${{ needs.detect.outputs.latest }}
path: |
container/compliance/base_sboms/manifest.json
container/compliance/base_sboms/*.cdx.json
if-no-files-found: error
retention-days: 7
- name: Notify on capture failure
# A failed capture pushes no branch and opens no PR, so without this the
# upgrade stalls silently -- a genuinely disallowed license in a new tag
# would go unnoticed until someone wonders why bumps stopped arriving.
if: failure()
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
with:
webhook: ${{ secrets.SLACK_NOTIFY_NIGHTLY_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
blocks:
- type: "section"
text:
type: mrkdwn
text: ":package: *Auto Dep Upgrade* :x: Baseline capture failed — ${{ needs.detect.outputs.framework }} v${{ needs.detect.outputs.latest }}"
- type: "section"
text:
type: mrkdwn
text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Workflow run> · image `${{ needs.detect.outputs.runtime_image }}:${{ needs.detect.outputs.latest }}`"
- type: "section"
text:
type: mrkdwn
text: "No branch was pushed. Common causes: the container tag is not published yet (the cron will retry), or the new image introduces a license the policy denies (needs a human)."
commit:
name: ${{ needs.detect.outputs.framework }} / commit
needs: [detect, capture]
if: needs.detect.outputs.proceed == 'true' && inputs.dry_run != true
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
token: ${{ secrets.OPS_BOT_PAT }}
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'
- name: Clear stale baseline SBOMs
# Artifacts carry additions, not deletions, so without this the SBOMs
# that --prune-superseded removed would survive into the commit.
run: rm -f container/compliance/base_sboms/*.cdx.json
- name: Download refreshed baseline corpus
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: baseline-refresh-${{ needs.detect.outputs.framework }}-${{ needs.detect.outputs.latest }}
path: container/compliance/base_sboms
- name: Apply bump
env:
FW: ${{ needs.detect.outputs.framework }}
VER: ${{ needs.detect.outputs.latest }}
STEM: ${{ needs.capture.outputs.stem }}
run: |
set -euo pipefail
test -n "$STEM"
# Pre-flight: prove the bump tooling still edits exactly the MAIN_TOT
# pin in releases.data.ts and the trtllm baseline_sbom in context.yaml
# before touching the real tree.
python3 .github/scripts/dep_upgrade/check_bump_dependency.py
python3 .github/scripts/dep_upgrade/bump_dependency.py \
--framework "$FW" --version "$VER" --baseline-stem "$STEM"
git status --short
- name: Commit + push upgrade branch
env:
FW: ${{ needs.detect.outputs.framework }}
VER: ${{ needs.detect.outputs.latest }}
BRANCH: ${{ needs.detect.outputs.branch }}
run: |
set -euo pipefail
git config user.name "dynamo-ops"
git config user.email "170655669+dynamo-ops@users.noreply.github.com"
git checkout -b "$BRANCH"
# releases.data.ts feeds generated llms-tables twins and JSON/Atom assets;
# regenerate them in the same commit or check_reference.sh flags the branch stale.
python3 docs/fern/scripts/gen_llms_tables.py
git add container/context.yaml pyproject.toml docs/fern/components/releases.data.ts
git add docs/fern/
# -A so the pruned SBOMs are staged as deletions, not left behind.
git add -A container/compliance/base_sboms/
git commit -s -m "chore: bump $FW to v$VER"
git push origin "$BRANCH"
- name: Dispatch post-merge-ci on upgrade branch
env:
GH_TOKEN: ${{ secrets.OPS_BOT_PAT }}
FW: ${{ needs.detect.outputs.framework }}
BRANCH: ${{ needs.detect.outputs.branch }}
run: |
gh workflow run post-merge-ci.yml --ref "$BRANCH" -f framework="$FW"