Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/sdk-proto-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

name: SDK Proto Check

on:
merge_group:
types: [checks_requested]
push:
branches:
- "pull-request/[0-9]+"
workflow_dispatch:

env:
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

permissions:
contents: read
packages: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
pr_metadata:
name: Resolve PR metadata
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
should_run: ${{ steps.gate.outputs.should_run }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- id: gate
uses: ./.github/actions/pr-gate

sdk_proto_drift:
name: Proto Drift (${{ matrix.sdk.name }})
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: linux-amd64-cpu8
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
strategy:
fail-fast: false
matrix:
sdk:
- name: go
drift_task: "go:proto:drift"
- name: typescript
drift_task: "sdk:ts:proto:drift"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install tools
run: mise install --locked

- name: Check proto drift
id: drift
run: |
REPORT=$(mise run ${{ matrix.sdk.drift_task }} 2>"$RUNNER_TEMP/drift_stderr.log") || true

if echo "$REPORT" | jq -e '.synced' >/dev/null 2>&1; then
SYNCED=$(echo "$REPORT" | jq -r '.synced')
{
echo "report<<REPORT_EOF"
echo "$REPORT"
echo "REPORT_EOF"
} >> "$GITHUB_OUTPUT"
echo "synced=$SYNCED" >> "$GITHUB_OUTPUT"
else
echo "::warning::Proto drift check failed: unable to parse report"
echo "stderr: $(cat "$RUNNER_TEMP/drift_stderr.log")"
echo "synced=error" >> "$GITHUB_OUTPUT"
fi

- name: Annotate drift warning
if: steps.drift.outputs.synced == 'false'
env:
DRIFT_REPORT: ${{ steps.drift.outputs.report }}
SDK_NAME: ${{ matrix.sdk.name }}
run: |
SUMMARY=$(echo "$DRIFT_REPORT" | jq -r '.summary')
FILES=$(echo "$DRIFT_REPORT" | jq -r '.files[] | select(.status != "synced") | " - \(.name) (\(.status), \(.diff_lines) lines changed)"' | sed ':a;N;$!ba;s/\n/%0A/g')
echo "::warning::SDK proto drift detected for ${SDK_NAME}: ${SUMMARY}%0A${FILES}"
181 changes: 181 additions & 0 deletions .github/workflows/sdk-sync-dashboard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

name: SDK Proto Sync

on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:

env:
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

permissions:
actions: read
contents: read
packages: read
issues: write

concurrency:
group: sdk-proto-sync
cancel-in-progress: true

jobs:
sdk_sync_check:
name: Sync Check
runs-on: linux-amd64-cpu8
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
outputs:
go_drift_report: ${{ steps.go_drift.outputs.report }}
go_has_drift: ${{ steps.go_drift.outputs.has_drift }}
go_build_report: ${{ steps.go_build.outputs.report }}
go_build_failed: ${{ steps.go_build.outputs.build_failed || 'false' }}
ts_drift_report: ${{ steps.ts_drift.outputs.report }}
ts_has_drift: ${{ steps.ts_drift.outputs.has_drift }}
ts_build_report: ${{ steps.ts_build.outputs.report }}
ts_build_failed: ${{ steps.ts_build.outputs.build_failed || 'false' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install tools
run: mise install --locked

- name: Check Go proto drift
id: go_drift
run: |
REPORT=$(mise run go:proto:drift 2>"$RUNNER_TEMP/go_drift_stderr.log") || true
if echo "$REPORT" | jq -e '.synced' >/dev/null 2>&1; then
SYNCED=$(echo "$REPORT" | jq -r '.synced')
{
echo "report<<REPORT_EOF"
echo "$REPORT"
echo "REPORT_EOF"
} >> "$GITHUB_OUTPUT"
[ "$SYNCED" = "true" ] && echo "has_drift=false" >> "$GITHUB_OUTPUT" || echo "has_drift=true" >> "$GITHUB_OUTPUT"
else
echo "::error::Go proto drift check failed"
echo "stderr: $(cat "$RUNNER_TEMP/go_drift_stderr.log")"
echo "report={}" >> "$GITHUB_OUTPUT"
echo "has_drift=error" >> "$GITHUB_OUTPUT"
fi

- name: Go build check
id: go_build
if: steps.go_drift.outputs.has_drift == 'true'
run: |
REPORT=$(mise run go:proto:build-check 2>"$RUNNER_TEMP/go_build_stderr.log") && BUILD_OK=true || BUILD_OK=false
if echo "$REPORT" | jq -e '.sdk' >/dev/null 2>&1; then
{ echo "report<<REPORT_EOF"; echo "$REPORT"; echo "REPORT_EOF"; } >> "$GITHUB_OUTPUT"
else
echo "stderr: $(cat "$RUNNER_TEMP/go_build_stderr.log")"
echo "report={}" >> "$GITHUB_OUTPUT"
fi
[ "$BUILD_OK" = "true" ] && echo "build_failed=false" >> "$GITHUB_OUTPUT" || echo "build_failed=true" >> "$GITHUB_OUTPUT"

- name: Check TypeScript proto drift
id: ts_drift
run: |
REPORT=$(mise run sdk:ts:proto:drift 2>"$RUNNER_TEMP/ts_drift_stderr.log") || true
if echo "$REPORT" | jq -e '.synced' >/dev/null 2>&1; then
SYNCED=$(echo "$REPORT" | jq -r '.synced')
{
echo "report<<REPORT_EOF"
echo "$REPORT"
echo "REPORT_EOF"
} >> "$GITHUB_OUTPUT"
[ "$SYNCED" = "true" ] && echo "has_drift=false" >> "$GITHUB_OUTPUT" || echo "has_drift=true" >> "$GITHUB_OUTPUT"
else
echo "::error::TypeScript proto drift check failed"
echo "stderr: $(cat "$RUNNER_TEMP/ts_drift_stderr.log")"
echo "report={}" >> "$GITHUB_OUTPUT"
echo "has_drift=error" >> "$GITHUB_OUTPUT"
fi

- name: TypeScript build check
id: ts_build
if: steps.ts_drift.outputs.has_drift == 'true'
run: |
REPORT=$(mise run sdk:ts:proto:build-check 2>"$RUNNER_TEMP/ts_build_stderr.log") && BUILD_OK=true || BUILD_OK=false
if echo "$REPORT" | jq -e '.sdk' >/dev/null 2>&1; then
{ echo "report<<REPORT_EOF"; echo "$REPORT"; echo "REPORT_EOF"; } >> "$GITHUB_OUTPUT"
else
echo "stderr: $(cat "$RUNNER_TEMP/ts_build_stderr.log")"
echo "report={}" >> "$GITHUB_OUTPUT"
fi
[ "$BUILD_OK" = "true" ] && echo "build_failed=false" >> "$GITHUB_OUTPUT" || echo "build_failed=true" >> "$GITHUB_OUTPUT"

issue_management:
name: Manage Drift Issue (${{ matrix.sdk.name }})
needs: sdk_sync_check
if: always() && needs.sdk_sync_check.result == 'success'
runs-on: linux-amd64-cpu8
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
strategy:
fail-fast: false
matrix:
sdk:
- name: go
label: "sdk:go:sync"
has_drift: ${{ needs.sdk_sync_check.outputs.go_has_drift }}
build_failed: ${{ needs.sdk_sync_check.outputs.go_build_failed }}
drift_report: ${{ needs.sdk_sync_check.outputs.go_drift_report }}
build_report: ${{ needs.sdk_sync_check.outputs.go_build_report }}
- name: typescript
label: "sdk:typescript:sync"
has_drift: ${{ needs.sdk_sync_check.outputs.ts_has_drift }}
build_failed: ${{ needs.sdk_sync_check.outputs.ts_build_failed }}
drift_report: ${{ needs.sdk_sync_check.outputs.ts_drift_report }}
build_report: ${{ needs.sdk_sync_check.outputs.ts_build_report }}
steps:
- name: Warn on drift detection error
if: matrix.sdk.has_drift == 'error'
run: |
echo "::error::Drift detection failed for ${{ matrix.sdk.name }} SDK — check the sdk_sync_check job logs"
exit 1

- if: matrix.sdk.has_drift == 'true' && matrix.sdk.build_failed == 'true'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install tools
if: matrix.sdk.has_drift == 'true' && matrix.sdk.build_failed == 'true'
run: mise install --locked

- name: Create or update drift issue
if: matrix.sdk.has_drift == 'true' && matrix.sdk.build_failed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRIFT_REPORT: ${{ matrix.sdk.drift_report }}
BUILD_REPORT: ${{ matrix.sdk.build_report }}
run: |
RESULT=$(uv run python tasks/scripts/sdk_sync.py manage-issue \
--drift-report "${DRIFT_REPORT:-{}}" \
--build-report "${BUILD_REPORT:-{}}" \
--sdk "${{ matrix.sdk.name }}" \
--repo "$GITHUB_REPOSITORY" \
--label "${{ matrix.sdk.label }}")
echo "$RESULT" | jq .
ACTION=$(echo "$RESULT" | jq -r '.action // "unknown"')
if [ "$ACTION" = "error" ] || [ "$ACTION" = "unknown" ]; then
echo "::error::Issue management for ${{ matrix.sdk.name }} failed: $ACTION"
exit 1
fi

- name: Close resolved drift issue
if: matrix.sdk.has_drift == 'false' || (matrix.sdk.has_drift == 'true' && matrix.sdk.build_failed == 'false')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ISSUE=$(gh issue list --repo "$GITHUB_REPOSITORY" --label "${{ matrix.sdk.label }}" --state open --json number --jq '.[0].number')
if [ -n "$ISSUE" ]; then
gh issue close "$ISSUE" --repo "$GITHUB_REPOSITORY" --comment "SDK builds and tests pass after proto regeneration. Closing automatically."
echo "Closed issue #$ISSUE"
fi
71 changes: 71 additions & 0 deletions tasks/go.toml
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,74 @@ fi
echo "Proto check passed: generated files are up to date."
"""
hide = true

["go:proto:drift"]
description = "Check Go SDK proto drift and output a JSON report"
dir = "sdk/go"
run = """
#!/usr/bin/env bash
set -euo pipefail

SDK_ROOT=$(pwd -P)
REPO_ROOT=$(cd ../.. && pwd -P)

for tool in buf protoc-gen-go protoc-gen-go-grpc jq; do
if ! command -v "$tool" &>/dev/null; then
echo '{"sdk":"go","synced":false,"error":"'"$tool"' not found"}'
exit 1
fi
done

WORK_DIR=$(mktemp -d)
trap 'rm -rf "$WORK_DIR"' EXIT

CHECK_TEMPLATE=$(sed 's|out: sdk/go|out: '"$WORK_DIR"'|' buf.gen.yaml)
if ! (cd "$REPO_ROOT" && buf generate --template "$CHECK_TEMPLATE") >/dev/null 2>&1; then
jq -n -c --arg sdk "go" '{sdk:$sdk, synced:false, files:[], summary:"buf generate failed"}'
exit 1
fi

NDJSON_FILE=$(mktemp)

for f in $(find "$WORK_DIR/proto" -name '*.go' -type f | sort); do
REL=${f#"$WORK_DIR/proto/"}
COMMITTED="$SDK_ROOT/proto/$REL"

if [ ! -f "$COMMITTED" ]; then
printf '%s\t%s\t%s\n' "$REL" "added" "0" >> "$NDJSON_FILE"
else
DIFF_LINES=$(diff -u "$COMMITTED" "$f" 2>/dev/null | wc -l | tr -d ' ') || true
if [ "$DIFF_LINES" -gt 0 ]; then
printf '%s\t%s\t%s\n' "$REL" "modified" "$DIFF_LINES" >> "$NDJSON_FILE"
fi
fi
done

for f in $(find "$SDK_ROOT/proto" -name '*.go' -type f | sort); do
REL=${f#"$SDK_ROOT/proto/"}
REGEN="$WORK_DIR/proto/$REL"
if [ ! -f "$REGEN" ]; then
printf '%s\t%s\t%s\n' "$REL" "removed" "0" >> "$NDJSON_FILE"
fi
done

jq -R -c -s --arg sdk "go" '
split("\n") | map(select(length > 0) | split("\t") |
{name: .[0], status: .[1], diff_lines: (.[2] | tonumber)}) |
{sdk: $sdk, synced: (length == 0), files: .,
summary: (if length == 0 then "all files synced"
else "\\(length) file(s) drifted" end)}
' "$NDJSON_FILE"

DRIFTED=$(wc -l < "$NDJSON_FILE" | tr -d ' ')
rm -f "$NDJSON_FILE"

[ "$DRIFTED" -eq 0 ] && exit 0 || exit 1
"""
hide = true

["go:proto:build-check"]
description = "Run full Go SDK proto sync, generate, build, and test pipeline"
dir = "sdk/go"
run = 'bash ../../tasks/scripts/sdk_build_check.sh go gen=go:proto:gen build=go:build test=go:test'
hide = true
35 changes: 35 additions & 0 deletions tasks/scripts/sdk_build_check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

SDK="${1:?Usage: sdk_build_check.sh <sdk> <step1=task1> [step2=task2] ...}"
shift

PAIRS=("$@")
LOG_FILE=$(mktemp)
trap 'rm -f "$LOG_FILE"' EXIT

FAILED_STEP=""
for pair in "${PAIRS[@]}"; do
STEP="${pair%%=*}"
TASK="${pair#*=}"

if ! mise run "$TASK" >> "$LOG_FILE" 2>&1; then
FAILED_STEP="$STEP"
break
fi
done

LOG_CONTENT=$(tail -500 "$LOG_FILE")

if [ -z "$FAILED_STEP" ]; then
jq -n -c --arg sdk "$SDK" \
'{sdk: $sdk, success: true, failed_step: null, log: ""}'
else
jq -n -c --arg sdk "$SDK" --arg step "$FAILED_STEP" --arg log "$LOG_CONTENT" \
'{sdk: $sdk, success: false, failed_step: $step, log: $log}'
exit 1
fi
Loading
Loading