Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create CODEOWNERS and validation #1153

Merged
merged 10 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/codeowners-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: CODEOWNERS Validation
run-name: ${{ github.actor }} is validating CODEOWNERS
on:
pull_request:
branches:
- main

jobs:
CheckDockerfileCodeowners:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4

- name: Ensure each CODEOWNER is a team
if: always()
run: ./eng/validate-codeowners.sh ownersAreTeams

- name: Check each Dockerfile for a CODEOWNER
if: always()
run: ./eng/validate-codeowners.sh dockerfilesHaveOwners

- name: Check for unused CODEOWNER paths
if: always()
run: ./eng/validate-codeowners.sh pathsAreUsed
46 changes: 46 additions & 0 deletions CODEOWNERS
ellahathaway marked this conversation as resolved.
Show resolved Hide resolved
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,4 +1,50 @@
# Users referenced in this file will automatically be requested as reviewers for PRs that modify the given paths.
# See https://help.github.com/articles/about-code-owners/

### General infra ###
* @dotnet/dotnet-docker-reviewers

### Dockerfiles ###

# common paths
src/**/helix/ @dotnet/dnceng
src/**/cross*/ @dotnet/runtime-infrastructure
src/**/webassembly*/ @dotnet/runtime-infrastructure

# almalinux
src/almalinux/**/source-build/ @dotnet/source-build-internal

# alpine
src/alpine/**/amd64/ @dotnet/source-build-internal

# azurelinux
src/azurelinux/**/android/ @dotnet/runtime-infrastructure
src/azurelinux/**/fpm/ @dotnet/runtime-infrastructure
src/azurelinux/**/opt/ @dotnet/runtime-infrastructure

# cbl-mariner
src/cbl-mariner/**/android/ @dotnet/runtime-infrastructure
src/cbl-mariner/**/fpm/ @dotnet/runtime-infrastructure
src/cbl-mariner/**/opt/ @dotnet/runtime-infrastructure
src/cbl-mariner/**/docker-testrunner/ @dotnet/dotnet-docker-reviewers
src/cbl-mariner/2.0/amd64/ @dotnet/runtime-infrastructure

# centos
src/centos/stream9/amd64/ @dotnet/source-build-internal

# debian
src/debian/11/amd64/ @dotnet/source-build-internal
src/debian/11/opt/arm64v8/ @dotnet/runtime-infrastructure
src/debian/12/gcc14/amd64/ @dotnet/runtime-infrastructure

# fedora
src/fedora/**/amd64/ @dotnet/source-build-internal

# ubuntu
src/ubuntu/**/debpkg/ @dotnet/runtime-infrastructure
src/ubuntu/22.04/mlnet/ @dotnet/runtime-infrastructure
src/ubuntu/22.04/opt/arm64v8/ @dotnet/runtime-infrastructure
src/ubuntu/common/coredeps/ @dotnet/runtime-infrastructure
src/ubuntu/20.04/Dockerfile @dotnet/source-build-internal
src/ubuntu/22.04/Dockerfile @dotnet/source-build-internal
src/ubuntu/24.04/Dockerfile @dotnet/source-build-internal
138 changes: 138 additions & 0 deletions eng/validate-codeowners.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/usr/bin/env bash

set -e

if [ $# -eq 0 ]; then
echo "No function name provided. Usage: ./validate-codeowners.sh <ownersAreTeams|pathsAreUsed|dockerfilesHaveOwners>"
exit 1
fi

declare -A codeOwnerEntries
readCodeOwnersFile() {
codeOwnersFilePath="CODEOWNERS"

# A newline is needed at the end of the file for the last line to be read
# but git likes to remove trailing newlines so we add one if it is missing
if [ "$(tail -c 1 "$codeOwnersFilePath")" != "" ]; then
echo "" >> "$codeOwnersFilePath"
fi

while IFS= read -r line; do

# Skip blank lines, comments, and * paths
if [[ "$line" =~ ^[[:space:]]*# ]] || [[ "$line" =~ ^[[:space:]]*$ ]] || [[ "$line" =~ ^\*[[:space:]] ]]; then
continue
fi

path=$(echo "$line" | awk '{print $1}' | awk '{$1=$1};1')
owner=$(echo "$line" | awk '{print $2}' | awk '{$1=$1};1')

# Escape periods
path=$(echo "$path" | sed 's/\./\\./g')

# A single asterisk matches anything that is not a slash (as long as it is not at the beginning of a pattern)
if [[ ! "$path" =~ ^\* ]]; then
path=$(echo "$path" | sed -E 's/([^*]|^)\*([^*]|$)/\1[^\/]*\2/g')
fi

# Trailing /** and leading **/ should match anything in all directories
path=$(echo "$path" | sed 's/\/\*\*$/\/.*/g')
path=$(echo "$path" | sed 's/^\*\*\//.*\//g')

# /**/ matches zero or more directories
path=$(echo "$path" | sed 's/\/\*\*\//\/.*/g')

# If the asterisk is at the beginning of the pattern or the pattern does not start with a slash, then match everything
if [[ "$path" =~ ^\* ]]; then
path=".$path"
elif [[ ! "$path" =~ ^/ && ! "$path" =~ ^\.\* ]]; then
path=".*$path"
fi

# If there is a trailing slash, then match everything below the directory
if [[ "${path: -1}" == "/" ]]; then
path="$path.*"
fi

path="^$path$"

codeOwnerEntries["$path"]="$owner"
done < "$codeOwnersFilePath"
}

ownersAreTeams() {
nonTeamOwners=()

for codeOwner in "${codeOwnerEntries[@]}"; do
if [[ "$codeOwner" != *"/"* ]]; then
nonTeamOwners+=("$codeOwner")
fi
done

if [[ ${#nonTeamOwners[@]} -gt 0 ]]; then
echo "The following CODEOWNERS are not teams:"
printf "%s\n" "${nonTeamOwners[@]}"
exit 1
fi

exit 0
}

pathsAreUsed() {
allFiles=$(find . -type f | sed 's/^\.//')
unusedPaths=()

for path in "${!codeOwnerEntries[@]}"; do
pathUsed=false
for file in $allFiles; do
if [[ "$file" =~ $path ]]; then
pathUsed=true
break
fi
done

if [[ "$pathUsed" == false ]]; then
# Undo regex changes
path=$(echo "$path" | sed 's/\[\^\/\]\*/\*/g' | sed 's/^\^//' | sed 's/\$$//')
unusedPaths+=("$path")
fi
done

if [[ ${#unusedPaths[@]} -gt 0 ]]; then
echo "The following paths in the CODEOWNERS file are not used by any file in the repository:"
printf "%s\n" "${unusedPaths[@]}"
exit 1
fi

exit 0
}

dockerfilesHaveOwners() {
dockerfiles=$(find . -type f -name "Dockerfile" | sed 's/^\.//')
filesWithoutOwner=()

for file in $dockerfiles; do
ownerFound=false
for pattern in "${!codeOwnerEntries[@]}"; do
if [[ "$file" =~ $pattern ]]; then
ownerFound=true
break
fi
done
if [ "$ownerFound" = false ]; then
filesWithoutOwner+=("$file")
fi
done

if [[ ${#filesWithoutOwner[@]} -gt 0 ]]; then
echo "The following Dockerfiles do not have an owner in the CODEOWNERS file:"
printf "%s\n" "${filesWithoutOwner[@]}"
exit 1
fi

exit 0
}

# Call the function passed as an argument
readCodeOwnersFile
"$1"
Loading