update #4
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: Bytebase Export SQL | |
on: | |
pull_request: | |
types: [closed] | |
branches: | |
- main | |
paths: | |
- 'export/**' | |
workflow_dispatch: | |
jobs: | |
bytebase-export: | |
if: github.event.pull_request.merged == true | |
runs-on: ubuntu-latest | |
permissions: | |
pull-requests: write | |
issues: write | |
contents: read | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.event.pull_request.head.sha }} | |
fetch-depth: 0 | |
- name: Login Bytebase | |
id: bytebase-login | |
uses: bytebase/[email protected] | |
with: | |
bytebase-url: ${{ secrets.BYTEBASE_URL }} | |
service-key: ${{ secrets.BYTEBASE_SERVICE_KEY }} | |
service-secret: ${{ secrets.BYTEBASE_SERVICE_SECRET }} | |
- name: Get changed files | |
id: changed-files | |
uses: tj-actions/changed-files@v42 | |
with: | |
files: | | |
export/**/*.sql | |
since_last_remote_commit: true | |
- name: Process SQL files | |
id: process-sql | |
if: steps.changed-files.outputs.any_changed == 'true' | |
run: | | |
# Function to make API calls with error handling | |
call_api() { | |
local url="$1" | |
local method="$2" | |
local data="$3" | |
local description="$4" | |
echo "Making $description API call..." | |
response=$(curl -s -w "\n%{http_code}" \ | |
--request "$method" "$url" \ | |
--header "Authorization: Bearer ${{ steps.bytebase-login.outputs.token }}" \ | |
--header "Content-Type: application/json" \ | |
--data "$data") | |
status_code=$(echo "$response" | tail -n1) | |
body=$(echo "$response" | sed '$d') | |
echo "Status code: $status_code" | |
if [[ $status_code -lt 200 || $status_code -ge 300 ]]; then | |
echo "Failed $description. Status: $status_code" | |
echo "Response: $body" | |
return 1 | |
fi | |
echo "$body" | |
return 0 | |
} | |
# Find the manifest.toml once at the start | |
MANIFEST_PATH="" | |
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do | |
DIR_PATH=$(dirname "$file") | |
while [[ "$DIR_PATH" == export* ]]; do | |
if [[ -f "$DIR_PATH/manifest.toml" ]]; then | |
MANIFEST_PATH="$DIR_PATH/manifest.toml" | |
break 2 # Break out of both loops once found | |
fi | |
DIR_PATH=$(dirname "$DIR_PATH") | |
done | |
done | |
if [[ -z "$MANIFEST_PATH" ]]; then | |
echo "Error: No manifest.toml found in the export directory" | |
exit 1 | |
fi | |
# Parse manifest.toml once using built-in tomllib | |
PROJECT=$(python3 -c "import tomllib; print(tomllib.load(open('$MANIFEST_PATH', 'rb'))['project'])") | |
INSTANCE=$(python3 -c "import tomllib; print(tomllib.load(open('$MANIFEST_PATH', 'rb'))['instance'])") | |
DATABASE=$(python3 -c "import tomllib; print(tomllib.load(open('$MANIFEST_PATH', 'rb'))['database'])") | |
FORMAT=$(python3 -c "import tomllib; config=tomllib.load(open('$MANIFEST_PATH', 'rb')); print(config.get('format', 'JSON'))") | |
# Process each SQL file | |
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do | |
echo "Processing $file" | |
# Read SQL content and encode to base64 | |
SQL_CONTENT=$(base64 < "$file") | |
# Generate UUID for step ID | |
STEP_ID=$(python3 -c "import uuid; print(str(uuid.uuid4()))") | |
BASE_URL="${{ steps.bytebase-login.outputs.api_url }}" | |
# 1. Create Sheet | |
sheet_data=$(call_api \ | |
"$BASE_URL/v1/projects/$PROJECT/sheets" \ | |
"POST" \ | |
"{\"title\":\"\",\"content\":\"$SQL_CONTENT\",\"type\":\"TYPE_SQL\",\"source\":\"SOURCE_BYTEBASE_ARTIFACT\",\"visibility\":\"VISIBILITY_PUBLIC\"}" \ | |
"Create Sheet") || exit 1 | |
SHEET_NAME=$(echo "$sheet_data" | python3 -c "import sys, json; print(json.load(sys.stdin)['name'])") | |
# 2. Create Plan | |
plan_data=$(call_api \ | |
"$BASE_URL/v1/projects/$PROJECT/plans" \ | |
"POST" \ | |
"{\"steps\":[{\"specs\":[{\"id\":\"$STEP_ID\",\"export_data_config\":{\"target\":\"/instances/$INSTANCE/databases/$DATABASE\",\"format\":\"$FORMAT\",\"sheet\":\"$SHEET_NAME\"}}]}],\"title\":\"Export data from $DATABASE\",\"description\":\"EXPORT\"}" \ | |
"Create Plan") || exit 1 | |
PLAN_NAME=$(echo "$plan_data" | python3 -c "import sys, json; print(json.load(sys.stdin)['name'])") | |
# 3. Create Issue | |
issue_data=$(call_api \ | |
"$BASE_URL/v1/projects/$PROJECT/issues" \ | |
"POST" \ | |
"{\"approvers\":[],\"approvalTemplates\":[],\"subscribers\":[],\"title\":\"Issue: Export data from instances/$INSTANCE/databases/$DATABASE\",\"description\":\"SQL request from GitHub\",\"type\":\"DATABASE_DATA_EXPORT\",\"assignee\":\"\",\"plan\":\"$PLAN_NAME\"}" \ | |
"Create Issue") || exit 1 | |
# 4. Create Rollout | |
rollout_data=$(call_api \ | |
"$BASE_URL/v1/projects/$PROJECT/rollouts" \ | |
"POST" \ | |
"{\"plan\":\"$PLAN_NAME\"}" \ | |
"Create Rollout") || exit 1 | |
# Extract issue link for PR comment | |
ISSUE_NUMBER=$(echo "$issue_data" | python3 -c "import sys, json; print(json.load(sys.stdin)['name'].split('/')[-1])") | |
ISSUE_LINK="${{ secrets.BYTEBASE_URL }}/projects/$PROJECT/issues/$ISSUE_NUMBER" | |
echo "ISSUE_LINK=$ISSUE_LINK" >> $GITHUB_ENV | |
echo "Successfully processed $file" | |
done | |
- name: Comment on PR | |
uses: actions/github-script@v7 | |
if: always() | |
env: | |
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} | |
with: | |
script: | | |
const changedFiles = process.env.CHANGED_FILES || ''; | |
let commentBody = `### SQL Export Summary\n\n`; | |
commentBody += `✅ **PR Status:** Merged\n\n`; | |
commentBody += `📝 **Processed SQL Files:**\n\n`; | |
if (changedFiles.trim()) { | |
commentBody += changedFiles.split(' ').map(f => `- ${f}`).join('\n'); | |
} else { | |
commentBody += `None`; | |
} | |
commentBody += '\n\n**Status:** ${process.env.STATUS || 'Completed'}`; | |
await github.rest.issues.createComment({ | |
...context.repo, | |
issue_number: context.issue.number, | |
body: commentBody | |
}); |