Skip to content

Commit 163aa6b

Browse files
authored
Merge pull request #4 from nix-open-org/validate-codeowners
Validate CODEOWNERS
2 parents 6c26838 + 9028d17 commit 163aa6b

File tree

6 files changed

+158
-13
lines changed

6 files changed

+158
-13
lines changed

.github/CODEOWNERS

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
# See ./org-repo.md
1+
# See doc/org-repo.md
2+
3+
/README.md @infinisil @zimbatm
4+
/CONTRIBUTING.md @infinisil @zimbatm
5+
/LICENSE @infinisil @zimbatm
26

37
/.github/CODEOWNERS @infinisil @zimbatm
8+
/.github/workflows @infinisil @zimbatm
9+
/scripts @infinisil @zimbatm
410

511
/doc/org-repo.md @infinisil @zimbatm
12+
/doc/discourse.md @infinisil @zimbatm
13+
/doc/github.md @infinisil @zimbatm
14+
/doc/matrix.md @infinisil @zimbatm
15+
/doc/moderation.md @infinisil @zimbatm
16+
/doc/nixos-releases.md @infinisil @zimbatm
17+
/doc/resources.md @infinisil @zimbatm
18+
/doc/rfcs.md @infinisil @zimbatm
19+
/doc/teams.md @infinisil @zimbatm

.github/workflows/ci.yml

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,67 @@
11
name: CI
22
on:
3-
pull_request:
4-
branches:
5-
- main
3+
# We use pull_request_target such that the code owner validation works for PRs from forks,
4+
# because we need repository secrets for that, which pull_request wouldn't allow from forks.
5+
# However, it's very important that we don't run code from forks without sandboxing it,
6+
# because that way anybody could potentially extract repository secrets!
7+
# Furthermore, using pull_request_target doesn't require manually approving first-time contributors
8+
pull_request_target:
69

710
jobs:
811
xrefcheck:
912
name: Check references
1013
runs-on: ubuntu-latest
1114
steps:
1215
- uses: actions/checkout@v4
16+
with:
17+
ref: refs/pull/${{ github.event.pull_request.number }}/merge
18+
path: untrusted-pr
19+
1320
- uses: serokell/xrefcheck-action@v1
21+
with:
22+
xrefcheck-args: "--root untrusted-pr"
23+
24+
codeowners:
25+
name: Validate codeowners
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: cachix/install-nix-action@v26
29+
30+
- uses: actions/checkout@v4
31+
with:
32+
path: trusted-base
33+
34+
- uses: actions/checkout@v4
35+
with:
36+
ref: refs/pull/${{ github.event.pull_request.number }}/merge
37+
path: untrusted-pr
38+
39+
- uses: mszostok/[email protected]
40+
with:
41+
# GitHub access token is required only if the `owners` check is enabled
42+
# See https://github.com/mszostok/codeowners-validator/blob/main/docs/gh-auth.md#public-repositories
43+
github_access_token: "${{ secrets.OWNERS_VALIDATOR_GITHUB_SECRET }}"
44+
45+
# The repository path in which CODEOWNERS file should be validated."
46+
repository_path: untrusted-pr
47+
48+
# The owner and repository name. For example, gh-codeowners/codeowners-samples. Used to check if GitHub team is in the given organization and has permission to the given repository."
49+
owner_checker_repository: "${{ github.repository }}"
50+
51+
# "The comma-separated list of experimental checks that should be executed. By default, all experimental checks are turned off. Possible values: notowned,avoid-shadowing"
52+
experimental_checks: "notowned,avoid-shadowing"
53+
54+
# Specifies whether CODEOWNERS may have unowned files. For example, `/infra/oncall-rotator/oncall-config.yml` doesn't have owner and this is not reported.
55+
owner_checker_allow_unowned_patterns: "false"
56+
57+
# Specifies whether only teams are allowed as owners of files.
58+
owner_checker_owners_must_be_teams: "false"
1459

15-
# TODO: Use https://github.com/marketplace/actions/github-codeowners-validator
60+
# The above validator doesn't currently ensure that people have write access: https://github.com/mszostok/codeowners-validator/issues/157
61+
# So we're doing it manually instead
62+
- name: Check that codeowners have write access
63+
# Important that we run the script from the base branch,
64+
# because otherwise a PR from a fork could change it to extract the secret
65+
run: trusted-base/scripts/unprivileged-owners.sh untrusted-pr ${{ github.repository }}
66+
env:
67+
GH_TOKEN: "${{ secrets.OWNERS_VALIDATOR_GITHUB_SECRET }}"

