name: đŸŽĒ Superset Showtime # Ultra-simple: just sync on any PR state change on: pull_request_target: types: [labeled, unlabeled, synchronize, closed] # Manual testing workflow_dispatch: inputs: pr_number: description: 'PR number to sync' required: true type: number sha: description: 'Specific SHA to deploy (optional, defaults to latest)' required: false type: string # Common environment variables for all jobs (non-sensitive only) env: AWS_REGION: us-west-2 GITHUB_ORG: ${{ github.repository_owner }} GITHUB_REPO: ${{ github.event.repository.name }} GITHUB_ACTOR: ${{ github.actor }} jobs: sync: name: đŸŽĒ Sync PR to desired state runs-on: ubuntu-latest timeout-minutes: 90 permissions: contents: read pull-requests: write steps: - name: Security Check - Authorize Maintainers Only id: auth uses: actions/github-script@v8 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: script: | const actor = context.actor; console.log(`🔍 Checking authorization for ${actor}`); // Early exit for workflow_dispatch - assume authorized since it's manually triggered if (context.eventName === 'workflow_dispatch') { console.log(`✅ Workflow dispatch event - assuming authorized for ${actor}`); core.setOutput('authorized', 'true'); return; } const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: actor }); console.log(`📊 Permission level for ${actor}: ${permission.permission}`); const authorized = ['write', 'admin'].includes(permission.permission); // If this is a synchronize event from unauthorized user, check if Showtime is active and set blocked label if (!authorized && context.eventName === 'pull_request_target' && context.payload.action === 'synchronize') { console.log(`🔒 Synchronize event detected - checking if Showtime is active`); // Check if PR has any circus tent labels (Showtime is in use) const { data: issue } = await github.rest.issues.get({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number }); const hasCircusLabels = issue.labels.some(label => label.name.startsWith('đŸŽĒ ')); if (hasCircusLabels) { console.log(`đŸŽĒ Circus labels found - setting blocked label to prevent auto-deployment`); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: ['đŸŽĒ 🔒 showtime-blocked'] }); console.log(`✅ Blocked label set - Showtime will detect and skip operations`); } else { console.log(`â„šī¸ No circus labels found - Showtime not in use, skipping block`); } } if (!authorized) { console.log(`🚨 Unauthorized user ${actor} - skipping all operations`); core.setOutput('authorized', 'false'); return; } console.log(`✅ Authorized maintainer: ${actor}`); core.setOutput('authorized', 'true'); - name: Install Superset Showtime if: steps.auth.outputs.authorized == 'true' run: | echo "::notice::Maintainer ${{ github.actor }} triggered deploy for PR ${{ github.event.pull_request.number || github.event.inputs.pr_number }}" pip install --upgrade superset-showtime showtime version - name: Check what actions are needed if: steps.auth.outputs.authorized == 'true' id: check env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Bulletproof PR number extraction if [[ -n "${{ github.event.pull_request.number }}" ]]; then PR_NUM="${{ github.event.pull_request.number }}" elif [[ -n "${{ github.event.inputs.pr_number }}" ]]; then PR_NUM="${{ github.event.inputs.pr_number }}" else echo "❌ No PR number found in event or inputs" exit 1 fi echo "Using PR number: $PR_NUM" # Run sync check-only with optional SHA override if [[ -n "${{ github.event.inputs.sha }}" ]]; then OUTPUT=$(python -m showtime sync $PR_NUM --check-only --sha "${{ github.event.inputs.sha }}") else OUTPUT=$(python -m showtime sync $PR_NUM --check-only) fi echo "$OUTPUT" # Extract the outputs we need for conditional steps BUILD=$(echo "$OUTPUT" | grep "build_needed=" | cut -d'=' -f2) SYNC=$(echo "$OUTPUT" | grep "sync_needed=" | cut -d'=' -f2) PR_NUM_OUT=$(echo "$OUTPUT" | grep "pr_number=" | cut -d'=' -f2) TARGET_SHA=$(echo "$OUTPUT" | grep "target_sha=" | cut -d'=' -f2) echo "build_needed=$BUILD" >> $GITHUB_OUTPUT echo "sync_needed=$SYNC" >> $GITHUB_OUTPUT echo "pr_number=$PR_NUM_OUT" >> $GITHUB_OUTPUT echo "target_sha=$TARGET_SHA" >> $GITHUB_OUTPUT - name: Checkout PR code (only if build needed) if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true' uses: actions/checkout@v6 with: ref: ${{ steps.check.outputs.target_sha }} persist-credentials: false - name: Setup Docker Environment (only if build needed) if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.build_needed == 'true' uses: ./.github/actions/setup-docker with: dockerhub-user: ${{ secrets.DOCKERHUB_USER }} dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} build: "true" install-docker-compose: "false" - name: Execute sync (handles everything) if: steps.auth.outputs.authorized == 'true' && steps.check.outputs.sync_needed == 'true' env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} run: | PR_NUM="${{ steps.check.outputs.pr_number }}" TARGET_SHA="${{ steps.check.outputs.target_sha }}" if [[ -n "$TARGET_SHA" ]]; then python -m showtime sync $PR_NUM --sha "$TARGET_SHA" else python -m showtime sync $PR_NUM fi