Skip to content

Commit

Permalink
Add additional tests + use script
Browse files Browse the repository at this point in the history
  • Loading branch information
ellahathaway committed Aug 5, 2024
1 parent bdd4d58 commit 8f732b5
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 112 deletions.
136 changes: 25 additions & 111 deletions .github/workflows/codeowners-validation.yml
Original file line number Diff line number Diff line change
@@ -1,111 +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: Define functions
run: |
#!/bin/bash
set -e
read_codeowners_file() {
local codeOwnersFilePath="CODEOWNERS"
local codeOwnerPaths=()
while IFS= read -r line; do
if [[ "$line" =~ ^\s*# ]] || [[ -z "$line" ]] || [[ "$line" =~ ^[[:space:]]*$ ]]; then
continue
fi
path=$(echo "$line" | cut -d' ' -f1 | sed 's/^[[:space:]]*//' | sed 's/[\/&]/\\&/g' | sed 's/\./\\./g' | sed 's/\*/.*/g')
codeOwnerPaths+=("$path")
done < "$codeOwnersFilePath"
echo "${codeOwnerPaths[@]}"
}
export -f read_codeowners_file
- name: Ensure each CODEOWNER is a team
run: |
#!/bin/bash
set -e
codeOwnersFilePath="CODEOWNERS"
codeOwners=$(grep -v '^#' "$codeOwnersFilePath" | cut -d' ' -f2)
for codeOwner in $codeOwners; do
if [[ "$codeOwner" != *"/"* ]]; then
echo "The CODEOWNERS file must contain teams, not individual users."
exit 1
fi
done
- name: Check each Dockerfile for a CODEOWNER
run: |
#!/bin/bash
set -e
codeOwnerPaths=($(read_codeowners_file))
filesWithoutOwner=()
dockerfiles=$(find . -type f -name "Dockerfile")
for dockerfile in $dockerfiles; do
ownerFound=false
for path in "${codeOwnerPaths[@]}"; do
if [[ "$dockerfile" =~ $path ]]; then
ownerFound=true
break
fi
done
if [[ "$ownerFound" == false ]]; then
filesWithoutOwner+=("$dockerfile")
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
- name: Check for unused CODEOWNER paths
run: |
#!/bin/bash
set -e
codeOwnerPaths=($(read_codeowners_file))
unusedPaths=()
allFiles=$(find . -type f)
for path in "${codeOwnerPaths[@]}"; do
pathUsed=false
for file in $allFiles; do
if [[ "$file" =~ $path ]]; then
pathUsed=true
break
fi
done
if [[ "$pathUsed" == false ]]; then
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
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: ./validate-codeowners.sh ownersAreTeams

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

- name: Check for unused CODEOWNER paths
if: always()
run: ./validate-codeowners.sh pathsAreUsed
1 change: 0 additions & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,3 @@ 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

118 changes: 118 additions & 0 deletions validate-codeowners.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env bash

set -e

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

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

while IFS= read -r line; do
# Skip blank lines and comments
if [[ "$line" =~ ^\s*# ]] || [[ -z "$line" ]] || [[ "$line" =~ ^[[:space:]]*$ ]]; then
continue
fi

path=$(echo "$line" | awk '{print $1}' | sed 's/[[:space:]]*$//')
owner=$(echo "$line" | sed 's/^[^ ]* //' | sed 's/[[:space:]]*$//')

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

# Single * matches anything that is not a slash
# Double ** matches anything
# Trailing / matches anything
# Remove leading slashes
path=$(echo "$path" | sed -E 's/([^*]|^)\*([^*]|$)/\1[^\/]*\2/g')
path=$(echo "$path" | sed 's/\*\*/.*/g')
if [[ "${path: -1}" == "/" ]]; then
path="$path.*"
fi
path=$(echo "$path" | sed 's/^\///')
path="^$path$"

# Use git check-ignore to determine if the path matches the patterns
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 8f732b5

Please sign in to comment.