Skip to content

Commit

Permalink
request reviews from codeowners
Browse files Browse the repository at this point in the history
  • Loading branch information
dmathieu committed Oct 30, 2024
1 parent c1cdce7 commit 6016ccd
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/request_codeowners_review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: 'Request reviews from code owners of a PR'
on:
pull_request:
types: [opened, synchronize]

jobs:
request_codeowners_review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
if: ${{ github.repository_owner == 'open-telemetry' }}
steps:
- uses: actions/checkout@v4

- name: Run request_codeowners_review.sh
run: ./tools/request_codeowners_review.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR: ${{ github.event.number }}
40 changes: 40 additions & 0 deletions tools/get-codeowners.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
#
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
#
# This script checks the GitHub CODEOWNERS file for any code owners
# of contrib components and returns a string of the code owners if it
# finds them.

set -euo pipefail

get_component_type() {
echo "${COMPONENT}" | cut -f 1 -d '/'
}

get_codeowners() {
# grep arguments explained:
# -m 1: Match the first occurrence
# ^: Match from the beginning of the line
# ${1}: Insert first argument given to this function
# [\/]\?: Match 0 or 1 instances of a forward slash
# \s: Match any whitespace character
(grep -m 1 "^${1}[\/]\?\s" CODEOWNERS || true) | \
sed 's/ */ /g' | \
cut -f3- -d ' '
}

if [[ -z "${COMPONENT:-}" ]]; then
echo "COMPONENT has not been set, please ensure it is set."
exit 1
fi

OWNERS="$(get_codeowners "${COMPONENT}")"

if [[ -z "${OWNERS:-}" ]]; then
COMPONENT_TYPE=$(get_component_type "${COMPONENT}")
OWNERS="$(get_codeowners "${COMPONENT}${COMPONENT_TYPE}")"
fi

echo "${OWNERS}"
11 changes: 11 additions & 0 deletions tools/get-components.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env sh
#
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
#
# Get a list of components within the repository that have some form of ownership
# ascribed to them.

grep -E '^[A-Za-z0-9/]' CODEOWNERS | \
awk '{ print $1 }' | \
sed -E 's%(.+)/$%\1%'
119 changes: 119 additions & 0 deletions tools/request_codeowners_review.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
#
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0
#
# Adds code owners without write access as reviewers on a PR. Note that
# the code owners must still be a member of the `open-telemetry`
# organization.
#
# Note that since this script is considered a requirement for PRs,
# it should never fail.

set -euo pipefail

if [[ -z "${REPO:-}" || -z "${PR:-}" ]]; then
echo "One or more of REPO and PR have not been set, please ensure each is set."
exit 0
fi

main () {
CUR_DIRECTORY=$(dirname "$0")

# Reviews may have comments that need to be cleaned up for jq,
# so restrict output to only printable characters and ensure escape
# sequences are removed.
# The latestReviews key only returns the latest review for each reviewer,
# cutting out any other reviews. We use that instead of requestedReviews
# since we need to get the list of users eligible for requesting another
# review. The GitHub CLI does not offer a list of all reviewers, which
# is only available through the API. To cut down on API calls to GitHub,
# we use the latest reviews to determine which users to filter out.
JSON=$(gh pr view "${PR}" --json "files,author,latestReviews" | tr -dc '[:print:]' | sed -E 's/\\[a-z]//g')
AUTHOR=$(echo -n "${JSON}"| jq -r '.author.login')
FILES=$(echo -n "${JSON}"| jq -r '.files[].path')
REVIEW_LOGINS=$(echo -n "${JSON}"| jq -r '.latestReviews[].author.login')
COMPONENTS=$(bash "${CUR_DIRECTORY}/get-components.sh")
REVIEWERS=""
declare -A PROCESSED_COMPONENTS
declare -A REVIEWED

for REVIEWER in ${REVIEW_LOGINS}; do
# GitHub adds "app/" in front of user logins. The API docs don't make
# it clear what this means or whether it will always be present. The
# '/' character isn't a valid character for usernames, so this won't
# replace characters within a username.
REVIEWED["@${REVIEWER//app\//}"]=true
done

if [[ -v REVIEWED[@] ]]; then
echo "Users that have already reviewed this PR and will not have another review requested:" "${!REVIEWED[@]}"
else
echo "This PR has not yet been reviewed, all code owners are eligible for a review request"
fi

for COMPONENT in ${COMPONENTS}; do
# Files will be in alphabetical order and there are many files to
# a component, so loop through files in an inner loop. This allows
# us to remove all files for a component from the list so they
# won't be checked against the remaining components in the components
# list. This provides a meaningful speedup in practice.
for FILE in ${FILES}; do
MATCH=$(echo -n "${FILE}" | grep -E "^${COMPONENT}" || true)

if [[ -z "${MATCH}" ]]; then
continue
fi

# If we match a file with a component we don't need to process the file again.
FILES=$(echo -n "${FILES}" | grep -v "${FILE}")

if [[ -v PROCESSED_COMPONENTS["${COMPONENT}"] ]]; then
continue
fi

PROCESSED_COMPONENTS["${COMPONENT}"]=true

OWNERS=$(COMPONENT="${COMPONENT}" bash "${CUR_DIRECTORY}/get-codeowners.sh")

for OWNER in ${OWNERS}; do
# Users that leave reviews are removed from the "requested reviewers"
# list and are eligible to have another review requested. We only want
# to request a review once, so remove them from the list.
if [[ -v REVIEWED["${OWNER}"] || "${OWNER}" = "@${AUTHOR}" ]]; then
continue
fi

if [[ -n "${REVIEWERS}" ]]; then
REVIEWERS+=","
fi
REVIEWERS+=$(echo -n "${OWNER}" | sed -E 's/@(.+)/"\1"/')
done
done
done

# We have to use the GitHub API directly due to an issue with how the CLI
# handles PR updates that causes it require access to organization teams,
# and the GitHub token doesn't provide that permission.
# For more: https://github.com/cli/cli/issues/4844
#
# The GitHub API validates that authors are not requested to review, but
# accepts duplicate logins and logins that are already reviewers.
if [[ -n "${REVIEWERS}" ]]; then
echo "Requesting review from ${REVIEWERS}"
curl \
-X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
"https://api.github.com/repos/${REPO}/pulls/${PR}/requested_reviewers" \
-d "{\"reviewers\":[${REVIEWERS}]}" \
| jq ".message" \
|| echo "jq was unable to parse GitHub's response"
else
echo "No code owners found"
fi
}

# We don't want this workflow to ever fail and block a PR,
# so ensure all errors are caught.
main || echo "Failed to run $0"

0 comments on commit 6016ccd

Please sign in to comment.