Skip to content

Commit 52e4730

Browse files
committed
fix: check org membership before labeling contributions
1 parent 43c6106 commit 52e4730

1 file changed

Lines changed: 92 additions & 50 deletions

File tree

‎.github/workflows/label-external-contributions.yml‎

Lines changed: 92 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -20,61 +20,103 @@ jobs:
2020
pull-requests: write
2121

2222
steps:
23+
- name: Create organization membership token
24+
id: membership-token
25+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
26+
with:
27+
client-id: ${{ vars.CODEQL_ORG_MEMBERS_APP_CLIENT_ID }}
28+
private-key: ${{ secrets.CODEQL_ORG_MEMBERS_APP_PRIVATE_KEY }}
29+
owner: ${{ github.repository_owner }}
30+
permission-members: read
31+
2332
- name: Label external contributions
33+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
2434
env:
25-
GH_TOKEN: ${{ github.token }}
26-
REPO: ${{ github.repository }}
27-
run: |
28-
set -euo pipefail
35+
MEMBERS_TOKEN: ${{ steps.membership-token.outputs.token }}
36+
with:
37+
github-token: ${{ github.token }}
38+
retries: 3
39+
script: |
40+
const label = "external-contribution";
41+
const { owner, repo } = context.repo;
42+
const fullRepo = `${owner}/${repo}`;
43+
const since = new Date(Date.now() - 60 * 60 * 1000).toISOString();
44+
const membershipGithub = getOctokit(process.env.MEMBERS_TOKEN);
45+
let membershipCheckFailed = false;
46+
47+
const issues = await github.paginate("GET /repos/{owner}/{repo}/issues", {
48+
owner,
49+
repo,
50+
state: "open",
51+
since,
52+
sort: "updated",
53+
direction: "desc",
54+
per_page: 100,
55+
});
56+
57+
for (const issue of issues) {
58+
if (!issue.pull_request) {
59+
continue;
60+
}
61+
62+
const { data: pull } = await github.request(
63+
"GET /repos/{owner}/{repo}/pulls/{pull_number}",
64+
{ owner, repo, pull_number: issue.number },
65+
);
2966
30-
label="external-contribution"
31-
updated_cutoff=$(date -u -d "1 hour ago" "+%Y-%m-%dT%H:%M:%SZ")
67+
if (
68+
pull.state !== "open" ||
69+
pull.draft ||
70+
pull.base.repo.full_name !== fullRepo ||
71+
!pull.head.repo?.full_name ||
72+
pull.head.repo.full_name === fullRepo ||
73+
pull.user?.type !== "User" ||
74+
!pull.user.login ||
75+
pull.labels.some(({ name }) => name === label)
76+
) {
77+
continue;
78+
}
3279
33-
while IFS= read -r pr_number; do
34-
if [[ ! "$pr_number" =~ ^[1-9][0-9]*$ ]]; then
35-
echo "Skipping malformed pull request number."
36-
continue
37-
fi
80+
const events = await github.paginate(
81+
"GET /repos/{owner}/{repo}/issues/{issue_number}/events",
82+
{ owner, repo, issue_number: issue.number, per_page: 100 },
83+
);
3884
39-
pr_json=$(gh api "repos/$REPO/pulls/$pr_number")
40-
if ! jq -e \
41-
--arg repo "$REPO" \
42-
--arg label "$label" \
43-
'.state == "open" and
44-
.draft == false and
45-
.base.repo.full_name == $repo and
46-
(.head.repo.full_name | type == "string") and
47-
.head.repo.full_name != $repo and
48-
.user.type == "User" and
49-
.author_association != "MEMBER" and
50-
.author_association != "OWNER" and
51-
(any(.labels[]?; .name == $label) | not)' \
52-
>/dev/null <<<"$pr_json"; then
53-
continue
54-
fi
85+
if (
86+
events.some(
87+
(event) =>
88+
event.event === "labeled" && event.label?.name === label,
89+
)
90+
) {
91+
continue;
92+
}
5593
56-
events=$(gh api --paginate \
57-
"repos/$REPO/issues/$pr_number/events?per_page=100" |
58-
jq -cs 'add')
94+
try {
95+
await membershipGithub.request(
96+
"GET /orgs/{org}/members/{username}",
97+
{ org: owner, username: pull.user.login },
98+
);
99+
core.info(
100+
`Pull request #${issue.number} was opened by an organization member; skipping.`,
101+
);
102+
continue;
103+
} catch (error) {
104+
if (error.status !== 404) {
105+
core.error(
106+
`Membership check failed for pull request #${issue.number}: ${error.message}`,
107+
);
108+
membershipCheckFailed = true;
109+
continue;
110+
}
111+
}
59112
60-
if jq -e --arg label "$label" \
61-
'any(.[]; .event == "labeled" and .label.name == $label)' \
62-
>/dev/null <<<"$events"; then
63-
continue
64-
fi
113+
await github.request(
114+
"POST /repos/{owner}/{repo}/issues/{issue_number}/labels",
115+
{ owner, repo, issue_number: issue.number, labels: [label] },
116+
);
117+
core.info(`Labelled pull request #${issue.number}.`);
118+
}
65119
66-
jq -n --arg label "$label" '{labels: [$label]}' |
67-
gh api --method POST \
68-
"repos/$REPO/issues/$pr_number/labels" \
69-
--input - \
70-
>/dev/null
71-
echo "Labelled pull request #$pr_number."
72-
done < <(
73-
gh api --method GET --paginate "repos/$REPO/issues" \
74-
-f state=open \
75-
-f since="$updated_cutoff" \
76-
-f sort=updated \
77-
-f direction=desc \
78-
-f per_page=100 |
79-
jq -r '.[] | select(.pull_request != null) | .number'
80-
)
120+
if (membershipCheckFailed) {
121+
core.setFailed("One or more organization membership checks failed.");
122+
}

0 commit comments

Comments
 (0)