Skip to content

Commit

Permalink
Create CODEOWNERS and validation (#1153)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellahathaway authored Aug 7, 2024
1 parent 7146e2a commit 2abed36
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 0 deletions.
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
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"

0 comments on commit 2abed36

Please sign in to comment.