From 9bb4ce42171105bee97378f88ed1ae74eb3c5a6a Mon Sep 17 00:00:00 2001 From: Nasser Grainawi Date: Fri, 2 Oct 2026 12:02:40 -0700 Subject: [PATCH] Support workflow-run pull request checks Workflow-run payloads omit top-level pull request metadata, so the action could not check completed PR workflows. Read all associated pull requests from the event payload, fetch each PR's metadata for its commit count, and reuse the existing commits API. Continue to preserve direct PR handling and skip unrelated workflow runs. Add event fixtures covering multiple PRs, pull_request_target, non-PR runs, and empty pull request lists. Signed-off-by: Nasser Grainawi --- README.md | 10 +- action.yml | 1 + check_email_pr.sh | 101 +++++++++++++++--- test/check_event_types.sh | 63 +++++++++++ test/fake_bin/workflow_run/curl | 17 +++ ...orkflow_run_empty_pull_requests_event.json | 6 ++ test/workflow_run_event.json | 31 ++++++ 7 files changed, 209 insertions(+), 20 deletions(-) create mode 100755 test/fake_bin/workflow_run/curl create mode 100644 test/workflow_run_empty_pull_requests_event.json create mode 100644 test/workflow_run_event.json 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" + } + } + ] + } +}