Răsfoiți Sursa

Exempt the bump bot from the external-PR scope guard

The External PR Scope Guard (#3353) and the auto-closer both look up the
PR author's collaborator permission and, for anyone who is not write/admin,
require the PR to ADD marketplace.json entries (additions-only). Internal
bump PRs are authored by github-actions[bot], which is not reported as a
member, so a SHA-bump — a legitimate MODIFY of an existing entry — fails the
guard (e.g. #3391 "modifies existing entry: astronomer-data-agents").

Add a shared isExemptAuthor() helper that exempts both org members and the
repo's own automation bot, and route both workflows through it. Safe under
pull_request_target: a fork PR cannot author as github-actions[bot] (only
the org's own GITHUB_TOKEN workflow can), and the member path is still a
real permission lookup. The helper also wraps getCollaboratorPermissionLevel
in try/catch — previously a non-collaborator/unknown-user lookup threw and
errored the job instead of falling through to scope evaluation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bryan Thompson 3 luni în urmă
părinte
comite
63f2b164fb

+ 30 - 1
.github/scripts/external-pr-scope.js

@@ -121,4 +121,33 @@ async function evaluate({ github, context }) {
   return analyze({ changedFiles, before, after, liveRepos: liveReposOf(liveBase) });
 }
 
-module.exports = { normalizeRepo, liveReposOf, analyze, readPlugins, evaluate, MARKETPLACE };
+// Authors that are NOT subject to the external-contributor scope rules:
+//   - the repo's own automation bot — its bump PRs legitimately MODIFY existing entries
+//     (SHA bumps), which the additions-only external-contributor rule forbids; AND
+//   - org members (write/admin).
+// Safe under pull_request_target: a fork PR cannot set its author to github-actions[bot]
+// (that login is only ever the org's own GITHUB_TOKEN workflow), and the member path is a
+// real permission lookup. Wrapped in try/catch because getCollaboratorPermissionLevel throws
+// for a non-collaborator/unknown user — without this, both callers would error the job rather
+// than fall through to scope evaluation.
+const EXEMPT_BOTS = new Set(['github-actions[bot]']);
+
+async function isExemptAuthor({ github, context }) {
+  const author = context.payload.pull_request.user.login;
+  if (EXEMPT_BOTS.has(author)) {
+    return { exempt: true, reason: `${author} is the trusted automation bot` };
+  }
+  try {
+    const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
+      owner: context.repo.owner, repo: context.repo.repo, username: author,
+    });
+    if (['admin', 'write'].includes(data.permission)) {
+      return { exempt: true, reason: `${author} is ${data.permission} (member)` };
+    }
+  } catch (e) {
+    // not a collaborator / lookup failed → not exempt; fall through to scope evaluation
+  }
+  return { exempt: false };
+}
+
+module.exports = { normalizeRepo, liveReposOf, analyze, readPlugins, evaluate, isExemptAuthor, MARKETPLACE };

+ 6 - 8
.github/workflows/close-external-prs.yml

@@ -23,14 +23,13 @@ jobs:
           script: |
             const author = context.payload.pull_request.user.login;
 
-            const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              username: author
-            });
+            const { evaluate, isExemptAuthor } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/external-pr-scope.js`);
 
-            if (['admin', 'write'].includes(data.permission)) {
-              console.log(`${author} has ${data.permission} access, allowing PR`);
+            // Members (write/admin) and the repo's own automation bot (bump SHA PRs) are never
+            // auto-closed.
+            const ex = await isExemptAuthor({ github, context });
+            if (ex.exempt) {
+              console.log(`${ex.reason} — allowing PR`);
               return;
             }
 
@@ -40,7 +39,6 @@ jobs:
             // of allowed repos is derived from the live marketplace.) This grants only the
             // right to open a reviewable PR; the External PR Scope Guard required check and a
             // maintainer approval still gate the merge.
-            const { evaluate } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/external-pr-scope.js`);
             const result = await evaluate({ github, context });
             if (result.ok && result.added.length > 0) {
               console.log(`In-scope external contribution (adds: ${result.added.join(', ')}) — allowing PR.`);

+ 6 - 7
.github/workflows/external-pr-scope-guard.yml

@@ -29,17 +29,16 @@ jobs:
       - uses: actions/github-script@v7
         with:
           script: |
-            const author = context.payload.pull_request.user.login;
+            const { evaluate, isExemptAuthor } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/external-pr-scope.js`);
 
-            const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({
-              owner: context.repo.owner, repo: context.repo.repo, username: author,
-            });
-            if (['admin', 'write'].includes(perm.permission)) {
-              console.log(`${author} is ${perm.permission} (member) — scope guard not applicable.`);
+            // Members (write/admin) and the repo's own automation bot (bump SHA PRs) are
+            // unrestricted; only genuinely external contributions are scope-checked.
+            const ex = await isExemptAuthor({ github, context });
+            if (ex.exempt) {
+              console.log(`${ex.reason} — scope guard not applicable.`);
               return;
             }
 
-            const { evaluate } = require(`${process.env.GITHUB_WORKSPACE}/.github/scripts/external-pr-scope.js`);
             const result = await evaluate({ github, context });
 
             if (!result.ok) {