You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sj...@apache.org on 2026/10/16 22:13:33 UTC

(maven-gh-actions-shared) branch v5 updated: Support workflow_run in PR Check [ci skip]

This is an automated email from the ASF dual-hosted git repository.

slawekjaranowski pushed a commit to branch v5
in repository https://gitbox.apache.org/repos/asf/maven-gh-actions-shared.git


The following commit(s) were added to refs/heads/v5 by this push:
     new df8b19b  Support workflow_run in PR Check [ci skip]
df8b19b is described below

commit df8b19b1ad9c6b6fe8d8ff3afe83a4e3b4d7236f
Author: Slawomir Jaranowski <s....@gmail.com>
AuthorDate: Fri Oct 16 23:50:33 2026 +0200

    Support workflow_run in PR Check [ci skip]
    
    A new pr-number job finds the pull request: from the event for
    pull_request(_target), with the pr-number action from the head commit
    for workflow_run, whose payload has no pull request.
    
    The author checks read the pull request from the API instead of the
    event payload: bots return early, organization members and committers
    pass like a fixed declaration, so an old comment is still hidden.
    
    Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---
 .github/workflows/pr-check.yml | 91 +++++++++++++++++++++++++++---------------
 1 file changed, 58 insertions(+), 33 deletions(-)

diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml
index e34e9d4..1b89a56 100644
--- a/.github/workflows/pr-check.yml
+++ b/.github/workflows/pr-check.yml
@@ -25,33 +25,47 @@ on:
 permissions: {}
 
 jobs:
+  # Number of the pull request being checked: taken from the event for pull_request(_target),
+  # looked up from the head commit for workflow_run (whose pull_requests is empty for forks).
+  pr-number:
+    permissions:
+      pull-requests: read
+    runs-on: ubuntu-slim
+    outputs:
+      number: ${{ github.event.pull_request.number || steps.pr.outputs.number }}
+    steps:
+      - name: Find the pull request
+        id: pr
+        if: github.event_name == 'workflow_run'
+        uses: apache/maven-gh-actions-shared/.github/actions/pr-number@v5
+        with:
+          head-sha: ${{ github.event.workflow_run.head_sha }}
+          state: open
+
   # The PR description must contain at least one CHECKED Apache license declaration
   # ("- [x] I hereby declare ...").
   # - Missing       -> adds ONE comment and fails.
   # - Fixed         -> hides the comment as outdated and passes.
   # - Removed again -> unhides the same comment (no duplicates).
   license-declaration:
+    needs: pr-number
     permissions:
       pull-requests: write
       issues: write # PR comments are "issue comments" in the API
     runs-on: ubuntu-slim
-    # Skip (a skipped job counts as successful, so a required check doesn't block the merge):
-    # - PRs opened by bots (Dependabot, Renovate, other GitHub Apps),
-    # - PRs opened by members/committers of the organization owning the repository (apache).
-    #   MEMBER       = org member with public membership (PMC member)
-    #   OWNER        = org owner
-    #   COLLABORATOR = committer whose org membership is private (covers most Apache committers)
-    if: >-
-      github.event.pull_request.user.type != 'Bot' &&
-      !contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association)
+    # Skip when no open pull request matches the commit of a workflow_run (a newer push triggers its own run).
+    # A skipped job counts as successful, so a required check doesn't block the merge.
+    if: needs.pr-number.outputs.number != ''
     # Prevents races (e.g. opened + edited in quick succession => two comments).
     # Do NOT define the same concurrency group in the caller – it would deadlock.
     concurrency:
-      group: license-declaration-check-${{ github.repository }}-${{ github.event.pull_request.number }}
+      group: license-declaration-check-${{ github.repository }}-${{ needs.pr-number.outputs.number }}
       cancel-in-progress: false
     steps:
       - name: Check license declaration
         uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
+        env:
+          PR_NUMBER: ${{ needs.pr-number.outputs.number }}
         with:
           script: |
             const MARKER = '<!-- license-declaration-check -->';
@@ -64,17 +78,12 @@ jobs:
 
             const { owner, repo } = context.repo;
 