.github/workflows/review.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,26 @@ jobs:
1111
update:
1212
runs-on: ubuntu-latest
1313
steps:
14+
- uses: cachix/install-nix-action@v26
15+
1416
- uses: actions/checkout@v4
17+
with:
18+
path: repo
19+
20+
- name: Generate issue body
21+
run: repo/scripts/review-body.sh repo ${{ github.repository }} > body
22+
env:
23+
# This token has read-only admin access to see who has write access to this repo
24+
GH_TOKEN: "${{ secrets.OWNERS_VALIDATOR_GITHUB_SECRET }}"
25+
1526
- run: |
1627
gh api \
1728
--method POST \
1829
-H "Accept: application/vnd.github+json" \
1930
-H "X-GitHub-Api-Version: 2022-11-28" \
20-
/repos/"$GITHUB_REPOSITORY"/issues \
31+
/repos/${{ github.repository }}/issues \
2132
-f title="[$(date +'%Y %B')] Regular manual review " \
22-
-f body="$(./review-body.sh)"
33+
-F body=@body
2334
env:
35+
# This token has write access to only issues to create one
2436
GH_TOKEN: ${{ github.token }}

doc/org-repo.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ This repository itself is the entry point for documentation on official resource
44

55
Everybody in the [CODEOWNERS](../.github/CODEOWNERS) file has write permission to this repository.
66
This allows people to get automatic review requests and merge PRs for the files that concern them.
7-
8-
TODO: Enable branch protection to require reviews by code owners.
9-
TODO: Ensure that all files have a code owner
7+
PRs can only be merged if a codeowner for the respective files approves it, and all files need to have a codeowner entry.
108

119
Furthermore, the code owners for the CODEOWNERS file should have permission to give more people write access to this repository.
1210
These people get requested for reviews when new people add themselves to CODEOWNERS, allowing them to give write access when merged.

review-body.sh renamed to scripts/review-body.sh

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
#!/usr/bin/env bash
1+
#!/usr/bin/env nix-shell
2+
#!nix-shell -i bash --pure --keep GH_TOKEN -I nixpkgs=channel:nixpkgs-unstable -p codeowners github-cli gitMinimal
3+
24
set -euo pipefail
35

46
# This script outputs the contents of the regular review issue, see ./github/workflows/review.yml
57

6-
rev=$(git rev-parse HEAD)
8+
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
9+
10+
if (( $# != 2 )); then
11+
echo "Usage: $0 PATH OWNER/REPO"
12+
exit 1
13+
fi
14+
15+
root=$1
16+
repo=$2
17+
18+
rev=$(git -C "$root" rev-parse HEAD)
719

820
echo "Because the documentation in this repository may slowly deviate from reality, this monthly issue is created to regularly review the files.
921
@@ -30,4 +42,11 @@ while read -r file users; do
3042
continue
3143
fi
3244
echo "- [ ] \`$file\`: $users"
33-
done < .github/CODEOWNERS
45+
done < "$root"/.github/CODEOWNERS
46+
47+
echo ""
48+
49+
# Check that all code owners have write permissions
50+
# `|| true` because this script fails when there are code owners without permissions,
51+
# which is useful to fail PRs, but not here
52+
bash "$SCRIPT_DIR"/unprivileged-owners.sh "$root" "$repo" || true

scripts/unprivileged-owners.sh

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env nix-shell
2+
#!nix-shell -i bash --pure --keep GH_TOKEN -I nixpkgs=channel:nixpkgs-unstable -p codeowners github-cli
3+
4+
set -euo pipefail
5+
6+
tmp=$(mktemp -d)
7+
trap 'rm -rf "$tmp"' exit
8+
9+
if (( $# != 2 )); then
10+
echo "Usage: $0 PATH OWNER/REPO"
11+
exit 1
12+
fi
13+
14+
root=$1
15+
repo=$2
16+
17+
# Writes all code owners into $tmp/codeowners, one user per line (without @)
18+
while read -r -a fields; do
19+
# The first field is the filename
20+
unset 'fields[0]'
21+
if [[ "${fields[1]}" != "(unowned)" ]]; then
22+
(IFS=$'\n'; echo "${fields[*]##@}")
23+
fi
24+
done < <(cd "$root"; codeowners) |
25+
sort -u > "$tmp/codeowners"
26+
27+
# Get all users with push access
28+
gh api \
29+
-H "Accept: application/vnd.github+json" \
30+
-H "X-GitHub-Api-Version: 2022-11-28" \
31+
--method GET \
32+
-f permission=push \
33+
/repos/"$repo"/collaborators \
34+
-F per_page=100 \
35+
--paginate \
36+
--jq '.[].login' |
37+
sort > "$tmp/collaborators"
38+
39+
# Figure out all the owners that aren't collaborators
40+
readarray -t unprivilegedOwners < <(comm -23 "$tmp/codeowners" "$tmp/collaborators")
41+
42+
if (( "${#unprivilegedOwners[@]}" == 0 )); then
43+
echo "All code owners have write permission"
44+
else
45+
echo "There are code owners that don't have write permission. Either remove them as code owners or give them write permission:"
46+
for handle in "${unprivilegedOwners[@]}"; do
47+
echo "- [ ] @$handle"
48+
done
49+
exit 1
50+
fi

0 commit comments

Comments
 (0)