Skip to content

Commit 5b15133

Browse files
committed
feat(ci): add SDK proto sync CI with drift detection and dashboard
Add automated proto drift detection for multi-language SDKs: - sdk_sync.py: Python script with six subcommands (drift-report, dashboard, issue-body, wiki-push, manage-issue, dispatch) for testable, lintable sync logic - sdk_sync_test.py: 22 pytest tests covering drift detection, dashboard generation, issue body formatting, log truncation, wiki push, issue management, repository dispatch, and agent prompt - go:proto:drift mise task: calls sdk_sync.py drift-report - go:proto:build-check mise task: runs full sync+gen+build+test pipeline - sdk-proto-check.yml: PR gate workflow with non-blocking warning annotations - sdk-sync-dashboard.yml: scheduled daily workflow for wiki dashboard, agent-actionable issue creation, and repository_dispatch Auto-created drift issues include an "Agent Instructions" section with a structured prompt that an AI agent can directly consume to fix the drift and create a PR. The prompt includes the scope, affected files, converter/types paths, fix steps, and commands to verify. Signed-off-by: Roland Huß <rhuss@redhat.com>
1 parent d556748 commit 5b15133

5 files changed

Lines changed: 1158 additions & 0 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: SDK Proto Check
2+
3+
on:
4+
push:
5+
branches:
6+
- "pull-request/[0-9]+"
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
packages: read
12+
13+
concurrency:
14+
group: ${{ github.workflow }}-${{ github.ref }}
15+
cancel-in-progress: true
16+
17+
jobs:
18+
pr_metadata:
19+
name: Resolve PR metadata
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
pull-requests: read
24+
outputs:
25+
should_run: ${{ steps.gate.outputs.should_run }}
26+
steps:
27+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
28+
29+
- id: gate
30+
uses: ./.github/actions/pr-gate
31+
32+
sdk_proto_drift:
33+
name: Proto Drift (${{ matrix.sdk.name }})
34+
needs: pr_metadata
35+
if: needs.pr_metadata.outputs.should_run == 'true'
36+
runs-on: ubuntu-latest
37+
container:
38+
image: ghcr.io/nvidia/openshell/ci:latest
39+
credentials:
40+
username: ${{ github.actor }}
41+
password: ${{ secrets.GITHUB_TOKEN }}
42+
strategy:
43+
fail-fast: false
44+
matrix:
45+
sdk:
46+
- name: go
47+
drift_task: "go:proto:drift"
48+
steps:
49+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
50+
51+
- name: Install tools
52+
run: mise install --locked
53+
54+
- name: Check proto drift
55+
id: drift
56+
run: |
57+
REPORT=$(uv run python tasks/scripts/sdk_sync.py drift-report \
58+
--sdk ${{ matrix.sdk.name }} \
59+
--upstream-path proto \
60+
--sdk-path sdk/${{ matrix.sdk.name }}/proto 2>&1) || true
61+
62+
if echo "$REPORT" | jq -e '.synced' >/dev/null 2>&1; then
63+
SYNCED=$(echo "$REPORT" | jq -r '.synced')
64+
{
65+
echo "report<<REPORT_EOF"
66+
echo "$REPORT"
67+
echo "REPORT_EOF"
68+
} >> "$GITHUB_OUTPUT"
69+
echo "synced=$SYNCED" >> "$GITHUB_OUTPUT"
70+
else
71+
echo "::warning::Proto drift check failed: $REPORT"
72+
echo "synced=error" >> "$GITHUB_OUTPUT"
73+
fi
74+
75+
- name: Annotate drift warning
76+
if: steps.drift.outputs.synced == 'false'
77+
env:
78+
DRIFT_REPORT: ${{ steps.drift.outputs.report }}
79+
SDK_NAME: ${{ matrix.sdk.name }}
80+
run: |
81+
SUMMARY=$(echo "$DRIFT_REPORT" | jq -r '.summary')
82+
FILES=$(echo "$DRIFT_REPORT" | jq -r '.files[] | select(.status != "synced") | " - \(.name) (\(.status), \(.diff_lines) lines changed)"' | sed ':a;N;$!ba;s/\n/%0A/g')
83+
echo "::warning::SDK proto drift detected for ${SDK_NAME}: ${SUMMARY}%0A${FILES}"
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
name: SDK Sync Dashboard
2+
3+
on:
4+
schedule:
5+
- cron: "0 6 * * *"
6+
workflow_dispatch:
7+
8+
permissions:
9+
actions: read
10+
contents: write
11+
packages: read
12+
issues: write
13+
14+
concurrency:
15+
group: sdk-sync-dashboard
16+
cancel-in-progress: true
17+
18+
jobs:
19+
sdk_sync_check:
20+
name: Sync Check (${{ matrix.sdk.name }})
21+
runs-on: ubuntu-latest
22+
container:
23+
image: ghcr.io/nvidia/openshell/ci:latest
24+
credentials:
25+
username: ${{ github.actor }}
26+
password: ${{ secrets.GITHUB_TOKEN }}
27+
strategy:
28+
fail-fast: false
29+
matrix:
30+
sdk:
31+
- name: go
32+
build_task: "go:proto:build-check"
33+
label: "sdk-sync:go"
34+
outputs:
35+
drift_report: ${{ steps.drift.outputs.report }}
36+
has_drift: ${{ steps.drift.outputs.has_drift }}
37+
build_report: ${{ steps.build.outputs.report || '' }}
38+
build_failed: ${{ steps.build.outputs.build_failed || 'false' }}
39+
steps:
40+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
41+
42+
- name: Install tools
43+
run: mise install --locked
44+
45+
- name: Check proto drift
46+
id: drift
47+
run: |
48+
REPORT=$(uv run python tasks/scripts/sdk_sync.py drift-report \
49+
--sdk ${{ matrix.sdk.name }} \
50+
--upstream-path proto \
51+
--sdk-path sdk/${{ matrix.sdk.name }}/proto 2>/dev/null) || true
52+
53+
if echo "$REPORT" | jq -e '.synced' >/dev/null 2>&1; then
54+
SYNCED=$(echo "$REPORT" | jq -r '.synced')
55+
{
56+
echo "report<<REPORT_EOF"
57+
echo "$REPORT"
58+
echo "REPORT_EOF"
59+
} >> "$GITHUB_OUTPUT"
60+
[ "$SYNCED" = "true" ] && echo "has_drift=false" >> "$GITHUB_OUTPUT" || echo "has_drift=true" >> "$GITHUB_OUTPUT"
61+
else
62+
echo "::error::Proto drift check failed"
63+
echo "report={}" >> "$GITHUB_OUTPUT"
64+
echo "has_drift=error" >> "$GITHUB_OUTPUT"
65+
fi
66+
67+
- name: Run build check
68+
id: build
69+
if: steps.drift.outputs.has_drift == 'true'
70+
env:
71+
BUILD_TASK: ${{ matrix.sdk.build_task }}
72+
run: |
73+
REPORT=$(mise run "$BUILD_TASK" 2>&1) && BUILD_OK=true || BUILD_OK=false
74+
if echo "$REPORT" | jq -e '.sdk' >/dev/null 2>&1; then
75+
{ echo "report<<REPORT_EOF"; echo "$REPORT"; echo "REPORT_EOF"; } >> "$GITHUB_OUTPUT"
76+
else
77+
echo "report={}" >> "$GITHUB_OUTPUT"
78+
fi
79+
[ "$BUILD_OK" = "true" ] && echo "build_failed=false" >> "$GITHUB_OUTPUT" || echo "build_failed=true" >> "$GITHUB_OUTPUT"
80+
81+
wiki_dashboard:
82+
name: Update Wiki Dashboard
83+
needs: sdk_sync_check
84+
if: always() && needs.sdk_sync_check.result != 'cancelled'
85+
runs-on: ubuntu-latest
86+
steps:
87+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
88+
89+
- name: Install tools
90+
run: mise install --locked
91+
92+
- name: Generate and push dashboard
93+
env:
94+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
95+
DRIFT_REPORT: ${{ needs.sdk_sync_check.outputs.drift_report }}
96+
BUILD_REPORT: ${{ needs.sdk_sync_check.outputs.build_report }}
97+
run: |
98+
DASHBOARD=$(mktemp)
99+
uv run python tasks/scripts/sdk_sync.py dashboard \
100+
--drift-report "${DRIFT_REPORT:-{}}" \
101+
--build-report "${BUILD_REPORT:-{}}" \
102+
--output "$DASHBOARD"
103+
104+
uv run python tasks/scripts/sdk_sync.py wiki-push \
105+
--content "$DASHBOARD" \
106+
--page-name SDK-Sync-Status \
107+
--repo "$GITHUB_REPOSITORY"
108+
109+
issue_management:
110+
name: Manage Drift Issues
111+
needs: sdk_sync_check
112+
if: needs.sdk_sync_check.outputs.has_drift == 'true' && needs.sdk_sync_check.outputs.build_failed == 'true'
113+
runs-on: ubuntu-latest
114+
outputs:
115+
issue_url: ${{ steps.issue.outputs.issue_url }}
116+
steps:
117+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
118+
119+
- name: Install tools
120+
run: mise install --locked
121+
122+
- name: Create or update drift issue
123+
id: issue
124+
env:
125+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
126+
DRIFT_REPORT: ${{ needs.sdk_sync_check.outputs.drift_report }}
127+
BUILD_REPORT: ${{ needs.sdk_sync_check.outputs.build_report }}
128+
run: |
129+
RESULT=$(uv run python tasks/scripts/sdk_sync.py manage-issue \
130+
--drift-report "${DRIFT_REPORT:-{}}" \
131+
--build-report "${BUILD_REPORT:-{}}" \
132+
--sdk go \
133+
--repo "$GITHUB_REPOSITORY" \
134+
--label "sdk-sync:go")
135+
echo "issue_url=$(echo "$RESULT" | jq -r '.issue_url')" >> "$GITHUB_OUTPUT"
136+
137+
- name: Fire repository_dispatch
138+
if: steps.issue.outputs.issue_url != ''
139+
env:
140+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
141+
DRIFT_REPORT: ${{ needs.sdk_sync_check.outputs.drift_report }}
142+
run: |
143+
DRIFT_SUMMARY=$(echo "${DRIFT_REPORT:-{}}" | jq -r '.summary // "unknown"')
144+
uv run python tasks/scripts/sdk_sync.py dispatch \
145+
--repo "$GITHUB_REPOSITORY" \
146+
--issue-url "${{ steps.issue.outputs.issue_url }}" \
147+
--sdk go \
148+
--drift-summary "$DRIFT_SUMMARY"

