Repository navigation
Auto Dependency Upgrade - Trigger #141
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
| # 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" |