Repository navigation
Docker Tag Monitor #4
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
| name: Docker Tag Monitor | |
| on: | |
| schedule: | |
| - cron: '0 7 * * 6' | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'Dry run - check releases but do not create issues' | |
| required: false | |
| default: false | |
| type: boolean | |
| jobs: | |
| check-releases: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v6.0.2 | |
| - name: Check for new Docker image releases | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| SEVEN_DAYS_AGO=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ) | |
| # Extract unique image:tag pairs from both config files | |
| # Track highest version per repo and the tag suffixes actually in use | |
| declare -A CURRENT_VERSIONS | |
| declare -A REPO_SUFFIXES | |
| # Only monitor these Docker Hub repos for updates | |
| MONITORED_REPOS="lmsysorg/sglang lmsysorg/sglang-rocm vllm/vllm-openai vllm/vllm-openai-rocm" | |
| # Normalize suffix: strip rc prefixes and date stamps so suffixes | |
| # like "rc0-rocm720-mi35x-20260413" become "-rocm720-mi35x" | |
| normalize_suffix() { | |
| local s="$1" | |
| s=$(echo "$s" | sed -E 's/^rc[0-9]+//; s/-[0-9]{8}(-[0-9]+)?$//') | |
| [[ -z "$s" ]] && s="BARE" | |
| echo "$s" | |
| } | |
| while IFS= read -r img; do | |
| repo="${img%%:*}" | |
| tag="${img#*:}" | |
| # Only track repos we want to auto-update | |
| [[ ! " $MONITORED_REPOS " == *" $repo "* ]] && continue | |
| [[ "$img" == *nightly* || "$img" == *-dev* ]] && continue | |
| base_ver=$(echo "$tag" | grep -oP 'v\d+\.\d+\.\d+(\.\w+)?' || true) | |
| [[ -z "$base_ver" ]] && continue | |
| suffix="${tag#"$base_ver"}" | |
| suffix=$(normalize_suffix "$suffix") | |
| prev="${CURRENT_VERSIONS[$repo]:-}" | |
| if [[ -z "$prev" ]] || [[ "$(printf '%s\n%s\n' "$prev" "$base_ver" | sort -V | tail -1)" == "$base_ver" ]]; then | |
| CURRENT_VERSIONS[$repo]="$base_ver" | |
| fi | |
| if [[ ! " ${REPO_SUFFIXES[$repo]:-} " == *" ${suffix} "* ]]; then | |
| REPO_SUFFIXES[$repo]="${REPO_SUFFIXES[$repo]:-} ${suffix}" | |
| fi | |
| done < <(grep -h '^\s*image:' .github/configs/nvidia-master.yaml .github/configs/amd-master.yaml \ | |
| | sed 's/.*image:\s*//; s/"//g; s/'"'"'//g' | sort -u) | |
| echo "=== Currently tracked repos ===" | |
| for repo in "${!CURRENT_VERSIONS[@]}"; do | |
| echo " $repo: ${CURRENT_VERSIONS[$repo]} (suffixes:${REPO_SUFFIXES[$repo]:-})" | |
| done | |
| FOUND_UPDATES=$(mktemp) | |
| check_dockerhub() { | |
| local repo="$1" current_ver="$2" allowed_suffixes="$3" | |
| echo "Checking $repo (current: $current_ver, suffixes:$allowed_suffixes)..." >&2 | |
| local response | |
| response=$(curl -sf "https://hub.docker.com/v2/repositories/${repo}/tags?page_size=50&ordering=last_updated" 2>/dev/null) || { | |
| echo " Failed to fetch tags for $repo" >&2 | |
| return | |
| } | |
| echo "$response" | jq -r --arg since "$SEVEN_DAYS_AGO" ' | |
| (.results // [])[] | |
| | select(.last_updated > $since) | |
| | select(.name | test("^v[0-9]")) | |
| | "\(.name)\t\(.last_updated)" | |
| ' 2>/dev/null | while IFS=$'\t' read -r tag updated; do | |
| local tag_ver | |
| tag_ver=$(echo "$tag" | grep -oP 'v\d+\.\d+\.\d+(\.\w+)?' || true) | |
| [[ -z "$tag_ver" ]] && continue | |
| [[ "$tag" == *nightly* || "$tag" == *dev* ]] && continue | |
| # Only report tags whose normalized suffix matches one already in use | |
| local tag_suffix | |
| tag_suffix=$(normalize_suffix "${tag#"$tag_ver"}") | |
| local matched=false | |
| for s in $allowed_suffixes; do | |
| if [[ "$tag_suffix" == "$s" ]]; then | |
| matched=true | |
| break | |
| fi | |
| done | |
| [[ "$matched" == false ]] && continue | |
| local higher | |
| higher=$(printf '%s\n%s\n' "$current_ver" "$tag_ver" | sort -V | tail -1) | |
| if [[ "$higher" == "$tag_ver" && "$higher" != "$current_ver" ]]; then | |
| echo " Found update: $tag ($tag_ver > $current_ver)" >&2 | |
| echo "${repo}|${tag}|${tag_ver}|${current_ver}|${updated}" | |
| fi | |
| done || true | |
| } | |
| for repo in "${!CURRENT_VERSIONS[@]}"; do | |
| current="${CURRENT_VERSIONS[$repo]}" | |
| suffixes="${REPO_SUFFIXES[$repo]:-}" | |
| check_dockerhub "$repo" "$current" "$suffixes" >> "$FOUND_UPDATES" || true | |
| done | |
| UPDATES=$(grep '|' "$FOUND_UPDATES" | sort -u || true) | |
| rm -f "$FOUND_UPDATES" | |
| if [[ -z "$UPDATES" ]]; then | |
| echo "No new releases found in the last 7 days." | |
| echo "has_updates=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "has_updates=true" >> "$GITHUB_OUTPUT" | |
| # Build issue body and write to file | |
| BODY_FILE=$(mktemp) | |
| { | |
| echo "## Docker Image Updates Available" | |
| echo "" | |
| echo "New release tags have been detected on Docker Hub within the last 7 days." | |
| echo "" | |
| echo "### Updates Found" | |
| echo "" | |
| echo "| Image | New Tag | New Version | Current Version | Published |" | |
| echo "|-------|---------|-------------|-----------------|-----------|" | |
| while IFS='|' read -r repo tag tag_ver cur_ver updated; do | |
| pub_date=$(echo "$updated" | cut -dT -f1) | |
| echo "| \`$repo\` | \`$tag\` | $tag_ver | $cur_ver | $pub_date |" | |
| done <<< "$UPDATES" | |
| echo "" | |
| echo "---" | |
| echo "" | |
| echo "@claude Please update the configurations:" | |
| echo "" | |
| echo "1. Update image tags in \`.github/configs/nvidia-master.yaml\` and/or \`.github/configs/amd-master.yaml\`" | |
| echo "2. Add entries to \`perf-changelog.yaml\` documenting the version changes" | |
| echo "3. Create separate PRs grouped by framework and image family, and link each PR." | |
| echo "" | |
| echo "Group by framework plus CUDA/ROCm image family (for example sglang-cuda, sglang-rocm, vllm-cuda, vllm-rocm, atom, trt), not by individual GPU. Split by GPU only when an update is genuinely hardware-specific." | |
| echo "" | |
| echo "Focus on updating single-node configurations. For each framework/image family, check if there are multiple CUDA/ROCm versions available and choose appropriately based on current usage patterns in the configs." | |
| } > "$BODY_FILE" | |
| echo "body_file=$BODY_FILE" >> "$GITHUB_OUTPUT" | |
| echo "=== Issue body ===" | |
| cat "$BODY_FILE" | |
| - name: Create issue for updates | |
| if: steps.check.outputs.has_updates == 'true' && inputs.dry_run != true | |
| env: | |
| GH_TOKEN: ${{ secrets.CLAUDE_PAT }} | |
| run: | | |
| DATE=$(date +%Y-%m-%d) | |
| BODY_FILE="${{ steps.check.outputs.body_file }}" | |
| EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --label "docker-update" --state open --json number --jq '.[0].number // empty') | |
| if [[ -n "$EXISTING" ]]; then | |
| echo "Updating existing issue #$EXISTING" | |
| jq -Rs '{body: .}' "$BODY_FILE" \ | |
| | gh api --method POST "repos/$GITHUB_REPOSITORY/issues/$EXISTING/comments" --input - | |
| else | |
| jq -n \ | |
| --arg title "[Auto] Docker Image Updates Available - $DATE" \ | |
| --rawfile body "$BODY_FILE" \ | |
| '{title: $title, body: $body, labels: ["docker-update", "claude-task"]}' \ | |
| | gh api --method POST "repos/$GITHUB_REPOSITORY/issues" --input - | |
| fi | |
| - name: Dry run output | |
| if: steps.check.outputs.has_updates == 'true' && inputs.dry_run == true | |
| run: | | |
| echo "## Dry Run - Updates Found" | |
| cat "${{ steps.check.outputs.body_file }}" |