Debug experience for runtime async #41
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: PR Validation | |
| on: | |
| issue_comment: | |
| types: [created] | |
| permissions: | |
| pull-requests: write | |
| id-token: write # Required to fetch an OIDC token for Azure authentication | |
| jobs: | |
| validate-and-trigger: | |
| name: Validate and Trigger Azure Pipeline | |
| if: | | |
| github.event.issue.pull_request && | |
| (contains(github.event.comment.body, '/dart') || contains(github.event.comment.body, '/pr-val')) | |
| runs-on: ubuntu-latest | |
| environment: roslyn_perf | |
| steps: | |
| - name: Check if command invoker has write access | |
| id: check-invoker-access | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| username: context.actor | |
| }); | |
| const hasWriteAccess = ['admin', 'write', 'maintain'].includes(permission.permission); | |
| console.log(`Command invoker ${context.actor} has permission: ${permission.permission}`); | |
| core.setOutput('has-access', hasWriteAccess); | |
| return hasWriteAccess; | |
| - name: Check if command invoker is Microsoft org member | |
| id: check-invoker-org | |
| if: steps.check-invoker-access.outputs.has-access == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| try { | |
| await github.rest.orgs.checkMembershipForUser({ | |
| org: 'microsoft', | |
| username: context.actor | |
| }); | |
| console.log(`Command invoker ${context.actor} is a member of Microsoft org`); | |
| core.setOutput('is-member', 'true'); | |
| return true; | |
| } catch (error) { | |
| console.log(`Command invoker ${context.actor} is not a member of Microsoft org`); | |
| core.setOutput('is-member', 'false'); | |
| return false; | |
| } | |
| - name: Block unauthorized users | |
| if: steps.check-invoker-access.outputs.has-access != 'true' || steps.check-invoker-org.outputs.is-member != 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: 'You do not have permission to trigger this workflow. Only Microsoft employees who are contributors to the roslyn repository can run pipelines.' | |
| }); | |
| core.setFailed('Unauthorized user'); | |
| - name: Get PR author details | |
| id: pr-author | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| const prAuthor = pr.user.login; | |
| console.log(`PR author: ${prAuthor}`); | |
| core.setOutput('username', prAuthor); | |
| // Check if PR author has write access | |
| try { | |
| const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| username: prAuthor | |
| }); | |
| const hasWriteAccess = ['admin', 'write', 'maintain'].includes(permission.permission); | |
| console.log(`PR author ${prAuthor} has permission: ${permission.permission}`); | |
| core.setOutput('has-access', hasWriteAccess); | |
| } catch (error) { | |
| console.log(`PR author ${prAuthor} does not have write access`); | |
| core.setOutput('has-access', 'false'); | |
| } | |
| - name: Check if PR author is Microsoft org member | |
| id: pr-author-org | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const prAuthor = '${{ steps.pr-author.outputs.username }}'; | |
| try { | |
| await github.rest.orgs.checkMembershipForUser({ | |
| org: 'microsoft', | |
| username: prAuthor | |
| }); | |
| console.log(`PR author ${prAuthor} is a member of Microsoft org`); | |
| core.setOutput('is-member', 'true'); | |
| return true; | |
| } catch (error) { | |
| console.log(`PR author ${prAuthor} is not a member of Microsoft org`); | |
| core.setOutput('is-member', 'false'); | |
| return false; | |
| } | |
| - name: Parse commit hash from comment | |
| id: parse-commit | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const commentBody = context.payload.comment.body; | |
| // Extract commit hash after /dart or /pr-val | |
| const match = commentBody.match(/\/(dart|pr-val)\s+([a-f0-9]{7,40})/i); | |
| if (!match || !match[2]) { | |
| console.log('No commit hash found in comment'); | |
| core.setOutput('has-commit', 'false'); | |
| return false; | |
| } | |
| const commitHash = match[2]; | |
| console.log(`Extracted commit hash: ${commitHash}`); | |
| core.setOutput('has-commit', 'true'); | |
| core.setOutput('commit-hash', commitHash); | |
| return true; | |
| - name: Require commit hash for external PR authors | |
| if: | | |
| (steps.pr-author.outputs.has-access != 'true' || steps.pr-author-org.outputs.is-member != 'true') && | |
| steps.parse-commit.outputs.has-commit != 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: 'This PR is from an external author. You must specify a commit hash to trigger this workflow. Please use the format `/dart <commit-hash>` or `/pr-val <commit-hash>`.' | |
| }); | |
| core.setFailed('Commit hash required for external PR author'); | |
| - name: Get PR branch details | |
| id: pr-details | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const { data: pr } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| core.setOutput('ref', pr.head.ref); | |
| core.setOutput('repo', pr.head.repo.full_name); | |
| core.setOutput('sha', pr.head.sha); | |
| console.log(`PR #${context.issue.number}: ${pr.head.repo.full_name}@${pr.head.ref} (${pr.head.sha})`); | |
| - name: Determine commit SHA to use | |
| id: commit-sha | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const parseCommitOutput = '${{ steps.parse-commit.outputs.has-commit }}'; | |
| const providedCommit = '${{ steps.parse-commit.outputs.commit-hash }}'; | |
| const prHeadSha = '${{ steps.pr-details.outputs.sha }}'; | |
| let commitSha; | |
| if (parseCommitOutput === 'true' && providedCommit) { | |
| // Use the commit hash provided in the comment | |
| commitSha = providedCommit; | |
| console.log(`Using commit hash from comment: ${commitSha}`); | |
| } else { | |
| // Use the PR head SHA | |
| commitSha = prHeadSha; | |
| console.log(`Using PR head SHA: ${commitSha}`); | |
| } | |
| core.setOutput('sha', commitSha); | |
| - name: Validate commit exists in PR | |
| if: steps.parse-commit.outputs.has-commit == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const commitSha = '${{ steps.commit-sha.outputs.sha }}'; | |
| const { data: commits } = await github.rest.pulls.listCommits({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.issue.number | |
| }); | |
| const commitExists = commits.some(commit => commit.sha.startsWith(commitSha)); | |
| if (!commitExists) { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: `The specified commit hash \`${commitSha}\` was not found in this PR. Please ensure you are using a valid commit hash from this PR.` | |
| }); | |
| core.setFailed(`Commit ${commitSha} not found in PR`); | |
| } else { | |
| console.log(`Validated commit ${commitSha} exists in PR`); | |
| } | |
| - name: Azure Login with OpenID Connect | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | |
| - name: Determine validation type and pipeline ID | |
| id: validation-type | |
| run: | | |
| COMMENT_BODY="${{ github.event.comment.body }}" | |
| if echo "$COMMENT_BODY" | grep -q "/dart"; then | |
| echo "type=dart" >> $GITHUB_OUTPUT | |
| echo "pipeline-id=15324" >> $GITHUB_OUTPUT | |
| elif echo "$COMMENT_BODY" | grep -q "/pr-val"; then | |
| echo "type=pr-val" >> $GITHUB_OUTPUT | |
| echo "pipeline-id=8972" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Trigger Pipeline | |
| id: trigger-pipeline | |
| run: | | |
| # Get Azure DevOps access token | |
| AZDO_TOKEN=$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv) | |
| VALIDATION_TYPE="${{ steps.validation-type.outputs.type }}" | |
| PIPELINE_ID="${{ steps.validation-type.outputs.pipeline-id }}" | |
| DEVDIV_ORG="devdiv" | |
| DEVDIV_PROJECT="DevDiv" | |
| # Determine if commit was explicitly provided (EnforceLatestCommit should be false if commit was provided) | |
| HAS_COMMIT="${{ steps.parse-commit.outputs.has-commit }}" | |
| if [ "$HAS_COMMIT" = "true" ]; then | |
| ENFORCE_LATEST="false" | |
| else | |
| ENFORCE_LATEST="true" | |
| fi | |
| echo "Triggering DevDiv $VALIDATION_TYPE pipeline (ID: $PIPELINE_ID)..." | |
| echo "EnforceLatestCommit: $ENFORCE_LATEST" | |
| # Build request body based on validation type | |
| if [ "$VALIDATION_TYPE" = "dart" ]; then | |
| # DART pipeline uses prNumber and sha parameters | |
| REQUEST_BODY=$(cat <<EOF | |
| { | |
| "templateParameters": { | |
| "prNumber": "${{ github.event.issue.number }}", | |
| "sha": "${{ steps.commit-sha.outputs.sha }}", | |
| "EnforceLatestCommit": "$ENFORCE_LATEST" | |
| } | |
| } | |
| EOF | |
| ) | |
| else | |
| # PR-Val pipeline uses PRNumber and CommitSHA parameters | |
| REQUEST_BODY=$(cat <<EOF | |
| { | |
| "templateParameters": { | |
| "PRNumber": ${{ github.event.issue.number }}, | |
| "CommitSHA": "${{ steps.commit-sha.outputs.sha }}", | |
| "EnforceLatestCommit": "$ENFORCE_LATEST" | |
| } | |
| } | |
| EOF | |
| ) | |
| fi | |
| echo "Request body: $REQUEST_BODY" | |
| # Trigger the pipeline | |
| RESPONSE=$(curl -X POST \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer $AZDO_TOKEN" \ | |
| -d "$REQUEST_BODY" \ | |
| "https://dev.azure.com/$DEVDIV_ORG/$DEVDIV_PROJECT/_apis/pipelines/$PIPELINE_ID/runs?api-version=7.0") | |
| echo "Response: $RESPONSE" | |
| # Extract pipeline run information | |
| BUILD_ID=$(echo $RESPONSE | jq -r '.id // empty') | |
| if [ -z "$BUILD_ID" ]; then | |
| echo "Failed to trigger pipeline" | |
| echo "Error details: $(echo $RESPONSE | jq -r '.message // "Unknown error"')" | |
| exit 1 | |
| fi | |
| WEB_URL="https://dev.azure.com/$DEVDIV_ORG/$DEVDIV_PROJECT/_build/results?buildId=$BUILD_ID" | |
| echo "pipeline-url=$WEB_URL" >> $GITHUB_OUTPUT | |
| echo "build-id=$BUILD_ID" >> $GITHUB_OUTPUT | |
| echo "Successfully triggered pipeline: $WEB_URL" | |
| - name: Comment pipeline link | |
| if: success() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: `Pipeline triggered by @${context.actor}\n\n[View Pipeline Run](${{ steps.trigger-pipeline.outputs.pipeline-url }})\n\n**Parameters:**\n- Validation Type: \`${{ steps.validation-type.outputs.type }}\`\n- Pipeline ID: \`${{ steps.validation-type.outputs.pipeline-id }}\`\n- PR Number: \`${{ github.event.issue.number }}\`\n- Commit SHA: \`${{ steps.commit-sha.outputs.sha }}\`\n- Source Branch: \`${{ steps.pr-details.outputs.ref }}\`\n- Build ID: \`${{ steps.trigger-pipeline.outputs.build-id }}\`` | |
| }); | |
| - name: Comment on failure | |
| if: failure() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: 'Failed to trigger the pipeline. Please check the workflow logs for details.' | |
| }); |