diff --git a/.github/workflows/stlc-promote.yml b/.github/workflows/stlc-promote.yml new file mode 100644 index 0000000..5d446ce --- /dev/null +++ b/.github/workflows/stlc-promote.yml @@ -0,0 +1,83 @@ +name: Promote SDKs + +# Production requires pull requests, so staging is promoted through a merge- +# commit PR. Never squash or rebase this cross-repo PR: preserving the incoming +# commits keeps production and staging on one ancestry chain. +on: + push: + branches: [main] + workflow_dispatch: {} + +permissions: + contents: read + +jobs: + promote: + if: github.repository == 'kernel/kernel-go-sdk-staging' + runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }} + concurrency: + group: stlc-promote + cancel-in-progress: true + steps: + - name: Check out staging + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Mint production token + id: production-token + uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1 + with: + app-id: ${{ secrets.ADMIN_APP_ID }} + private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }} + owner: kernel + repositories: kernel-go-sdk + permission-contents: write + permission-workflows: write + permission-pull-requests: write + + - name: Fetch production main + env: + GH_TOKEN: ${{ steps.production-token.outputs.token }} + PRODUCTION_REPO: kernel/kernel-go-sdk + run: | + git remote add production "https://x-access-token:${GH_TOKEN}@github.com/${PRODUCTION_REPO}.git" + git fetch production main + + - name: Check whether production already has staging's content + id: diff + run: | + MERGED=$(git merge-tree --write-tree production/main origin/main) || MERGED=conflict + PRODUCTION_TREE=$(git rev-parse 'production/main^{tree}') + if [ "$MERGED" = "$PRODUCTION_TREE" ]; then + echo "Production already contains staging's content. Nothing to promote." + echo "synced=true" >> "$GITHUB_OUTPUT" + else + echo "synced=false" >> "$GITHUB_OUTPUT" + fi + + - name: Push the production release branch + if: steps.diff.outputs.synced == 'false' + env: + GH_TOKEN: ${{ steps.production-token.outputs.token }} + PRODUCTION_REPO: kernel/kernel-go-sdk + run: git push production origin/main:refs/heads/stainless/release --force + + - name: Open or update the promote PR + if: steps.diff.outputs.synced == 'false' + env: + GH_TOKEN: ${{ steps.production-token.outputs.token }} + PRODUCTION_REPO: kernel/kernel-go-sdk + run: | + body=$(mktemp) + git log --oneline production/main..origin/main > "$body" + existing=$(gh pr list --repo "$PRODUCTION_REPO" --head stainless/release --state open --json number --jq 'if length == 0 then "" else .[0].number end') + if [ -z "$existing" ]; then + gh pr create --repo "$PRODUCTION_REPO" --base main --head stainless/release --title "Release SDK updates" --body-file "$body" + else + gh pr edit "$existing" --repo "$PRODUCTION_REPO" --title "Release SDK updates" --body-file "$body" + fi + if ! gh pr merge stainless/release --repo "$PRODUCTION_REPO" --merge --auto; then + echo "::warning title=Manual promotion required::Merge the promote PR with a merge commit." + fi diff --git a/.github/workflows/stlc-sync.yml b/.github/workflows/stlc-sync.yml new file mode 100644 index 0000000..ca263e1 --- /dev/null +++ b/.github/workflows/stlc-sync.yml @@ -0,0 +1,107 @@ +name: Sync SDK repos + +# Keeps production and staging on one fast-forward-only history. Optional +# dispatch tokens make the polling loop eager; the scheduled back-sync is the +# safety backstop when those secrets are absent. +on: + schedule: + - cron: '7,37 * * * *' + workflow_dispatch: {} + repository_dispatch: + types: [prod-released] + release: + types: [published] + push: + branches: [main] + +jobs: + back-sync: + if: >- + github.repository == 'kernel/kernel-go-sdk-staging' && + (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch') + runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }} + permissions: + contents: write + concurrency: + group: stlc-back-sync + cancel-in-progress: true + steps: + - name: Check out staging + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Fetch production main + run: | + git remote add production "https://github.com/kernel/kernel-go-sdk.git" + git -c "http.https://github.com/.extraheader=" fetch production main + + - name: Check whether production has content staging lacks + id: diff + run: | + MERGED=$(git merge-tree --write-tree origin/main production/main) || MERGED=conflict + STAGING_TREE=$(git rev-parse 'origin/main^{tree}') + if [ "$MERGED" = "$STAGING_TREE" ]; then + echo "Staging already has production's content. Nothing to pull back." + echo "behind=false" >> "$GITHUB_OUTPUT" + else + echo "behind=true" >> "$GITHUB_OUTPUT" + fi + + - name: Sync production to staging + if: steps.diff.outputs.behind == 'true' + run: | + if ! git merge-base --is-ancestor origin/main production/main; then + echo "::error title=Back-sync blocked::staging main is not an ancestor of production/main." + exit 1 + fi + git push origin production/main:refs/heads/main + + notify-back-sync: + if: >- + github.repository == 'kernel/kernel-go-sdk' && + (github.event_name == 'release' || github.event_name == 'workflow_dispatch') + runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }} + permissions: + contents: read + steps: + - name: Dispatch back-sync to staging + env: + DISPATCH_TOKEN: ${{ secrets.STAGING_DISPATCH_TOKEN }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + if [ -z "${DISPATCH_TOKEN:-}" ]; then + echo "::notice::STAGING_DISPATCH_TOKEN not configured; the scheduled back-sync remains active." + exit 0 + fi + payload=$(jq -n --arg ref "$REF_NAME" '{event_type:"prod-released",client_payload:{ref:$ref}}') + curl --fail-with-body -sS -X POST -H "Authorization: Bearer $DISPATCH_TOKEN" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/kernel/kernel-go-sdk-staging/dispatches" -d "$payload" + + seal-dispatch: + if: github.repository == 'kernel/kernel-go-sdk-staging' && github.event_name == 'push' + runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }} + permissions: + contents: read + concurrency: + group: seal-dispatch-${{ github.ref }} + cancel-in-progress: false + steps: + - name: Dispatch tracking sync + env: + DISPATCH_TOKEN: ${{ secrets.CONFIG_DISPATCH_TOKEN }} + HEAD_MSG: ${{ github.event.head_commit.message }} + HEAD_AUTHOR_NAME: ${{ github.event.head_commit.author.name }} + SHA: ${{ github.sha }} + run: | + set -euo pipefail + if printf '%s' "$HEAD_MSG" | grep -q 'Stainless-Generated-From' || [ "$HEAD_AUTHOR_NAME" = "stlc-bot" ]; then + echo "Generated commit; skipping tracking dispatch." + exit 0 + fi + if [ -z "${DISPATCH_TOKEN:-}" ]; then + echo "::notice::CONFIG_DISPATCH_TOKEN not configured; the config repo's scheduled sync remains active." + exit 0 + fi + payload=$(jq -n --arg sha "$SHA" --arg repo "${{ github.repository }}" '{event_type:"seal-custom-code",client_payload:{target:"all",sha:$sha,repo:$repo}}') + curl --fail-with-body -sS -X POST -H "Authorization: Bearer $DISPATCH_TOKEN" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" "https://api.github.com/repos/kernel/kernel/dispatches" -d "$payload"