pr-approval-check: refactor, fix pagination, fix approval logic #21
Workflow file for this run
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 Approval Check | |
| on: | |
| pull_request_review: | |
| types: [submitted, dismissed] | |
| pull_request: | |
| types: [opened, reopened, synchronize] | |
| jobs: | |
| check-product-eng-approval: | |
| name: Check Product Eng Approval | |
| runs-on: ubuntu-latest | |
| permissions: | |
| statuses: write | |
| pull-requests: write | |
| steps: | |
| - name: Check for Product Engineering approval | |
| uses: actions/github-script@v7 | |
| env: | |
| ORG: trufflesecurity | |
| APPROVER_TEAM: product-eng | |
| with: | |
| # github-token: ${{ secrets.GH_REVIEW_TOKEN }} | |
| script: | | |
| const { ORG: org, APPROVER_TEAM: team} = process.env | |
| const prettyTeamName = `@${org}/${team}` | |
| const children = await github.rest.teams.listChildInOrg({ | |
| org: org, | |
| team_slug: team, | |
| }); | |
| async function status(state, msg) { | |
| await github.rest.repos.createCommitStatus({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| sha: context.payload.pull_request.head.sha, | |
| state: state, | |
| context: `product-eng-approval-${team}`, | |
| description: msg, | |
| }); | |
| } | |
| async function fail(msg) { | |
| core.setFailed(msg); | |
| console.error(msg) | |
| await status('failure', msg) | |
| process.exit(1); | |
| } | |
| async function succeed(msg) { | |
| core.setOutput(msg); | |
| console.info(msg) | |
| await status('success', msg) | |
| process.exit(0); | |
| } | |
| await status('pending', `Waiting for approval from ${prettyTeamName} team member`) | |
| let latestReviews = {}; | |
| try { | |
| const iter = octokit.paginate.iterator(github.rest.pulls.listReviews, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: context.payload.pull_request.number, | |
| }); | |
| let pages = []; | |
| for await (const p of iter) { | |
| pages.push(p) | |
| } | |
| const laterThan = (a, b) => new Date(a.submitted_at) > new Date(b.submitted_at) ? a : b; | |
| latestReviews = pages.reduce((acc, page) => page.data.reduce((a, r) => ((!(r.user.login in a) || laterThan(r, a[r.user.login]) ? { ...a, [r.user.login]: r } : a)), acc) , {}) | |
| } catch (error) { | |
| await fail(`⚠️ Could not get reviews: ${error.status}`); | |
| } | |
| for (const reviewer in latestReviews) { | |
| if (latestReviews[reviewer].state !== 'APPROVED') { | |
| continue | |
| } | |
| try { | |
| const membership = await github.rest.teams.getMembershipForUserInOrg({ | |
| org: org, | |
| team_slug: team, | |
| username: reviewer, | |
| }); | |
| if (membership.data.state == 'active') { | |
| await succeed(`✅ Approved by ${reviewer} on behalf of ${prettyTeamName}`) | |
| } | |
| } catch (error) { | |
| if (error.status != 404) { | |
| await fail(`⚠️ Could not determine membership for ${reviewer} in ${prettyTeamName}: ${error.status}`) | |
| } | |
| } | |
| } | |
| await fail(`⚠️ No approvers found from ${prettyTeamName}`) | |