From c0c338874b01045b63ce89824dad1711df9afdbd Mon Sep 17 00:00:00 2001 From: charly-bg <85914018+charly-bg@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:27:11 -0300 Subject: [PATCH 1/3] chore: exclude some labels on enforce approvals workflow --- .github/workflows/enforce-group-approvals.yml | 197 +++++++++--------- 1 file changed, 94 insertions(+), 103 deletions(-) diff --git a/.github/workflows/enforce-group-approvals.yml b/.github/workflows/enforce-group-approvals.yml index b239e922d6..d15fadcdb2 100644 --- a/.github/workflows/enforce-group-approvals.yml +++ b/.github/workflows/enforce-group-approvals.yml @@ -7,111 +7,102 @@ on: types: [submitted, review_requested, ready_for_review] jobs: + setup-context: + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.set-vars.outputs.pr_number }} + repo_owner: ${{ steps.set-vars.outputs.repo_owner }} + repo_name: ${{ steps.set-vars.outputs.repo_name }} + + steps: + - name: Set PR number and repo details + id: set-vars + run: | + echo "Fetching context details..." + PR_NUMBER=${{ github.event.pull_request.number }} + if [ -z "$PR_NUMBER" ]; then + echo "::error::PR number is missing from the event context." + exit 1 + fi + + REPO_OWNER=${{ github.repository_owner }} + REPO_NAME=$(echo ${{ github.repository }} | cut -d'/' -f2) + + echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT + echo "repo_owner=$REPO_OWNER" >> $GITHUB_OUTPUT + echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT + enforce-approvals: + needs: setup-context runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }} + PR_NUMBER: ${{ needs.setup-context.outputs.pr_number }} + REPO_OWNER: ${{ needs.setup-context.outputs.repo_owner }} + REPO_NAME: ${{ needs.setup-context.outputs.repo_name }} steps: + - name: Check for excluding labels + run: | + # Check if the label "auto-pr" is set + LABELS=$(gh api \ + "/repos/$REPO_OWNER/$REPO_NAME/issues/$PR_NUMBER/labels" --jq '.[].name') + + if echo "$LABELS" | grep -q "auto-pr"; then + echo "SKIP=true" >> $GITHUB_ENV + echo ">>> Skipping approval validation as 'auto-pr' label is set." + exit 0 + fi + - name: Validate Group Approvals - uses: actions/github-script@v6 - with: - script: | - const fetchTeamMembers = async (teamSlug, token) => { - const url = `https://api.github.com/orgs/${context.repo.owner}/teams/${teamSlug}/members`; - console.log(`Fetching team members for team: ${teamSlug}`); - - try { - const response = await fetch(url, { - method: "GET", - headers: { - authorization: `Bearer ${token}`, - accept: "application/vnd.github.v3+json", - }, - }); - - console.log("Response Status:", response.status, response.statusText); - - const data = await response.json(); - if (!response.ok) { - console.error("Response Headers:", JSON.stringify([...response.headers], null, 2)); - throw new Error(`HTTP ${response.status} - ${data.message}`); - } - - console.log(`Fetched Members for ${teamSlug}:`, data.map(member => member.login)); - return data.map(member => member.login); - } catch (error) { - console.error(`Error fetching team members for ${teamSlug}:`, error.message); - throw error; - } - }; - - const fetchPullRequestReviews = async (token) => { - if (!context.payload.pull_request) { - throw new Error("This workflow must be triggered by a pull_request event."); - } - - const url = `https://api.github.com/repos/${context.repo.owner}/${context.repo.repo}/pulls/${context.payload.pull_request.number}/reviews`; - console.log("Fetching PR reviews..."); - - try { - const response = await fetch(url, { - method: "GET", - headers: { - authorization: `Bearer ${token}`, - accept: "application/vnd.github.v3+json", - }, - }); - - console.log("Response Status:", response.status, response.statusText); - - const data = await response.json(); - if (!response.ok) { - console.error("Response Headers:", JSON.stringify([...response.headers], null, 2)); - throw new Error(`HTTP ${response.status} - ${data.message}`); - } - - console.log("Fetched PR Reviews:", data); - return data; - } catch (error) { - console.error("Error fetching PR reviews:", error.message); - throw error; - } - }; - - const token = process.env.ORG_ACCESS_TOKEN; - const QA_TEAM = "qa"; - const DEV_TEAM = "explorer-devs"; - - // Fetch team members - const qaMembers = await fetchTeamMembers(QA_TEAM, token); - const devMembers = await fetchTeamMembers(DEV_TEAM, token); - - // Fetch PR reviews - const reviews = await fetchPullRequestReviews(token); - - // Filter unique APPROVED reviews - const uniqueApprovals = reviews.filter( - (review, index, self) => - review.state === "APPROVED" && - self.findIndex(r => r.user.login === review.user.login) === index - ); - - console.log("Unique Approvals:", uniqueApprovals.map(approval => approval.user.login)); - - // Validate approvals - const hasQaApproval = uniqueApprovals.some(review => qaMembers.includes(review.user.login)); - const hasDevApproval = uniqueApprovals.some(review => devMembers.includes(review.user.login)); - - if (!hasQaApproval || !hasDevApproval) { - const missing = [ - !hasQaApproval ? "QA approval" : null, - !hasDevApproval ? "DEV approval" : null, - ] - .filter(Boolean) - .join(" and "); - - throw new Error(`PR must have at least 1 ${missing}.`); - } - - console.log("PR has the required approvals."); - env: - ORG_ACCESS_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }} + if: env.SKIP != 'true' + run: | + # Define the teams + QA_TEAM="qa" + DEV_TEAM="explorer-devs" + + # Fetch team members + fetch_team_members() { + local team_slug=$1 + echo "Fetching team members for team: $team_slug" + gh api \ + "/orgs/$REPO_OWNER/teams/$team_slug/members" --jq '.[].login' + } + + QA_MEMBERS=$(fetch_team_members "$QA_TEAM") + DEV_MEMBERS=$(fetch_team_members "$DEV_TEAM") + + echo ">>> QA Team Members: $QA_MEMBERS" + echo ">>> DEV Team Members: $DEV_MEMBERS" + + # Fetch PR reviews + echo ">>> Fetching PR reviews for PR #$PR_NUMBER" + PR_REVIEWS=$(gh api \ + "/repos/$REPO_OWNER/$REPO_NAME/pulls/$PR_NUMBER/reviews" --jq '.[] | select(.state == "APPROVED") | .user.login') + + echo "Approved Reviews: $PR_REVIEWS" + + # Validate approvals + HAS_QA_APPROVAL=false + HAS_DEV_APPROVAL=false + + for reviewer in $PR_REVIEWS; do + if echo "$QA_MEMBERS" | grep -q "^$reviewer$"; then + HAS_QA_APPROVAL=true + fi + + if echo "$DEV_MEMBERS" | grep -q "^$reviewer$"; then + HAS_DEV_APPROVAL=true + fi + done + + if [ "$HAS_QA_APPROVAL" != true ] || [ "$HAS_DEV_APPROVAL" != true ]; then + MISSING=() + [ "$HAS_QA_APPROVAL" != true ] && MISSING+=("QA approval") + [ "$HAS_DEV_APPROVAL" != true ] && MISSING+=("DEV approval") + MISSING_MSG=$(IFS=", "; echo "${MISSING[*]}") + echo "::error::PR must have at least 1: $MISSING_MSG." + exit 1 + fi + + echo ">>> PR has the required approvals." From b0f7b0ea4bbe74db896307306051159407b6221a Mon Sep 17 00:00:00 2001 From: charly-bg <85914018+charly-bg@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:34:40 -0300 Subject: [PATCH 2/3] chore: add additional types on pull requests triggers --- .github/workflows/enforce-group-approvals.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/enforce-group-approvals.yml b/.github/workflows/enforce-group-approvals.yml index d15fadcdb2..0c8176c3f0 100644 --- a/.github/workflows/enforce-group-approvals.yml +++ b/.github/workflows/enforce-group-approvals.yml @@ -4,7 +4,14 @@ on: pull_request: branches: - dev # Only run for PRs targeting the dev branch - types: [submitted, review_requested, ready_for_review] + types: + - submitted + - opened + - reopened + - synchronize + - review_requested + - ready_for_review + - labeled jobs: setup-context: From e673db491ddc8579675dbd805ddf567d24ae2cd5 Mon Sep 17 00:00:00 2001 From: charly-bg <85914018+charly-bg@users.noreply.github.com> Date: Wed, 15 Jan 2025 15:10:42 -0300 Subject: [PATCH 3/3] chore: add trigger for pull request review only on dev branch --- .github/workflows/enforce-group-approvals.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/enforce-group-approvals.yml b/.github/workflows/enforce-group-approvals.yml index 0c8176c3f0..0e19fbf2c1 100644 --- a/.github/workflows/enforce-group-approvals.yml +++ b/.github/workflows/enforce-group-approvals.yml @@ -2,19 +2,19 @@ name: Enforce QA and DEV Approvals on: pull_request: - branches: - - dev # Only run for PRs targeting the dev branch types: - - submitted - opened - reopened - synchronize - review_requested - ready_for_review - labeled + - unlabeled + pull_request_review: jobs: setup-context: + if: github.event.pull_request.base.ref == 'dev' runs-on: ubuntu-latest outputs: pr_number: ${{ steps.set-vars.outputs.pr_number }} @@ -40,6 +40,7 @@ jobs: echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT enforce-approvals: + if: github.event.pull_request.base.ref == 'dev' needs: setup-context runs-on: ubuntu-latest env: