Update Bindings #57
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
| name: Update Bindings | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| binding_type: | |
| description: "Which bindings do you want to update?" | |
| required: true | |
| type: choice | |
| options: | |
| - api | |
| - identity | |
| - both | |
| default: "both" | |
| schedule: | |
| # Runs every Monday at 4 AM UTC | |
| # Note: schedule triggers don't have inputs, so binding_type will be null and default to 'both' | |
| - cron: "0 4 * * 1" | |
| permissions: | |
| contents: read | |
| env: | |
| _BOT_NAME: "bw-ghapp[bot]" | |
| _BOT_EMAIL: "178206702+bw-ghapp[bot]@users.noreply.github.com" | |
| jobs: | |
| download: | |
| name: Update Bindings | |
| runs-on: ubuntu-24.04 | |
| permissions: | |
| actions: read | |
| contents: write | |
| id-token: write | |
| steps: | |
| - name: Early exit for uneven weeks | |
| if: github.event_name == 'schedule' | |
| run: | | |
| WEEK_NUM=$(date +%V) | |
| if [ $((WEEK_NUM % 2)) -ne 0 ]; then | |
| echo "Odd week ($WEEK_NUM), exiting early." | |
| exit 0 | |
| fi | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: true | |
| - name: Capture base commit | |
| id: base-commit | |
| run: | | |
| BASE_SHA=$(git rev-parse HEAD) | |
| echo "sha=$BASE_SHA" >> $GITHUB_OUTPUT | |
| echo "📌 Base commit: $BASE_SHA" | |
| - name: Log in to Azure | |
| uses: bitwarden/gh-actions/azure-login@main | |
| with: | |
| subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| tenant_id: ${{ secrets.AZURE_TENANT_ID }} | |
| client_id: ${{ secrets.AZURE_CLIENT_ID }} | |
| - name: Get Azure Key Vault secrets | |
| id: get-kv-secrets | |
| uses: bitwarden/gh-actions/get-keyvault-secrets@main | |
| with: | |
| keyvault: gh-org-bitwarden | |
| secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" | |
| - name: Log out from Azure | |
| uses: bitwarden/gh-actions/azure-logout@main | |
| - name: Generate GH App token | |
| uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1 | |
| id: app-token | |
| with: | |
| app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} | |
| private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} | |
| permission-pull-requests: write | |
| - name: Download json artifacts from server for updated bindings | |
| uses: bitwarden/gh-actions/download-artifacts@main | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| repo: bitwarden/server | |
| branch: main | |
| workflow: build.yml | |
| artifacts: "*.json" | |
| path: artifacts/ | |
| - name: List downloaded files | |
| run: | | |
| echo "Downloaded files:" | |
| find artifacts/ -type f -name "*.json" | head -10 | |
| if [ -f "artifacts/internal.json" ]; then | |
| echo "internal.json file size: $(stat -c%s artifacts/internal.json) bytes" | |
| fi | |
| - name: Set Rust Nightly Toolchain | |
| id: nightly-toolchain | |
| shell: bash | |
| run: | | |
| RUST_NIGHTLY_TOOLCHAIN="$(grep -oP '^nightly-channel.*"(\K.*?)(?=")' rust-toolchain.toml)" | |
| echo "RUST_NIGHTLY_TOOLCHAIN=${RUST_NIGHTLY_TOOLCHAIN}" | tee -a "${GITHUB_OUTPUT}" | |
| - name: Install rust nightly | |
| env: | |
| RUST_NIGHTLY_TOOLCHAIN: ${{ steps.nightly-toolchain.outputs.RUST_NIGHTLY_TOOLCHAIN }} | |
| run: | | |
| rustup toolchain install "${RUST_NIGHTLY_TOOLCHAIN}" | |
| rustup component add rustfmt --toolchain "${RUST_NIGHTLY_TOOLCHAIN}"-x86_64-unknown-linux-gnu | |
| - name: Cache cargo registry | |
| uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 | |
| - name: Set Node Version | |
| id: retrieve-node-version | |
| working-directory: ./ | |
| run: | | |
| NODE_NVMRC=$(cat .nvmrc) | |
| NODE_VERSION=${NODE_NVMRC/v/''} | |
| echo "node_version=$NODE_VERSION" >> $GITHUB_OUTPUT | |
| - name: Set up Node | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| cache: "npm" | |
| cache-dependency-path: "package-lock.json" | |
| node-version: ${{ env._NODE_VERSION }} | |
| env: | |
| _NODE_VERSION: ${{ steps.retrieve-node-version.outputs.node_version }} | |
| - name: NPM setup | |
| run: npm ci | |
| - name: Set Commit Info | |
| id: commit-info | |
| run: | | |
| API_HASH=$(cat ./artifacts/api.json | jq -r '.["x-git-commit"]') | |
| IDENTITY_HASH=$(cat ./artifacts/identity.json | jq -r '.["x-git-commit"]') | |
| echo "API commit hash: $API_HASH" | |
| echo "Identity commit hash: $IDENTITY_HASH" | |
| if [ "$API_HASH" != "$IDENTITY_HASH" ]; then | |
| echo "::error::Commit hash mismatch! API: $API_HASH, Identity: $IDENTITY_HASH" | |
| exit 1 | |
| fi | |
| echo "HASH=$API_HASH" >> $GITHUB_OUTPUT | |
| echo "✅ Using server commit: $API_HASH" | |
| - name: Configure Git | |
| run: | | |
| git config user.name "$_BOT_NAME" | |
| git config user.email "$_BOT_EMAIL" | |
| # API Bindings PR | |
| - name: Switch to API branch | |
| id: switch-api-branch | |
| if: ${{ inputs.binding_type == 'api' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| env: | |
| BRANCH_NAME: "api-bindings-update" | |
| BASE_SHA: ${{ steps.base-commit.outputs.sha }} | |
| run: | | |
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| git fetch origin | |
| if git switch $BRANCH_NAME 2>/dev/null; then | |
| echo "✅ Switched to existing branch: $BRANCH_NAME" | |
| echo "updating_existing_branch=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "📝 Creating new branch: $BRANCH_NAME from $BASE_SHA" | |
| git switch -c $BRANCH_NAME $BASE_SHA | |
| echo "updating_existing_branch=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Prevent updating API branch when last committer isn't the bot | |
| if: ${{ (inputs.binding_type == 'api' || inputs.binding_type == 'both' || !inputs.binding_type) && steps.switch-api-branch.outputs.updating_existing_branch == 'true' }} | |
| env: | |
| _BRANCH_NAME: ${{ steps.switch-api-branch.outputs.branch_name }} | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| LATEST_COMMIT_AUTHOR=$(git log -1 --format='%ae' $_BRANCH_NAME) | |
| echo "Latest commit author in branch ($_BRANCH_NAME): $LATEST_COMMIT_AUTHOR" | |
| echo "Expected bot email: $_BOT_EMAIL" | |
| if [ "$LATEST_COMMIT_AUTHOR" != "$_BOT_EMAIL" ]; then | |
| echo "::error::Branch $_BRANCH_NAME has a commit not made by the bot." \ | |
| "This indicates manual changes have been made to the branch," \ | |
| "PR has to be merged or closed before running this workflow again." | |
| EXISTING_PR=$(gh pr list --head $_BRANCH_NAME --base main --state open --json number --jq '.[0].number // empty') | |
| if [ -z "$EXISTING_PR" ]; then | |
| echo "::error::Couldn't find an existing PR for branch $_BRANCH_NAME." | |
| exit 1 | |
| fi | |
| PR_URL="https://github.com/${{ github.repository }}/pull/$EXISTING_PR" | |
| echo "## ❌ Merge or close: $PR_URL" >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi | |
| echo "✅ Branch tip commit was made by the bot. Safe to proceed." | |
| - name: Generate API bindings | |
| if: ${{ inputs.binding_type == 'api' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| run: ./support/generate-api-bindings-ci.sh | |
| - name: Format API bindings | |
| if: ${{ inputs.binding_type == 'api' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| env: | |
| RUST_NIGHTLY_TOOLCHAIN: ${{ steps.nightly-toolchain.outputs.RUST_NIGHTLY_TOOLCHAIN }} | |
| run: cargo +"${RUST_NIGHTLY_TOOLCHAIN}" fmt -p bitwarden-api-api | |
| - name: Commit API bindings | |
| if: ${{ inputs.binding_type == 'api' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| env: | |
| _HASH: ${{ steps.commit-info.outputs.HASH }} | |
| _BRANCH_NAME: ${{ steps.switch-api-branch.outputs.branch_name }} | |
| run: | | |
| echo "👀 Committing API bindings update..." | |
| git add crates/bitwarden-api-api | |
| git commit -m "Update API bindings to $_HASH" --no-verify || echo "No changes to commit for API" | |
| git push origin $_BRANCH_NAME | |
| - name: Create or Update API Pull Request | |
| if: ${{ inputs.binding_type == 'api' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| _HASH: ${{ steps.commit-info.outputs.HASH }} | |
| _BRANCH_NAME: ${{ steps.switch-api-branch.outputs.branch_name }} | |
| run: | | |
| PR_TITLE="Update API bindings to $_HASH" | |
| PR_BODY="Updates the API bindings to \`$_HASH\`" | |
| # Set reviewer flag for scheduled runs | |
| if [ "${{ github.event_name }}" == "schedule" ]; then | |
| echo "🔔 Assigning team-platform-dev as reviewer for scheduled run" | |
| REVIEWER_FLAG="--reviewer bitwarden/team-platform-dev" | |
| else | |
| REVIEWER_FLAG="" | |
| fi | |
| EXISTING_PR=$(gh pr list --head $_BRANCH_NAME --base main --state open --json number --jq '.[0].number // empty') | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "🔄 Updating existing API PR #$EXISTING_PR..." | |
| echo -e "$PR_BODY" | gh pr edit $EXISTING_PR \ | |
| --title "$PR_TITLE" \ | |
| --body-file - | |
| PR_URL="https://github.com/${{ github.repository }}/pull/$EXISTING_PR" | |
| echo "## ✅ Updated API PR: $PR_URL" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "📝 Creating new API PR..." | |
| PR_URL=$(echo -e "$PR_BODY" | gh pr create \ | |
| --title "$PR_TITLE" \ | |
| --body-file - \ | |
| --base main \ | |
| --head $_BRANCH_NAME \ | |
| $REVIEWER_FLAG) | |
| echo "## 🚀 Created API PR: $PR_URL" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Identity Bindings PR | |
| - name: Switch to Identity branch | |
| id: switch-identity-branch | |
| if: ${{ inputs.binding_type == 'identity' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| env: | |
| BRANCH_NAME: "identity-bindings-update" | |
| BASE_SHA: ${{ steps.base-commit.outputs.sha }} | |
| run: | | |
| echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
| git fetch origin | |
| if git switch $BRANCH_NAME 2>/dev/null; then | |
| echo "✅ Switched to existing branch: $BRANCH_NAME" | |
| echo "updating_existing_branch=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "📝 Creating new branch: $BRANCH_NAME from $BASE_SHA" | |
| git switch -c $BRANCH_NAME $BASE_SHA | |
| echo "updating_existing_branch=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Prevent updating Identity branch when last committer isn't the bot | |
| if: ${{ (inputs.binding_type == 'identity' || inputs.binding_type == 'both' || !inputs.binding_type) && steps.switch-identity-branch.outputs.updating_existing_branch == 'true' }} | |
| env: | |
| _BRANCH_NAME: ${{ steps.switch-identity-branch.outputs.branch_name }} | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| LATEST_COMMIT_AUTHOR=$(git log -1 --format='%ae' $_BRANCH_NAME) | |
| echo "Latest commit author in branch ($_BRANCH_NAME): $LATEST_COMMIT_AUTHOR" | |
| echo "Expected bot email: $_BOT_EMAIL" | |
| if [ "$LATEST_COMMIT_AUTHOR" != "$_BOT_EMAIL" ]; then | |
| echo "::error::Branch $_BRANCH_NAME has a commit not made by the bot." \ | |
| "This indicates manual changes have been made to the branch," \ | |
| "PR has to be merged or closed before running this workflow again." | |
| EXISTING_PR=$(gh pr list --head $_BRANCH_NAME --base main --state open --json number --jq '.[0].number // empty') | |
| if [ -z "$EXISTING_PR" ]; then | |
| echo "::error::Couldn't find an existing PR for branch $_BRANCH_NAME." | |
| exit 1 | |
| fi | |
| PR_URL="https://github.com/${{ github.repository }}/pull/$EXISTING_PR" | |
| echo "## ❌ Merge or close: $PR_URL" >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi | |
| echo "✅ Branch tip commit was made by the bot. Safe to proceed." | |
| - name: Generate Identity bindings | |
| if: ${{ inputs.binding_type == 'identity' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| run: ./support/generate-identity-bindings-ci.sh | |
| - name: Format Identity bindings | |
| if: ${{ inputs.binding_type == 'identity' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| env: | |
| RUST_NIGHTLY_TOOLCHAIN: ${{ steps.nightly-toolchain.outputs.RUST_NIGHTLY_TOOLCHAIN }} | |
| run: cargo +"${RUST_NIGHTLY_TOOLCHAIN}" fmt -p bitwarden-api-identity | |
| - name: Commit Identity bindings | |
| if: ${{ inputs.binding_type == 'identity' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| env: | |
| _HASH: ${{ steps.commit-info.outputs.HASH }} | |
| _BRANCH_NAME: ${{ steps.switch-identity-branch.outputs.branch_name }} | |
| run: | | |
| echo "👀 Committing Identity bindings update..." | |
| git add crates/bitwarden-api-identity | |
| git commit -m "Update Identity bindings to $_HASH" --no-verify || echo "No changes to commit for Identity" | |
| git push origin $_BRANCH_NAME | |
| - name: Create or Update Identity Pull Request | |
| if: ${{ inputs.binding_type == 'identity' || inputs.binding_type == 'both' || !inputs.binding_type }} | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| _HASH: ${{ steps.commit-info.outputs.HASH }} | |
| _BRANCH_NAME: ${{ steps.switch-identity-branch.outputs.branch_name }} | |
| run: | | |
| PR_TITLE="Update Identity bindings to $_HASH" | |
| PR_BODY="Updates the Identity bindings to \`$_HASH\`" | |
| EXISTING_PR=$(gh pr list --head $_BRANCH_NAME --base main --state open --json number --jq '.[0].number // empty') | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "🔄 Updating existing Identity PR #$EXISTING_PR..." | |
| echo -e "$PR_BODY" | gh pr edit $EXISTING_PR \ | |
| --title "$PR_TITLE" \ | |
| --body-file - | |
| PR_URL="https://github.com/${{ github.repository }}/pull/$EXISTING_PR" | |
| echo "## ✅ Updated Identity PR: $PR_URL" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "📝 Creating new Identity PR..." | |
| PR_URL=$(echo -e "$PR_BODY" | gh pr create \ | |
| --title "$PR_TITLE" \ | |
| --body-file - \ | |
| --base main \ | |
| --head $_BRANCH_NAME) | |
| echo "## 🚀 Created Identity PR: $PR_URL" >> $GITHUB_STEP_SUMMARY | |
| fi |