/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
.github/workflows/pr-validation.yml
419 строк
17 KB
Ankita Khera
Update pr validation inputs (#84742)
04 авг 2026, 19:53
Не верифицирован
04 авг 2026, 19:53
a4f585c
Код
Авторство
О чём код?
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@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: PR_AUTHOR: ${{ steps.pr-author.outputs.username }} with: script: | const prAuthor = process.env.PR_AUTHOR; 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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 unsupportedBranchCharacters = /[`$'"{}()]/; if (unsupportedBranchCharacters.test(pr.head.ref)) { core.setFailed('The PR source branch contains unsupported characters. Rename the branch before running PR validation.'); return; } core.setOutput('ref', pr.head.ref); core.setOutput('repo', pr.head.repo.full_name); core.setOutput('sha', pr.head.sha); core.setOutput('base', pr.base.ref); console.log(`PR #${context.issue.number}: ${pr.head.repo.full_name}@${pr.head.ref} (${pr.head.sha}) -> ${pr.base.ref}`); - name: Determine commit SHA to use id: commit-sha uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: HAS_COMMIT: ${{ steps.parse-commit.outputs.has-commit }} PROVIDED_COMMIT: ${{ steps.parse-commit.outputs.commit-hash }} PR_HEAD_SHA: ${{ steps.pr-details.outputs.sha }} with: script: | const parseCommitOutput = process.env.HAS_COMMIT; const providedCommit = process.env.PROVIDED_COMMIT; const prHeadSha = process.env.PR_HEAD_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@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: COMMIT_SHA: ${{ steps.commit-sha.outputs.sha }} with: script: | const commitSha = process.env.COMMIT_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 env: COMMENT_BODY: ${{ github.event.comment.body }} run: | 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: Determine target branch for pipeline id: target-branch env: BASE_BRANCH: ${{ steps.pr-details.outputs.base }} run: | # Determine pipeline version based on target branch # Use the target branch for release/* and feature/*, otherwise use main if [ "$BASE_BRANCH" = "main" ]; then PIPELINE_VERSION="main" elif [[ "$BASE_BRANCH" =~ ^release/.+ ]] || [[ "$BASE_BRANCH" =~ ^feature/.+ ]]; then PIPELINE_VERSION="$BASE_BRANCH" else # Default to main for other branches PIPELINE_VERSION="main" fi echo "pipeline-version=$PIPELINE_VERSION" >> $GITHUB_OUTPUT echo "Target branch: $BASE_BRANCH -> Pipeline version: $PIPELINE_VERSION" - name: Trigger Pipeline id: trigger-pipeline env: VALIDATION_TYPE: ${{ steps.validation-type.outputs.type }} PIPELINE_ID: ${{ steps.validation-type.outputs.pipeline-id }} PIPELINE_VERSION: ${{ steps.target-branch.outputs.pipeline-version }} HAS_COMMIT: ${{ steps.parse-commit.outputs.has-commit }} PR_NUMBER: ${{ github.event.issue.number }} COMMIT_SHA: ${{ steps.commit-sha.outputs.sha }} run: | # Get Azure DevOps access token AZDO_TOKEN=$(az account get-access-token --resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv) DEVDIV_ORG="devdiv" DEVDIV_PROJECT="DevDiv" # Determine if commit was explicitly provided (EnforceLatestCommit should be false if commit was provided) if [ "$HAS_COMMIT" = "true" ]; then ENFORCE_LATEST="false" else ENFORCE_LATEST="true" fi echo "Triggering DevDiv $VALIDATION_TYPE pipeline (ID: $PIPELINE_ID)..." echo "Pipeline version: $PIPELINE_VERSION" 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 { "resources": { "repositories": { "self": { "refName": "refs/heads/$PIPELINE_VERSION" } } }, "templateParameters": { "prNumber": "$PR_NUMBER", "sha": "$COMMIT_SHA", "EnforceLatestCommit": "$ENFORCE_LATEST" } } EOF ) else # PR-Val pipeline uses PRNumber and CommitSHA parameters REQUEST_BODY=$(cat <<EOF { "resources": { "repositories": { "self": { "refName": "refs/heads/$PIPELINE_VERSION" } } }, "templateParameters": { "PRNumber": $PR_NUMBER, "CommitSHA": "$COMMIT_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@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 env: VALIDATION_TYPE: ${{ steps.validation-type.outputs.type }} PIPELINE_URL: ${{ steps.trigger-pipeline.outputs.pipeline-url }} PIPELINE_ID: ${{ steps.validation-type.outputs.pipeline-id }} PIPELINE_VERSION: ${{ steps.target-branch.outputs.pipeline-version }} PR_NUMBER: ${{ github.event.issue.number }} COMMIT_SHA: ${{ steps.commit-sha.outputs.sha }} SOURCE_BRANCH: ${{ steps.pr-details.outputs.ref }} TARGET_BRANCH: ${{ steps.pr-details.outputs.base }} BUILD_ID: ${{ steps.trigger-pipeline.outputs.build-id }} with: script: | const { VALIDATION_TYPE: validationType, PIPELINE_URL: pipelineUrl, PIPELINE_ID: pipelineId, PIPELINE_VERSION: pipelineVersion, PR_NUMBER: prNumber, COMMIT_SHA: commitSha, SOURCE_BRANCH: sourceBranch, TARGET_BRANCH: targetBranch, BUILD_ID: buildId } = process.env; const linkText = validationType === 'dart' ? 'View DartLab Run' : 'View PR Validation Run'; await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: `[${linkText}](${pipelineUrl}) triggered by @${context.actor}\n\n<details>\n<summary>Parameters</summary>\n\n- Validation Type: \`${validationType}\`\n- Pipeline ID: \`${pipelineId}\`\n- Pipeline Version: \`${pipelineVersion}\`\n- PR Number: \`${prNumber}\`\n- Commit SHA: \`${commitSha}\`\n- Source Branch: \`${sourceBranch}\`\n- Target Branch: \`${targetBranch}\`\n- Build ID: \`${buildId}\`\n</details>` }); - name: Comment on failure if: | failure() && steps.check-invoker-access.outcome != 'failure' && steps.check-invoker-org.outcome != 'failure' && steps.pr-author.outcome != 'failure' && steps.pr-author-org.outcome != 'failure' && steps.parse-commit.outcome != 'failure' uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # 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.' });