Add the vendor VFIO vGPU device backend #293
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
| # Generates the Python, TypeScript, and Go SDKs with the self-hosted stlc CLI, | |
| # replacing the hosted-Stainless pipeline that stainless-sdks.yml used to drive. | |
| # | |
| # On pull_request: builds every target, pushes a stlc/preview/pr-N branch to each | |
| # SDK repo, and comments the build manifest. The branch is deleted when the PR | |
| # closes. | |
| # A workflow_dispatch with integration_test=true (allowed from any ref) runs | |
| # the same real generate job on a stlc/integration-test/run-<run id> branch in | |
| # each SDK repo and opens a do-not-merge draft PR per repo — never main, no | |
| # seal-back, no docs publish. See the runbook. | |
| # On push to main: rebuilds and pushes to each staging repo's main. The staging | |
| # promotion workflow fast-forwards production main, where release-please opens | |
| # the version + changelog PR. Merging that PR publishes the package, and | |
| # production releases fast-forward back into staging. | |
| # | |
| # Operator commands and recovery notes are in docs/runbooks/sdk-generation-stlc.md. | |
| name: Generate SDKs with stlc | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| paths: | |
| - "openapi.yaml" | |
| - "stainless.yaml" | |
| - "stainless/**" | |
| - ".github/workflows/stlc-generate.yml" | |
| - ".github/actions/setup-stlc/**" | |
| - ".github/scripts/normalize-sdk-commit-message*.sh" | |
| push: | |
| branches: [main] | |
| paths: | |
| - "openapi.yaml" | |
| - "stainless.yaml" | |
| - "stainless/**" | |
| - ".github/workflows/stlc-generate.yml" | |
| - ".github/actions/setup-stlc/**" | |
| - ".github/scripts/normalize-sdk-commit-message*.sh" | |
| workflow_dispatch: | |
| inputs: | |
| integration_test: | |
| description: "Push to stlc/integration-test/* branches and open draft PRs instead of regenerating main." | |
| type: boolean | |
| default: false | |
| concurrency: | |
| # Last write wins on PRs: a newer push cancels an in-flight preview build so a | |
| # slower run can't push stale SDK output over a newer one. Runs on main queue | |
| # instead — cancelling one midway can leave the SDK repos and the custom-code | |
| # tracking files here out of sync. | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| env: | |
| STAINLESS_WORKSPACE: stainless | |
| # Preview branch pushed to each SDK repo for a PR; deleted when the PR closes. | |
| PREVIEW_BRANCH: stlc/preview/pr-${{ github.event.pull_request.number }} | |
| # An integration-test dispatch pushes this branch instead of the preview | |
| # branch or main. | |
| INTEGRATION_BRANCH: stlc/integration-test/run-${{ github.run_id }} | |
| SDK_REPOS: hypeman-go-staging hypeman-python-staging hypeman-ts-staging | |
| SDK_TARGETS: go,python,typescript | |
| jobs: | |
| guard: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| skip: ${{ steps.check.outputs.skip }} | |
| steps: | |
| - name: Decide whether to build | |
| id: check | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| EVENT: ${{ github.event_name }} | |
| HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} | |
| REPO: ${{ github.repository }} | |
| REF: ${{ github.event.pull_request.head.sha || github.sha }} | |
| BEFORE: ${{ github.event.before }} | |
| AFTER: ${{ github.sha }} | |
| INTEGRATION_TEST: ${{ inputs.integration_test }} | |
| run: | | |
| set -euo pipefail | |
| # The workspace arrives with `stlc init --from-cloud` (see the runbook). | |
| # Until then this workflow is a no-op rather than a red X on every PR | |
| # — except an integration-test dispatch, which runs the real generator | |
| # and fails loudly instead. | |
| # Queried via the API because a closed PR's merge ref can be gone by | |
| # the time this runs, so a checkout is not reliable here. Only a 404 | |
| # means "not initialized" — anything else fails the job so a | |
| # transient API error can't silently skip generation on main. | |
| if ! err=$(gh api "repos/$REPO/contents/$STAINLESS_WORKSPACE/workspace.json?ref=$REF" 2>&1 >/dev/null); then | |
| if printf '%s\n' "$err" | grep -q 'HTTP 404'; then | |
| if [ "$INTEGRATION_TEST" = "true" ]; then | |
| echo "::error::No $STAINLESS_WORKSPACE/workspace.json at $REF — the stlc workspace is not initialized (runbook cutover step 4). Integration-test mode runs the real generator and cannot proceed without it." | |
| exit 1 | |
| fi | |
| echo "::notice::No $STAINLESS_WORKSPACE/workspace.json — stlc workspace not initialized yet, skipping." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| err=${err//$'\n'/ } | |
| echo "::error::Failed to check for $STAINLESS_WORKSPACE/workspace.json: $err" | |
| exit 1 | |
| fi | |
| # Generation needs secrets to push to the SDK repos, which a fork PR | |
| # does not get. Regenerate from a branch in this repo instead. A | |
| # deleted head repo reads as a fork. | |
| if [ "$EVENT" = "pull_request" ] && [ "$HEAD_REPO" != "$REPO" ]; then | |
| echo "::notice::Fork PR — no push credentials, skipping SDK generation." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # A manual run rebuilds each SDK repo's main, so it only makes sense | |
| # dispatched from main — unless it is an integration-test run, which | |
| # pushes test branches and is allowed from any ref. | |
| if [ "$EVENT" = "workflow_dispatch" ] && [ "$GITHUB_REF" != "refs/heads/main" ] && [ "$INTEGRATION_TEST" != "true" ]; then | |
| echo "::notice::workflow_dispatch from $GITHUB_REF — dispatch from main to regenerate the SDKs." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Merging the generated seal-back PR must not trigger another build. | |
| # Other tracking-only PRs still regenerate every target: they may | |
| # update only one target and leave the others' tracking stale. | |
| # The compare API caps the file list at 300, so a full-length list | |
| # might be truncated and can't prove the push was custom-code-only. | |
| if [ "$EVENT" = "push" ] && [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ]; then | |
| changed=$(gh api "repos/$REPO/compare/$BEFORE...$AFTER" --jq '.files[].filename') | |
| count=$(printf '%s\n' "$changed" | wc -l) | |
| if [ -n "$changed" ] && [ "$count" -lt 300 ] && ! printf '%s\n' "$changed" | grep -qvE "^${STAINLESS_WORKSPACE}/custom-code/"; then | |
| seal_back=$(gh api --method GET "repos/$REPO/pulls" \ | |
| -f state=closed \ | |
| -f head="${REPO%%/*}:stlc/seal-tracking" \ | |
| -f sort=updated \ | |
| -f direction=desc \ | |
| -f per_page=100 \ | |
| --jq 'any(.[]; .merged_at != null and .merge_commit_sha == env.AFTER)') | |
| if [ "$seal_back" = "true" ]; then | |
| echo "::notice::Only generated seal-back tracking files changed, skipping regeneration." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "::notice::Tracking files changed outside the generated seal-back PR; regenerating every target." | |
| fi | |
| fi | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| generate: | |
| needs: guard | |
| # An integration-test dispatch runs this same job but pushes the | |
| # integration-test branch instead of main and opens draft PRs from it. | |
| if: >- | |
| needs.guard.outputs.skip != 'true' && | |
| github.event.action != 'closed' && | |
| (github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || inputs.integration_test) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| # The build manifest comment on the PR. | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Test SDK commit message normalization | |
| run: .github/scripts/normalize-sdk-commit-message.test.sh | |
| - name: Mint app token for the SDK repos | |
| # Workflows:write because the generated SDKs contain .github/workflows. | |
| # Pull-requests:write for the integration-test draft PRs (action inputs | |
| # can't be conditional, so it is always requested). | |
| id: sdk-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.ADMIN_APP_ID }} | |
| private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }} | |
| owner: ${{ github.repository_owner }} | |
| repositories: hypeman-go,hypeman-python,hypeman-ts,hypeman-go-staging,hypeman-python-staging,hypeman-ts-staging | |
| permission-contents: write | |
| permission-workflows: write | |
| permission-pull-requests: write | |
| - name: Setup stlc | |
| uses: ./.github/actions/setup-stlc | |
| with: | |
| stlc-read-token: ${{ secrets.STLC_READ_TOKEN }} | |
| - name: Configure git auth for the SDK repo pushes | |
| env: | |
| GH_TOKEN: ${{ steps.sdk-token.outputs.token }} | |
| APP_SLUG: ${{ steps.sdk-token.outputs.app-slug }} | |
| run: | | |
| set -euo pipefail | |
| bot_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id) | |
| git config --global user.name "${APP_SLUG}[bot]" | |
| git config --global user.email "${bot_id}+${APP_SLUG}[bot]@users.noreply.github.com" | |
| gh auth setup-git | |
| - name: Resolve the SDK commit message | |
| # Every generated SDK change must be releasable without depending on | |
| # developers to use Conventional Commit prefixes. Preserve an explicit | |
| # release type when supplied; otherwise classify the change as a feature. | |
| id: msg | |
| if: github.event_name != 'pull_request' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| msg=$(gh api "repos/$REPO/commits/$SHA/pulls" --jq '.[0].title // empty') | |
| [ -n "$msg" ] || msg=$(git log -1 --pretty=%s) | |
| msg=$(.github/scripts/normalize-sdk-commit-message.sh "$msg") | |
| eof="MSG_$(openssl rand -hex 8)" | |
| { | |
| echo "commit_msg<<$eof" | |
| echo "$msg" | |
| echo "$eof" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Generate and validate SDKs | |
| id: build | |
| env: | |
| # A per-PR preview branch on pull_request, the integration-test branch | |
| # on an integration-test dispatch, the trunk otherwise. | |
| BRANCH: ${{ inputs.integration_test && env.INTEGRATION_BRANCH || (github.event_name == 'pull_request' && env.PREVIEW_BRANCH || github.ref_name) }} | |
| COMMIT_MSG: ${{ steps.msg.outputs.commit_msg }} | |
| GH_TOKEN: ${{ steps.sdk-token.outputs.token }} | |
| working-directory: ${{ env.STAINLESS_WORKSPACE }} | |
| run: | | |
| set -euo pipefail | |
| commit_args=() | |
| if [ -n "$COMMIT_MSG" ]; then | |
| commit_args=(--commit "$COMMIT_MSG") | |
| fi | |
| # Some of our sealed custom code repairs generated baseline code and | |
| # adds dependencies. Validate after integration, not before it. | |
| stlc build \ | |
| --branch "$BRANCH" \ | |
| --trunk-branch main \ | |
| --no-lint \ | |
| --targets all \ | |
| "${commit_args[@]}" | |
| stlc exec --targets "$SDK_TARGETS" -- ./scripts/bootstrap | |
| stlc lint --targets "$SDK_TARGETS" | |
| stlc test --targets "$SDK_TARGETS" | |
| stlc exec --targets "$SDK_TARGETS" -- sh -c \ | |
| 'status=$(git status --porcelain --untracked-files=all) && [ -z "$status" ] || { printf "%s\n" "$status" >&2; exit 1; }' | |
| # Re-running is deterministic and pushes the already-validated commits. | |
| stlc build \ | |
| --branch "$BRANCH" \ | |
| --trunk-branch main \ | |
| --no-lint \ | |
| --push \ | |
| --targets all | |
| - name: Report a pending seal-tracking PR on a custom-code conflict | |
| # Keep one actionable comment while an open tracking PR may explain the | |
| # conflict, and remove it once that condition no longer applies. | |
| if: always() | |
| env: | |
| BUILD_OUTCOME: ${{ steps.build.outcome }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.pull_request.number }} | |
| run: | | |
| set -euo pipefail | |
| existing="" | |
| if [ -n "$PR" ]; then | |
| existing=$(gh api "repos/$REPO/issues/$PR/comments" --paginate \ | |
| --jq '[.[] | select(.body | contains("<!-- stlc-seal-pending -->")) | .id][0] // empty') | |
| existing=${existing%%$'\n'*} | |
| fi | |
| seal_pr="" | |
| if [ "$BUILD_OUTCOME" = "failure" ]; then | |
| status=$(cd "$STAINLESS_WORKSPACE" && stlc status 2>&1 || true) | |
| if printf '%s\n' "$status" | grep -qi conflict; then | |
| seal_pr=$(gh pr list --repo "$REPO" --head stlc/seal-tracking --state open --json url --jq '.[0].url // empty') | |
| fi | |
| fi | |
| if [ -n "$seal_pr" ]; then | |
| echo "::error::stlc encountered a custom-code conflict while a tracking-file sync PR is open: $seal_pr. Stale tracking files may be the cause. Merge that PR, update the affected branch from main, and let a new workflow run. If the conflict persists, use stlc status to resolve it as a legitimate custom-code conflict." | |
| [ -n "$PR" ] || exit 0 | |
| body="<!-- stlc-seal-pending --> | |
| This build encountered a custom-code conflict while a tracking-file sync PR is open: $seal_pr | |
| Stale tracking files may be the cause. Merge the tracking PR, update this branch from \`main\`, and let a new workflow run. If the conflict persists, follow \`stlc status\` to resolve it as a legitimate custom-code conflict." | |
| if [ -n "$existing" ]; then | |
| gh api -X PATCH "repos/$REPO/issues/comments/$existing" -f body="$body" | |
| else | |
| gh api -X POST "repos/$REPO/issues/$PR/comments" -f body="$body" | |
| fi | |
| elif [ -n "$existing" ] && [ "$BUILD_OUTCOME" != "skipped" ]; then | |
| gh api -X DELETE "repos/$REPO/issues/comments/$existing" | |
| fi | |
| - name: Open or update draft PRs on the SDK repos | |
| # Integration-test runs only: a do-not-merge draft PR per SDK repo. | |
| # Links land in the run summary; cleanup is manual (see the runbook). | |
| if: success() && inputs.integration_test | |
| env: | |
| GH_TOKEN: ${{ steps.sdk-token.outputs.token }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| : > "$RUNNER_TEMP/integration-prs.md" | |
| for repo in $SDK_REPOS; do | |
| full="${{ github.repository_owner }}/$repo" | |
| title="[stlc integration test] run $GITHUB_RUN_ID" | |
| body="Real \`stlc build\` output from \`.github/workflows/stlc-generate.yml\` dispatched with \`integration_test=true\` — the same generator and config as a normal build, pushed to a test branch instead of \`main\`."$'\n\n'"Run: $RUN_URL"$'\n\n'"**Do not merge.** Close this PR and delete \`$INTEGRATION_BRANCH\` when the test is done — see docs/runbooks/sdk-generation-stlc.md." | |
| existing=$(gh pr list --repo "$full" --head "$INTEGRATION_BRANCH" --state open --json number --jq '.[0].number // empty') | |
| if [ -n "$existing" ]; then | |
| gh pr edit --repo "$full" "$existing" --title "$title" --body "$body" >/dev/null | |
| url=$(gh pr view --repo "$full" "$existing" --json url --jq .url) | |
| echo "updated $url" | |
| else | |
| url=$(gh pr create --repo "$full" --draft --base main --head "$INTEGRATION_BRANCH" --title "$title" --body "$body") | |
| echo "opened $url" | |
| fi | |
| echo "- $full: $url" >> "$RUNNER_TEMP/integration-prs.md" | |
| done | |
| { | |
| echo "### stlc integration test" | |
| echo | |
| echo "Pushed \`$INTEGRATION_BRANCH\` to each SDK repo with a real \`stlc build\` and opened/updated draft PRs (**do not merge**):" | |
| echo | |
| cat "$RUNNER_TEMP/integration-prs.md" | |
| echo | |
| echo "Cleanup is manual: close the PRs and delete \`$INTEGRATION_BRANCH\` on each repo — see docs/runbooks/sdk-generation-stlc.md." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Render the build manifest | |
| # A build that dies before writing a manifest makes `stlc show` exit | |
| # non-zero; don't let that mask the real failure above. | |
| id: manifest | |
| if: always() && github.event_name == 'pull_request' | |
| continue-on-error: true | |
| working-directory: ${{ env.STAINLESS_WORKSPACE }} | |
| env: | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| stlc show \ | |
| --renderer=markdown \ | |
| --marker '<!-- stlc-build-manifest -->' \ | |
| --workflow-run-url "$RUN_URL" > "$RUNNER_TEMP/manifest.md" | |
| cat "$RUNNER_TEMP/manifest.md" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Comment the build manifest on the PR | |
| if: always() && steps.manifest.outcome == 'success' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.pull_request.number }} | |
| run: | | |
| set -euo pipefail | |
| # The marker rendered into the manifest identifies our own comment, so | |
| # each run updates it in place instead of stacking new ones. | |
| jq -Rs '{body: .}' "$RUNNER_TEMP/manifest.md" > "$RUNNER_TEMP/comment.json" | |
| existing=$(gh api "repos/$REPO/issues/$PR/comments" --paginate \ | |
| --jq '[.[] | select(.body | contains("<!-- stlc-build-manifest -->")) | .id][0] // empty') | |
| # --paginate runs the --jq per page; keep the first match in case an | |
| # earlier run left duplicate comments. | |
| existing=${existing%%$'\n'*} | |
| if [ -n "$existing" ]; then | |
| gh api -X PATCH "repos/$REPO/issues/comments/$existing" --input "$RUNNER_TEMP/comment.json" | |
| else | |
| gh api -X POST "repos/$REPO/issues/$PR/comments" --input "$RUNNER_TEMP/comment.json" | |
| fi | |
| - name: Mint app token for the seal-back PR | |
| if: success() && github.event_name != 'pull_request' && !inputs.integration_test | |
| id: seal-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.ADMIN_APP_ID }} | |
| private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }} | |
| permission-contents: write | |
| permission-pull-requests: write | |
| - name: Seal custom-code tracking files back into this repo | |
| # `stlc build` re-seals custom code and rewrites the tracking files under | |
| # stainless/custom-code/. main is protected, so open a PR. Stale tracking | |
| # files are what let a later build drop custom code, so land this | |
| # promptly; the guard job keeps it from triggering another build. | |
| if: success() && github.event_name != 'pull_request' && !inputs.integration_test | |
| env: | |
| GH_TOKEN: ${{ steps.seal-token.outputs.token }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "$(git status --porcelain -- "$STAINLESS_WORKSPACE/custom-code")" ]; then | |
| echo "Tracking files already in sync — nothing to seal." | |
| exit 0 | |
| fi | |
| branch="stlc/seal-tracking" | |
| git checkout -B "$branch" | |
| git add "$STAINLESS_WORKSPACE/custom-code" | |
| git commit -m "chore(stlc): seal custom-code tracking files" | |
| git push --force "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" "$branch" | |
| if [ -z "$(gh pr list --head "$branch" --state open --json number --jq '.[0].number // empty')" ]; then | |
| gh pr create --base main --head "$branch" \ | |
| --title "chore(stlc): seal custom-code tracking files" \ | |
| --body "Opened by the stlc generate workflow. The last build on \`main\` re-sealed custom code and rewrote the tracking files under \`$STAINLESS_WORKSPACE/custom-code/\`; merging brings this repo back in sync with the SDK repos." | |
| fi | |
| if ! gh pr merge --auto --squash "$branch" 2>/dev/null; then | |
| echo "::warning::Could not enable auto-merge for $branch. Merge it promptly — stale tracking files can make a later build drop custom code." | |
| fi | |
| cleanup-preview: | |
| # Drop the PR's preview branch from each SDK repo once the PR closes, so | |
| # previews don't pile up in the public repos. | |
| needs: guard | |
| if: needs.guard.outputs.skip != 'true' && github.event_name == 'pull_request' && github.event.action == 'closed' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Mint app token for the SDK repos | |
| id: sdk-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.ADMIN_APP_ID }} | |
| private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }} | |
| owner: ${{ github.repository_owner }} | |
| repositories: hypeman-go-staging,hypeman-python-staging,hypeman-ts-staging | |
| permission-contents: write | |
| - name: Delete the preview branches | |
| env: | |
| GH_TOKEN: ${{ steps.sdk-token.outputs.token }} | |
| run: | | |
| set -euo pipefail | |
| for repo in $SDK_REPOS; do | |
| ref="repos/${{ github.repository_owner }}/$repo/git/refs/heads/$PREVIEW_BRANCH" | |
| if gh api "$ref" >/dev/null 2>&1; then | |
| gh api -X DELETE "$ref" && echo "deleted $PREVIEW_BRANCH in $repo" | |
| fi | |
| done |