Skip to content

Update Bindings

Update Bindings #57

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