tasks/go.toml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# Go SDK proto drift detection tasks
5+
# NOTE: This file is merged with the full tasks/go.toml from PR #2271.
6+
# The drift and build-check tasks below extend the Go SDK task set.
7+
8+
["go:proto:drift"]
9+
description = "Check Go SDK proto files for drift against root proto definitions"
10+
run = "uv run python tasks/scripts/sdk_sync.py drift-report --sdk go --upstream-path proto --sdk-path sdk/go/proto"
11+
hide = true
12+
13+
["go:proto:build-check"]
14+
description = "Run full Go SDK proto sync, generate, build, and test pipeline"
15+
dir = "sdk/go"
16+
run = '''
17+
#!/usr/bin/env bash
18+
set -euo pipefail
19+
20+
STEP_NAMES=("sync" "gen" "build" "test")
21+
STEP_TASKS=("go:proto:sync" "go:proto:gen" "go:build" "go:test")
22+
LOG_FILE=$(mktemp)
23+
trap 'rm -f "$LOG_FILE"' EXIT
24+
25+
FAILED_STEP=""
26+
for i in "${!STEP_NAMES[@]}"; do
27+
STEP="${STEP_NAMES[$i]}"
28+
TASK="${STEP_TASKS[$i]}"
29+
30+
if ! mise run "$TASK" > "$LOG_FILE" 2>&1; then
31+
FAILED_STEP="$STEP"
32+
break
33+
fi
34+
done
35+
36+
LOG_CONTENT=$(tail -500 "$LOG_FILE")
37+
38+
if [ -z "$FAILED_STEP" ]; then
39+
jq -n -c --arg sdk "go" \
40+
'{sdk: $sdk, success: true, failed_step: null, log: ""}'
41+
else
42+
jq -n -c --arg sdk "go" --arg step "$FAILED_STEP" --arg log "$LOG_CONTENT" \
43+
'{sdk: $sdk, success: false, failed_step: $step, log: $log}'
44+
exit 1
45+
fi
46+
'''
47+
hide = true

0 commit comments

Comments
 (0)