-
Notifications
You must be signed in to change notification settings - Fork 1.3k
ci(sdk): add proto drift detection and sync notifications #3123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # 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 | ||
| timeout-minutes: 5 | ||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
| outputs: | ||
| should_run: ${{ steps.gate.outputs.should_run }} | ||
| matrix: ${{ steps.config.outputs.matrix }} | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
|
|
||
| - id: gate | ||
| uses: ./.github/actions/pr-gate | ||
|
|
||
| - id: config | ||
| name: Load SDK configuration | ||
| run: echo "matrix=$(jq -c '.include' tasks/sdk-sync-config.json)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| 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 | ||
| timeout-minutes: 15 | ||
| container: | ||
| image: ghcr.io/nvidia/openshell/ci:latest | ||
| credentials: | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| sdk: ${{ fromJSON(needs.pr_metadata.outputs.matrix) }} | ||
| 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 'has("synced") and (.synced | type == "boolean")' >/dev/null 2>&1; then | ||
| SYNCED=$(echo "$REPORT" | jq -r '.synced') | ||
| DELIMITER="REPORT_EOF_$(openssl rand -hex 16)" | ||
| { | ||
| echo "report<<$DELIMITER" | ||
| echo "$REPORT" | ||
| echo "$DELIMITER" | ||
| } >> "$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}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| # 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: | ||
| load_config: | ||
| name: Load SDK configuration | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| outputs: | ||
| matrix: ${{ steps.config.outputs.matrix }} | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| - id: config | ||
| run: echo "matrix=$(jq -c '.include' tasks/sdk-sync-config.json)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| sdk_sync_check: | ||
|
Ygnas marked this conversation as resolved.
|
||
| name: Sync Check (${{ matrix.sdk.name }}) | ||
| needs: load_config | ||
| runs-on: linux-amd64-cpu8 | ||
| timeout-minutes: 30 | ||
| container: | ||
| image: ghcr.io/nvidia/openshell/ci:latest | ||
| credentials: | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| sdk: ${{ fromJSON(needs.load_config.outputs.matrix) }} | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| - name: Install tools | ||
| run: mise install --locked | ||
|
Ygnas marked this conversation as resolved.
|
||
| - name: Check proto drift and build | ||
| env: | ||
| SDK_NAME: ${{ matrix.sdk.name }} | ||
| DRIFT_TASK: ${{ matrix.sdk.drift_task }} | ||
| BUILD_CHECK_TASK: ${{ matrix.sdk.build_check_task }} | ||
| run: | | ||
| mkdir -p report | ||
| mise run "$DRIFT_TASK" > report/drift.json 2> report/drift.stderr || true | ||
| if ! jq -e 'has("synced") and (.synced | type == "boolean")' report/drift.json >/dev/null 2>&1; then | ||
| jq -n --arg sdk "$SDK_NAME" '{sdk:$sdk, has_drift:"error", build_failed:"false"}' > report/status.json | ||
| exit 0 | ||
| fi | ||
| SYNCED=$(jq -r '.synced' report/drift.json) | ||
| if [ "$SYNCED" = "true" ]; then | ||
| jq -n --arg sdk "$SDK_NAME" '{sdk:$sdk, has_drift:"false", build_failed:"false"}' > report/status.json | ||
| exit 0 | ||
| fi | ||
| if mise run "$BUILD_CHECK_TASK" > report/build.json 2> report/build.stderr; then | ||
| BUILD_FAILED=false | ||
| else | ||
| BUILD_FAILED=true | ||
| fi | ||
| jq -n --arg sdk "$SDK_NAME" --arg build_failed "$BUILD_FAILED" \ | ||
| '{sdk:$sdk, has_drift:"true", build_failed:$build_failed}' > report/status.json | ||
| - name: Upload SDK report | ||
| if: always() | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | ||
| with: | ||
| name: sdk-sync-${{ matrix.sdk.name }} | ||
| path: report/ | ||
| if-no-files-found: error | ||
| retention-days: 1 | ||
|
|
||
| issue_management: | ||
| name: Manage Drift Issue (${{ matrix.sdk.name }}) | ||
| needs: [load_config, sdk_sync_check] | ||
| if: always() && needs.load_config.result == 'success' && needs.sdk_sync_check.result == 'success' | ||
| runs-on: linux-amd64-cpu8 | ||
| timeout-minutes: 5 | ||
| container: | ||
| image: ghcr.io/nvidia/openshell/ci:latest | ||
| credentials: | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| sdk: ${{ fromJSON(needs.load_config.outputs.matrix) }} | ||
| steps: | ||
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | ||
| with: | ||
| name: sdk-sync-${{ matrix.sdk.name }} | ||
| path: report | ||
| - name: Read SDK status | ||
| id: status | ||
| run: | | ||
| echo "has_drift=$(jq -r '.has_drift' report/status.json)" >> "$GITHUB_OUTPUT" | ||
| echo "build_failed=$(jq -r '.build_failed' report/status.json)" >> "$GITHUB_OUTPUT" | ||
| - name: Warn on drift detection error | ||
| if: steps.status.outputs.has_drift == 'error' | ||
| run: | | ||
| echo "::error::Drift detection failed for ${{ matrix.sdk.name }} SDK" | ||
| cat report/drift.stderr | ||
| exit 1 | ||
| - if: steps.status.outputs.has_drift == 'true' && steps.status.outputs.build_failed == 'true' | ||
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| - name: Install tools | ||
| if: steps.status.outputs.has_drift == 'true' && steps.status.outputs.build_failed == 'true' | ||
| run: mise install --locked | ||
| - name: Create or update drift issue | ||
| if: steps.status.outputs.has_drift == 'true' && steps.status.outputs.build_failed == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| RESULT=$(uv run python tasks/scripts/sdk_sync.py manage-issue \ | ||
| --drift-report "$(cat report/drift.json)" \ | ||
| --build-report "$(cat report/build.json)" \ | ||
| --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: steps.status.outputs.has_drift == 'false' || (steps.status.outputs.has_drift == 'true' && steps.status.outputs.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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,43 +138,17 @@ hide = true | |
| ["go:proto:check"] | ||
| description = "Verify generated Go SDK proto files are up to date" | ||
| 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; do | ||
| if ! command -v "$tool" &>/dev/null; then | ||
| echo "ERROR: $tool not found. Run 'mise install' to install it." | ||
| exit 1 | ||
| fi | ||
| done | ||
|
|
||
| WORK_DIR=$(mktemp -d) | ||
| trap 'rm -rf "$WORK_DIR"' EXIT | ||
|
|
||
| if find proto -maxdepth 1 -name '*.proto' -print -quit | grep -q .; then | ||
| echo "ERROR: sdk/go/proto contains copied proto sources." | ||
| echo "Proto sources belong in the repository root proto/ directory." | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Generate to temp directory with adjusted output path | ||
| CHECK_TEMPLATE=$(sed 's|out: sdk/go|out: '"$WORK_DIR"'|' buf.gen.yaml) | ||
| (cd "$REPO_ROOT" && buf generate --template "$CHECK_TEMPLATE") | ||
|
|
||
| DIFF_OUTPUT=$(diff -r "$WORK_DIR/proto" "$SDK_ROOT/proto" 2>&1) || true | ||
| run = 'bash ../../tasks/scripts/go_proto_check.sh text' | ||
| hide = true | ||
|
Ygnas marked this conversation as resolved.
|
||
|
|
||
| if [ -n "$DIFF_OUTPUT" ]; then | ||
| echo "ERROR: Generated proto files are out of date." | ||
| echo "Run 'mise run go:proto:gen' to regenerate." | ||
| echo "" | ||
| echo "$DIFF_OUTPUT" | ||
| exit 1 | ||
| fi | ||
| ["go:proto:drift"] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as said above, I would align the naming convention along side the typescript task (and add a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as #3123 (comment)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, same reasoning as above. Resolving. |
||
| description = "Check Go SDK proto drift and output a JSON report" | ||
| dir = "sdk/go" | ||
| run = 'bash ../../tasks/scripts/go_proto_check.sh json' | ||
| hide = true | ||
|
|
||
| echo "Proto check passed: generated files are up to date." | ||
| """ | ||
| ["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 | ||
Uh oh!
There was an error while loading. Please reload this page.