-            // The workflow only makes sense for pull request events
-            const prNumber = context.payload.pull_request?.number;
+            const prNumber = Number(process.env.PR_NUMBER);
             if (!prNumber) {
-              core.setFailed(`This workflow must be called from pull_request or pull_request_target events (got "${context.eventName}").`);
+              core.setFailed(`This workflow must be called from pull_request, pull_request_target or workflow_run events (got "${context.eventName}").`);
               return;
             }
 
-            // Values used by the job condition, logged for diagnosis
-            const { user, author_association } = context.payload.pull_request;
-            core.info(`Author: ${user?.login}, type: ${user?.type}, author_association: ${author_association}`);
-
             // "- [x] <declaration>" or "* [X] <declaration>", one per line, optional indentation/trailing spaces.
             // Links may use either http:// or https://.
             const escape = s => s
@@ -85,23 +94,39 @@ jobs:
               'm'
             );
 
-            // Fetch the current description from the API (the event payload may be stale on "Re-run jobs")
+            // Read the pull request from the API: the workflow_run payload doesn't contain it,
+            // and the pull_request payload may be stale on "Re-run jobs"
             const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
+            core.info(`Author: ${pr.user.login}, type: ${pr.user.type}, author_association: ${pr.author_association}`);
+
+            // Bots (Dependabot, Renovate, other GitHub Apps) don't need the declaration
+            if (pr.user.type === 'Bot') {
+              core.info(`✅ ${pr.user.login} is a bot - declaration not required.`);
+              return;
+            }
+
             const hasDeclaration = regex.test(pr.body || '');
 
-            // Committers don't need the declaration.
-            // Note: private org members appear as CONTRIBUTOR in the event payload,
-            // so we check write access via the API instead of relying on author_association.
+            // Members/committers of the organization owning the repository (apache) don't need the declaration.
+            //   MEMBER       = org member with public membership (PMC member)
+            //   OWNER        = org owner
+            //   COLLABORATOR = committer whose org membership is private (covers most Apache committers)
+            const isMember = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(pr.author_association);
+
+            // Note: private org members may appear as CONTRIBUTOR,
+            // so we also check write access via the API instead of relying on author_association only.
             let hasWriteAccess = false;
-            try {
-              const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({
-                owner, repo, username: pr.user.login,
-              });
-              hasWriteAccess = ['admin', 'maintain', 'write'].includes(perm.permission);
-              core.info(`Permission of ${pr.user.login}: ${perm.permission}`);
-            } catch (err) {
-              // Fall back to requiring the declaration - a committer's PR may then fail the check
-              core.warning(`Cannot read the permission of ${pr.user.login}: ${err.message}`);
+            if (!hasDeclaration && !isMember) {
+              try {
+                const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({
+                  owner, repo, username: pr.user.login,
+                });
+                hasWriteAccess = ['admin', 'maintain', 'write'].includes(perm.permission);
+                core.info(`Permission of ${pr.user.login}: ${perm.permission}`);
+              } catch (err) {
+                // Fall back to requiring the declaration - a committer's PR may then fail the check
+                core.warning(`Cannot read the permission of ${pr.user.login}: ${err.message}`);
+              }
             }
 
             // Find our comment: author must be a bot and the comment must contain the marker.
@@ -132,7 +157,7 @@ jobs:
             }
 
             // ---------- Declaration OK ----------
-            if (hasDeclaration || hasWriteAccess) {
+            if (hasDeclaration || isMember || hasWriteAccess) {
               if (existing && !isMinimized) {
                 try {
                   await github.graphql(
@@ -149,8 +174,8 @@ jobs:
                   core.warning(`Cannot hide the existing comment: ${err.message}`);
                 }
               }
-              core.info(hasDeclaration
-                ? '✅ PR description contains a checked license declaration.'
+              core.info(hasDeclaration ? '✅ PR description contains a checked license declaration.'
+                : isMember ? `✅ ${pr.user.login} is ${pr.author_association} - declaration not required.`
                 : `✅ ${pr.user.login} has write access to the repository - declaration not required.`);
               return;
             }