diff --git a/README.md b/README.md index 71f375e..9346948 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,12 @@ Qualcomm PR email addresses checker For each commit in a PR, validates that the commit's author and committer email addresses are appropriate for the repo. -**NOTE:** This action should be used with `pull_request` or -`pull_request_target` events. It exits successfully without checking anything -for other event types during normal execution. The `--test` option intentionally -uses fixture data regardless of the event type. +**NOTE:** This action should be used with `pull_request`, +`pull_request_target`, or `workflow_run` events. For `workflow_run` events, it +checks all pull requests associated with a workflow whose triggering event is a +pull request event. It exits successfully without checking anything for other +event types during normal execution. The `--test` option intentionally uses +fixture data regardless of the event type. ## Example Usage diff --git a/action.yml b/action.yml index 78598b8..fb349dc 100644 --- a/action.yml +++ b/action.yml @@ -12,3 +12,4 @@ runs: GITHUB_TOKEN: ${{ github.token }} PULL_NUMBER: ${{ github.event.number }} COMMITS_COUNT: ${{ github.event.pull_request.commits }} + WORKFLOW_RUN_EVENT: ${{ github.event.workflow_run.event }} diff --git a/check_email_pr.sh b/check_email_pr.sh index 62154f7..34a4a22 100755 --- a/check_email_pr.sh +++ b/check_email_pr.sh @@ -9,6 +9,32 @@ MYDIR=$(dirname -- "$MYPROG") debug() { echo "::debug::$1" >&2 ; } # message error() { echo "::error::$1" >&2 ; } # message +# Make an authenticated request to the GitHub REST API. +github_api_get() { + local endpoint=$1 + debug "Getting data from $endpoint" + local debug_opts=() + [ -n "$RUNNER_DEBUG" ] && debug_opts=("--verbose" "--progress-meter" "--show-error") + curl -L --no-progress-meter --fail-with-body \ + "${debug_opts[@]}" \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "$endpoint" +} + +# https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#get-a-pull-request +get_pr_metadata() { + local endpoint="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls/$PULL_NUMBER" + local metadata + metadata=$(github_api_get "$endpoint") || return 1 + COMMITS_COUNT=$(jq -r '.commits // empty' <<< "$metadata") + if [ -z "$COMMITS_COUNT" ] ; then + error "Cannot determine commit count for pull request $PULL_NUMBER" + return 1 + fi +} + # https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-commits-on-a-pull-request get_pr_commits() { if [ -n "$TEST_MODE" ] ; then @@ -20,15 +46,7 @@ get_pr_commits() { # TODO: Handle paginated results # https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2022-11-28#using-link-headers local endpoint="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls/$PULL_NUMBER/commits?per_page=$COMMITS_COUNT" - debug "Getting commits from $endpoint" - local debug_opts=() - [ -n "$RUNNER_DEBUG" ] && debug_opts=("--verbose" "--progress-meter" "--show-error") - curl -L --no-progress-meter --fail-with-body \ - "${debug_opts[@]}" \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "$endpoint" + github_api_get "$endpoint" } # https://docs.github.com/en/rest/repos/custom-properties?apiVersion=2022-11-28#get-all-custom-property-values-for-a-repository @@ -69,10 +87,36 @@ EOF is_pr_event() { case "${GITHUB_EVENT_NAME:-}" in pull_request|pull_request_target) return 0 ;; + workflow_run) + case "${WORKFLOW_RUN_EVENT:-}" in + pull_request|pull_request_target) return 0 ;; + esac + return 1 + ;; *) return 1 ;; esac } +get_pr_numbers() { + if [ "${GITHUB_EVENT_NAME:-}" = "workflow_run" ] ; then + jq -r '.workflow_run.pull_requests[]?.number // empty' "$GITHUB_EVENT_PATH" + else + echo "$PULL_NUMBER" + fi +} + +check_pr_commits() { + local result=0 + while read -r pr_commit ; do + debug "Running check on: $pr_commit" + "$MYDIR"/src/check_email.sh \ + --commit-json "$pr_commit" \ + --custom-properties-json "$custom_properties" \ + "${TEST_MODE[@]}" || result=1 + done < <(get_pr_commits | split_commits_and_add_metadata) + return "$result" +} + TEST_MODE=() while [ $# -gt 0 ] ; do case "$1" in @@ -87,14 +131,39 @@ if [ "${#TEST_MODE[@]}" -eq 0 ] && ! is_pr_event ; then exit 0 fi +if [ "${#TEST_MODE[@]}" -gt 0 ] ; then + custom_properties=$(get_custom_properties) + check_pr_commits + exit $? +fi + +if [ "${GITHUB_EVENT_NAME:-}" = "workflow_run" ] && \ + { [ -z "${GITHUB_EVENT_PATH:-}" ] || [ ! -r "$GITHUB_EVENT_PATH" ]; } ; then + error "Cannot read workflow_run event payload" + exit 1 +fi + +PR_NUMBERS=$(get_pr_numbers) || { + error "Cannot determine pull requests from workflow_run event payload" + exit 1 +} + +if [ -z "$PR_NUMBERS" ] ; then + debug "Skipping email check: workflow_run has no associated pull requests" + exit 0 +fi + RESULT=0 custom_properties=$(get_custom_properties) -while read -r pr_commit ; do - debug "Running check on: $pr_commit" - "$MYDIR"/src/check_email.sh \ - --commit-json "$pr_commit" \ - --custom-properties-json "$custom_properties" \ - "${TEST_MODE[@]}" || RESULT=1 -done < <(get_pr_commits | split_commits_and_add_metadata) +while read -r PULL_NUMBER ; do + [ -n "$PULL_NUMBER" ] || continue + if [ "${GITHUB_EVENT_NAME:-}" = "workflow_run" ] ; then + get_pr_metadata || { + RESULT=1 + continue + } + fi + check_pr_commits || RESULT=1 +done <<< "$PR_NUMBERS" exit $RESULT diff --git a/test/check_event_types.sh b/test/check_event_types.sh index c06821f..3ec2c59 100755 --- a/test/check_event_types.sh +++ b/test/check_event_types.sh @@ -49,8 +49,71 @@ run_pull_request_event_test() { grep -q 'Running check on:' <<< "$output" } +run_workflow_run_pull_request_test() { + local event output status + event=$1 + + if output=$(env -u TEST_MODE \ + GITHUB_EVENT_PATH="$ROOT_DIR/test/workflow_run_event.json" \ + GITHUB_API_URL=https://api.example.com \ + GITHUB_EVENT_NAME=workflow_run \ + WORKFLOW_RUN_EVENT="$event" \ + PATH="$ROOT_DIR/test/fake_bin/workflow_run:$PATH" \ + "$ROOT_DIR/check_email_pr.sh" 2>&1) ; then + status=0 + else + status=$? + fi + + [ "$status" -eq 0 ] + [ "$(grep -c 'Running check on:' <<< "$output")" -eq 4 ] +} + +run_workflow_run_non_pr_event_test() { + local output status + + if output=$(env -u TEST_MODE -u PULL_NUMBER -u COMMITS_COUNT \ + GITHUB_EVENT_PATH="$ROOT_DIR/test/workflow_run_event.json" \ + GITHUB_API_URL= \ + GITHUB_EVENT_NAME=workflow_run \ + WORKFLOW_RUN_EVENT=push \ + PATH="$ROOT_DIR/test/fake_bin:$PATH" \ + "$ROOT_DIR/check_email_pr.sh" 2>&1) ; then + status=0 + else + status=$? + fi + + [ "$status" -eq 0 ] + ! grep -q 'curl was called' <<< "$output" + ! grep -q 'jq was called' <<< "$output" +} + +run_workflow_run_empty_pull_requests_test() { + local output status + + if output=$(env -u TEST_MODE -u PULL_NUMBER -u COMMITS_COUNT \ + GITHUB_EVENT_PATH="$ROOT_DIR/test/workflow_run_empty_pull_requests_event.json" \ + GITHUB_API_URL=https://api.example.com \ + GITHUB_EVENT_NAME=workflow_run \ + WORKFLOW_RUN_EVENT=pull_request \ + PATH="$ROOT_DIR/test/fake_bin/workflow_run:$PATH" \ + "$ROOT_DIR/check_email_pr.sh" 2>&1) ; then + status=0 + else + status=$? + fi + + [ "$status" -eq 0 ] + grep -q 'no associated pull requests' <<< "$output" +} + for event in push workflow_dispatch schedule unset ; do run_non_pr_event_test "$event" done run_pull_request_event_test pull_request run_pull_request_event_test pull_request_target +run_workflow_run_pull_request_test pull_request +run_workflow_run_pull_request_test pull_request_target +run_workflow_run_non_pr_event_test +run_workflow_run_empty_pull_requests_test diff --git a/test/fake_bin/workflow_run/curl b/test/fake_bin/workflow_run/curl new file mode 100755 index 0000000..3d5f93e --- /dev/null +++ b/test/fake_bin/workflow_run/curl @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +endpoint=${@: -1} + +case "$endpoint" in + */pulls/101|*/pulls/202) + echo '{"commits": 2}' + ;; + */pulls/101/commits\?per_page=2|*/pulls/202/commits\?per_page=2) + cat "$(cd -- "$(dirname -- "$0")/../.." && pwd)/pr_list_commits.json" + ;; + *) + echo "Unexpected endpoint: $endpoint" >&2 + exit 1 + ;; +esac diff --git a/test/workflow_run_empty_pull_requests_event.json b/test/workflow_run_empty_pull_requests_event.json new file mode 100644 index 0000000..38981aa --- /dev/null +++ b/test/workflow_run_empty_pull_requests_event.json @@ -0,0 +1,6 @@ +{ + "workflow_run": { + "event": "pull_request", + "pull_requests": [] + } +} diff --git a/test/workflow_run_event.json b/test/workflow_run_event.json new file mode 100644 index 0000000..2f20926 --- /dev/null +++ b/test/workflow_run_event.json @@ -0,0 +1,31 @@ +{ + "workflow_run": { + "event": "pull_request", + "pull_requests": [ + { + "number": 101, + "url": "https://api.github.com/repos/example/example/pulls/101", + "head": { + "ref": "feature-one", + "sha": "1111111111111111111111111111111111111111" + }, + "base": { + "ref": "main", + "sha": "2222222222222222222222222222222222222222" + } + }, + { + "number": 202, + "url": "https://api.github.com/repos/example/example/pulls/202", + "head": { + "ref": "feature-two", + "sha": "3333333333333333333333333333333333333333" + }, + "base": { + "ref": "main", + "sha": "4444444444444444444444444444444444444444" + } + } + ] + } +}