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: Trigger Skypilot job with Approval Check | |
| on: | |
| issue_comment: | |
| types: [created] | |
| jobs: | |
| validate-pr: | |
| name: Validate PR comment & approval | |
| runs-on: ubuntu-slim | |
| # 1️⃣ Only run for PR comments containing the trigger string | |
| if: | | |
| github.event.issue.pull_request != null && | |
| contains(github.event.comment.body, '/run') | |
| steps: | |
| - name: Check PR approval | |
| id: check-approval | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUMBER=${{ github.event.issue.number }} | |
| REPO=${{ github.repository }} | |
| APPROVALS=$(curl -s \ | |
| -H "Authorization: Bearer $GH_TOKEN" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| https://api.github.com/repos/$REPO/pulls/$PR_NUMBER/reviews \ | |
| | jq '[.[] | select(.state=="APPROVED")] | length') | |
| if [ "$APPROVALS" -ge 1 ]; then | |
| echo "approved=true" >> "$GITHUB_OUTPUT" | |
| echo "PR is approved, continuing skypilot job..." | |
| else | |
| echo "approved=false" >> "$GITHUB_OUTPUT" | |
| echo "PR is not approved, exiting." | |
| fi | |
| outputs: | |
| approved: ${{ steps.check-approval.outputs.approved }} | |
| launch-skypilot-job: | |
| name: Run job after validation | |
| needs: validate-pr | |
| runs-on: ubuntu-latest | |
| # 2️⃣ Run only if validation job confirmed approval | |
| if: needs.validate-pr.outputs.approved == 'true' | |
| steps: | |
| - name: Checkout the code | |
| uses: actions/checkout@v4 | |
| - name: Generate Job Name | |
| id: generate_job_name | |
| shell: bash | |
| run: | | |
| set -eo pipefail | |
| # GitHub actor (person triggering the workflow) | |
| ACTOR="${{ github.actor }}" | |
| # Short SHA of the checked-out commit | |
| SHORT_COMMIT_HASH=$(git rev-parse --short HEAD) | |
| # Branch name, sanitized for safe use in job name | |
| BRANCH_RAW=$(git rev-parse --abbrev-ref HEAD) | |
| BRANCH_SANITIZED=$(echo "$BRANCH_RAW" | sed 's/[^a-zA-Z0-9_-]/-/g') | |
| # GitHub run ID ensures uniqueness for every workflow run | |
| RUN_ID="${GITHUB_RUN_ID}" | |
| # Construct final job name | |
| FINAL_RUN_NAME="${ACTOR}.${BRANCH_SANITIZED}.${SHORT_COMMIT_HASH}.${RUN_ID}" | |
| echo "Generated job name: $FINAL_RUN_NAME" | |
| echo "job_name=$FINAL_RUN_NAME" >> "$GITHUB_OUTPUT" | |
| echo "### Generated job name: $FINAL_RUN_NAME" >> "$GITHUB_STEP_SUMMARY" | |
| - name: Launch SkyPilot Job via Custom Action | |
| id: skylaunch | |
| uses: ./.github/actions/launch-skypilot-job | |
| with: | |
| # The file path is intentionally fixed and sourced from the main branch only. | |
| # This is a governance decision to prevent feature branches from altering SkyPilot configuration behavior. | |
| task_yaml_path: './skypilot.yaml' | |
| job_name: ${{ steps.generate_job_name.outputs.job_name }} | |
| skypilot_api_url: ${{ secrets.SKYPILOT_API_URL }} | |
| skypilot_service_account_token: ${{ secrets.SKYPILOT_SERVICE_ACCOUNT_TOKEN }} | |
| - name: Extract SkyPilot Request ID | |
| id: extract_request_id | |
| shell: bash | |
| run: | | |
| # Extract the request ID from the launch output | |
| # Looking for pattern like "Submitted sky.jobs.launch request: c01f1273-4610-4949-8f1c-e24365ad0330" | |
| REQUEST_ID=$(echo "${{ steps.skylaunch.outputs.launch_output }}" | grep -oP 'Submitted sky.jobs.launch request: \K[a-f0-9-]+' || true) | |
| if [ -z "$REQUEST_ID" ]; then | |
| # Try alternate extraction method from stored output | |
| REQUEST_ID=$(cat /tmp/skypilot_launch_output.txt 2>/dev/null | grep -oP 'Submitted sky.jobs.launch request: \K[a-f0-9-]+' || true) | |
| fi | |
| if [ -n "$REQUEST_ID" ]; then | |
| echo "Extracted request ID: $REQUEST_ID" | |
| echo "request_id=$REQUEST_ID" >> $GITHUB_OUTPUT | |
| echo "request_id_short=$(echo $REQUEST_ID | cut -c1-8)" >> $GITHUB_OUTPUT | |
| echo '### SkyPilot Request ID $REQUEST_ID extracted' >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "Could not extract request ID from launch output" | |
| echo "request_id=" >> $GITHUB_OUTPUT | |
| echo "request_id_short=" >> $GITHUB_OUTPUT | |
| echo '### SkyPilot Request ID not found in logs' >> $GITHUB_STEP_SUMMARY | |
| fi | |
| - name: Get SkyPilot Job ID | |
| id: get_job_id | |
| if: steps.extract_request_id.outputs.request_id != '' | |
| shell: bash | |
| run: | | |
| set +e # Don't exit on error | |
| # Wait a bit for the job to be submitted | |
| sleep 5 | |
| # Get the logs and extract the job ID | |
| echo "Getting logs for request: ${{ steps.extract_request_id.outputs.request_id_short }}" | |
| LOG_OUTPUT=$(sky api logs ${{ steps.extract_request_id.outputs.request_id_short }} 2>&1 | tail -20) | |
| # Extract job ID from pattern like "Job submitted, ID: 4700" | |
| JOB_ID=$(echo "$LOG_OUTPUT" | grep -oP 'Job submitted, ID: \K\d+' || true) | |
| if [ -n "$JOB_ID" ]; then | |
| echo "Extracted SkyPilot Job ID: $JOB_ID" | |
| echo "job_id=$JOB_ID" >> $GITHUB_OUTPUT | |
| echo '### SkyPilot Job ID $JOB_ID launched' >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "Could not extract Job ID from logs" | |
| echo "Log output was:" | |
| echo "$LOG_OUTPUT" | |
| echo "job_id=" >> $GITHUB_OUTPUT | |
| echo '### SkyPilot Job ID not found in logs' >> $GITHUB_STEP_SUMMARY | |
| fi | |
| set -e | |
| - name: Fetch Slack-GitHub json mapping | |
| id: fetch_mapping | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| OWNER="aftershootco" | |
| REPO="infra" | |
| FILE_PATH="user-mapping.json" | |
| BRANCH="main" | |
| curl -sSf \ | |
| -H "Authorization: Bearer ${{ secrets.AFTERSHOOT_INFRA_REPO_READ_TOKEN }}" \ | |
| -H "Accept: application/vnd.github+json" \ | |
| "https://api.github.com/repos/$OWNER/$REPO/contents/$FILE_PATH?ref=$BRANCH" \ | |
| | jq -r '.content' \ | |
| | base64 --decode > user-mapping.json | |
| - name: Check if slack secrets are set | |
| id: check_slack_secrets | |
| shell: bash | |
| run: | | |
| if [ -z "${{ secrets.SLACK_BOT_TOKEN }}" ]; then | |
| echo "slack_secrets_set=False" >> $GITHUB_OUTPUT | |
| else | |
| echo "slack_secrets_set=True" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Resolve Slack user for GitHub actor | |
| id: slack_user | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ACTOR="${{ github.actor }}" | |
| MAP_FILE="user-mapping.json" | |
| SLACK_USER_ID=$(jq -r --arg actor "$ACTOR" '.[$actor] // empty' "$MAP_FILE") | |
| if [[ -z "$SLACK_USER_ID" ]]; then | |
| echo "No Slack user mapped for GitHub actor: $ACTOR" | |
| exit 0 | |
| fi | |
| echo "slack_user_id=$SLACK_USER_ID" >> "$GITHUB_OUTPUT" | |
| - name: Generate Run Information for Slack | |
| id: generate_slack_message | |
| if: steps.check_slack_secrets.outputs.slack_secrets_set == 'True' && steps.slack_user.outputs.slack_user_id != '' | |
| shell: bash | |
| run: | | |
| MESSAGE_TEXT="Skypilot job ${{ steps.get_job_id.outputs.job_id }} triggered by: ${{ github.event_name }}" | |
| MESSAGE_BLOCK="$MESSAGE_TEXT\nSkyPilot Job Name: ${{ steps.generate_job_name.outputs.job_name }}" | |
| if [ -n "${{ steps.get_job_id.outputs.job_id }}" ]; then | |
| MESSAGE_BLOCK="$MESSAGE_BLOCK\n✅ SkyPilot Job ID: ${{ steps.get_job_id.outputs.job_id }}" | |
| MESSAGE_BLOCK="$MESSAGE_BLOCK\n📝 View logs: sky jobs logs ${{ steps.get_job_id.outputs.job_id }}" | |
| MESSAGE_BLOCK="$MESSAGE_BLOCK\n🔗 View job: ${{ secrets.SKYPILOT_API_URL }}/dashboard/jobs/${{ steps.get_job_id.outputs.job_id }}" | |
| elif [ -n "${{ steps.extract_request_id.outputs.request_id_short }}" ]; then | |
| MESSAGE_BLOCK="$MESSAGE_BLOCK\n⏳ Request ID: ${{ steps.extract_request_id.outputs.request_id_short }}" | |
| MESSAGE_BLOCK="$MESSAGE_BLOCK\n📝 Get job ID: sky api logs ${{ steps.extract_request_id.outputs.request_id_short }}" | |
| else | |
| MESSAGE_BLOCK="$MESSAGE_BLOCK\nJob ID / Request ID not found" | |
| fi | |
| echo "message_text=$MESSAGE_TEXT" >> $GITHUB_OUTPUT | |
| echo "message_block=$MESSAGE_BLOCK" >> $GITHUB_OUTPUT | |
| echo $MESSAGE_BLOCK | |
| echo "$MESSAGE_BLOCK" >> $GITHUB_STEP_SUMMARY | |
| - name: Notify Slack channel | |
| uses: slackapi/[email protected] | |
| if: steps.check_slack_secrets.outputs.slack_secrets_set == 'True' && steps.slack_user.outputs.slack_user_id != '' | |
| with: | |
| method: chat.postMessage | |
| token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| payload: | | |
| { | |
| "channel": "${{ steps.slack_user.outputs.slack_user_id }}", | |
| "text": "${{ steps.generate_slack_message.outputs.message_text }}", | |
| "blocks": [ | |
| { | |
| "type": "section", | |
| "text": { | |
| "type": "mrkdwn", | |
| "text": "${{ steps.generate_slack_message.outputs.message_block }}" | |
| } | |
| } | |
| ] | |
| } |