From e02808f442f3f7464cd44f14d941672d886f692a Mon Sep 17 00:00:00 2001 From: Michael Hudgins Date: Wed, 7 May 2025 13:36:45 +0000 Subject: [PATCH 1/7] Create file check aciton --- .github/workflows/file-check-tests.yaml | 30 ++++ file_check/README.md | 8 ++ file_check/action.yaml | 29 ++++ file_check/file_check.sh | 176 ++++++++++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 .github/workflows/file-check-tests.yaml create mode 100644 file_check/README.md create mode 100644 file_check/action.yaml create mode 100755 file_check/file_check.sh diff --git a/.github/workflows/file-check-tests.yaml b/.github/workflows/file-check-tests.yaml new file mode 100644 index 00000000..741605d5 --- /dev/null +++ b/.github/workflows/file-check-tests.yaml @@ -0,0 +1,30 @@ +# A workflow to test waiting for remote connections to the runner, when an error occurs. +name: Connection Test - On Error +on: + pull_request: + paths: + - file_check + - .github/workflows/file-check-tests.yaml + branches: + - main +defaults: + run: + shell: bash +# Cancel any previous iterations if a new commit is pushed +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + test-valid: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # ratchet:actions/checkout@v4 + - name: Test Valid Case 1 + uses: ./file_check/ + with: + patterns: | + *.yaml + file_check/* + + diff --git a/file_check/README.md b/file_check/README.md new file mode 100644 index 00000000..27744bf2 --- /dev/null +++ b/file_check/README.md @@ -0,0 +1,8 @@ +Basic reuseable script to filter if a job should run based on changes + +Usage of this action makes the following assumptions and prerequsites for safe usage + +* The base ubutnu actions image is being used to run it +* The repo using this action does not run GH actions on untrusted PRs without prior approval +* Contents and pull_requests:read should be the only permissions given to this workflow +* This only operates on pull_request events diff --git a/file_check/action.yaml b/file_check/action.yaml new file mode 100644 index 00000000..2aeb5ac0 --- /dev/null +++ b/file_check/action.yaml @@ -0,0 +1,29 @@ +# Copyright 2025 Google LLC + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# https://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +name: "Check changed files" +description: 'Action to determine if a file glob was changed in this PR' +inputs: + patterns: + description: 'Newline seperated list of file patterns to filter upon' + required: false + default: "" +runs: + using: "composite" + steps: + - name: Check modified files + shell: bash + env: + FILE_PATTERNS: ${{ inputs.patterns }} + run: | + "$GITHUB_ACTION_PATH/file_check.sh" diff --git a/file_check/file_check.sh b/file_check/file_check.sh new file mode 100755 index 00000000..358105d7 --- /dev/null +++ b/file_check/file_check.sh @@ -0,0 +1,176 @@ +#!/bin/bash + +# Copyright 2025 Google LLC + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# https://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# Script to check if provided file patterns match any files +# changed in the current GitHub Pull Request. + +# It is not reccomended to use this script outside of repos managed by ML Infra +# See the readme.md for assumptions for security and usage + +# Required environment variables these should normally be supplied by the actions environement: +# GITHUB_REPOSITORY: The owner and repository name (e.g., "owner/repo"). +# GITHUB_PULL_REQUEST_NUMBER: The number of the pull request. +# GITHUB_TOKEN: (Optional but recommended) A GitHub token for authentication. + +# Check for required commands +if ! command -v curl &> /dev/null; then + echo "Error: curl command not found. Curl is required in the base image for this action." + exit 1 +fi + +if ! command -v jq &> /dev/null; then + echo "Error: jq command not found. jq is required in the base image for this action." + exit 1 +fi + +# Check for required GitHub environment variables +if [ -z "$GITHUB_REPOSITORY" ]; then + echo "Error: GITHUB_REPOSITORY environment variable is not set." + echo "Please set it to your repository (e.g., 'owner/repo')." + exit 1 +fi + +if [ -z "$GITHUB_PULL_REQUEST_NUMBER" ]; then + echo "Error: GITHUB_PULL_REQUEST_NUMBER environment variable is not set." + echo "Please set it to the pull request number." + exit 1 +fi + +# GITHUB_TOKEN is optional but recommended for private repos or to avoid rate limits +if [ -z "$GITHUB_TOKEN" ]; then + echo "Warning: GITHUB_TOKEN environment variable is not set." + echo "API requests will be unauthenticated and may be rate-limited or fail for private repositories." + # Script will proceed without a token if not set. +fi + + +echo $FILE_PATTERNS + +exit 1 + + +# Construct GitHub API URL +OWNER_REPO="$GITHUB_REPOSITORY" +PR_NUMBER="$GITHUB_PULL_REQUEST_NUMBER" +API_URL="https://api.github.com/repos/${OWNER_REPO}/pulls/${PR_NUMBER}/files" + +echo "Fetching changed files for PR #$PR_NUMBER in repository $OWNER_REPO..." + +# Prepare curl command arguments +curl_args=("-s" "-L") # -s for silent, -L to follow redirects +curl_args+=("-H" "Accept: application/vnd.github.v3+json") + +if [ -n "$GITHUB_TOKEN" ]; then + curl_args+=("-H" "Authorization: Bearer $GITHUB_TOKEN") +fi + +# Append URL to arguments +curl_args+=("$API_URL") + +# Fetch changed files from GitHub API +# -w "%{http_code}" appends the HTTP status code to the output +# Store curl output (body + http_code) in a variable +raw_curl_output=$(curl "${curl_args[@]}" -w "\n%{http_code}") +curl_exit_status=$? + +if [ $curl_exit_status -ne 0 ]; then + echo "Error: curl command failed with exit status $curl_exit_status." + exit 1 +fi + +# Extract HTTP status code (last line of raw_curl_output) +http_code=$(echo "$raw_curl_output" | tail -n1) +# Extract JSON body (everything except the last line) +json_body=$(echo "$raw_curl_output" | sed '$d') + +if [ "$http_code" -ne 200 ]; then + echo "Error: GitHub API request failed with HTTP status $http_code." + echo "Response body: $json_body" + exit 1 +fi + +# Parse the JSON response to get a list of filenames +# Use set -o pipefail to ensure errors in the pipeline are caught +# jq -r '.[]?.filename // empty' extracts filenames, handles nulls gracefully, and outputs raw strings. +# If the PR has no files, .[] will be empty, and jq will produce no output and exit 0. +set -o pipefail +CHANGED_FILES_LIST=$(echo "$json_body" | jq -r '.[]?.filename // empty') +jq_exit_status=$? +set +o pipefail # Important to reset pipefail + +if [ $jq_exit_status -ne 0 ]; then + echo "Error: jq failed to parse GitHub API response. Exit status: $jq_exit_status" + echo "JSON Body was: $json_body" + exit 1 +fi + +if [ -z "$CHANGED_FILES_LIST" ]; then + echo "No files found changed in PR #$PR_NUMBER or an issue occurred retrieving them." +fi + +echo "----------------------------------------------------" +echo "Processing patterns against changed files in PR #$PR_NUMBER:" +echo "----------------------------------------------------" + +# Iterate over each pattern provided as a command-line argument +# "$@" expands to all positional parameters as separate words. +at_least_one_match_found=false + +for pattern in "$@"; do + found_match_for_current_pattern=false # Reset flag for each new pattern + + # Read the list of changed files line by line (from the string variable) + # IFS= prevents leading/trailing whitespace from being trimmed from lines. + # -r prevents backslash escapes from being interpreted. + # The `|| [ -n "$filepath" ]` ensures that the last line is processed + # even if it doesn't end with a newline character. + # <<< "$CHANGED_FILES_LIST" is a "here string", feeding the variable's content to the loop. + while IFS= read -r filepath || [ -n "$filepath" ]; do + # Skip empty lines that might result from jq processing if any filename was problematic + # (though `// empty` should prevent this for .filename) + if [ -z "$filepath" ]; then + continue + fi + + # Perform glob pattern matching. + # In bash's [[ ... ]] construct, if the right-hand side of == or != + # is an unquoted string, it's treated as a pattern (glob). + if [[ "$filepath" == $pattern ]]; then + found_match_for_current_pattern=true + at_least_one_match_found=true + echo "Changed file $filepath matches pattern $pattern" + fi + done <<< "$CHANGED_FILES_LIST" # Feed the list of changed files to the while loop + + # Report the result for the current pattern + if [ "$found_match_for_current_pattern" = true ]; then + echo "Pattern '$pattern': Found a matching file in the PR." + else + echo "Pattern '$pattern': No matching file found in the PR." + fi +done + +# Echo final status out +echo "Pattern was matched: $at_least_one_match_found" + +if [ -n "$GITHUB_ENV" ]; then + # Store to the github env that at least one match was found + echo "patten_matched=$at_least_one_match_found" >> $GITHUB_ENV +fi + +# Exit with success code +exit 0 From 299a5771861456b8b2ac25327b258269f1cd5e07 Mon Sep 17 00:00:00 2001 From: Michael Hudgins Date: Wed, 7 May 2025 13:46:05 +0000 Subject: [PATCH 2/7] Set the PR number --- .github/workflows/file-check-tests.yaml | 5 +---- file_check/action.yaml | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/file-check-tests.yaml b/.github/workflows/file-check-tests.yaml index 741605d5..12a3243c 100644 --- a/.github/workflows/file-check-tests.yaml +++ b/.github/workflows/file-check-tests.yaml @@ -1,10 +1,7 @@ # A workflow to test waiting for remote connections to the runner, when an error occurs. -name: Connection Test - On Error +name: Test File Check Cases on: pull_request: - paths: - - file_check - - .github/workflows/file-check-tests.yaml branches: - main defaults: diff --git a/file_check/action.yaml b/file_check/action.yaml index 2aeb5ac0..4b86c436 100644 --- a/file_check/action.yaml +++ b/file_check/action.yaml @@ -25,5 +25,6 @@ runs: shell: bash env: FILE_PATTERNS: ${{ inputs.patterns }} + GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.number }} run: | "$GITHUB_ACTION_PATH/file_check.sh" From 46ca0dba57e345fe4a68c8c2f787bdc6df4466b2 Mon Sep 17 00:00:00 2001 From: Michael Hudgins Date: Wed, 7 May 2025 13:48:33 +0000 Subject: [PATCH 3/7] Remove early exit --- file_check/file_check.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/file_check/file_check.sh b/file_check/file_check.sh index 358105d7..13729566 100755 --- a/file_check/file_check.sh +++ b/file_check/file_check.sh @@ -58,10 +58,7 @@ if [ -z "$GITHUB_TOKEN" ]; then fi -echo $FILE_PATTERNS - -exit 1 - +echo "Patterns to match against $FILE_PATTERNS" # Construct GitHub API URL OWNER_REPO="$GITHUB_REPOSITORY" From f8c5b6b1bdf32c1d784c33cd924ec78617373e4b Mon Sep 17 00:00:00 2001 From: Michael Hudgins Date: Wed, 7 May 2025 13:52:31 +0000 Subject: [PATCH 4/7] Add more logging --- file_check/file_check.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/file_check/file_check.sh b/file_check/file_check.sh index 13729566..cbdd4aea 100755 --- a/file_check/file_check.sh +++ b/file_check/file_check.sh @@ -58,7 +58,7 @@ if [ -z "$GITHUB_TOKEN" ]; then fi -echo "Patterns to match against $FILE_PATTERNS" +echo "Patterns to match against \n$FILE_PATTERNS" # Construct GitHub API URL OWNER_REPO="$GITHUB_REPOSITORY" @@ -119,6 +119,8 @@ if [ -z "$CHANGED_FILES_LIST" ]; then echo "No files found changed in PR #$PR_NUMBER or an issue occurred retrieving them." fi +echo "Files modified by PR: $CHANGED_FILES_LIST" + echo "----------------------------------------------------" echo "Processing patterns against changed files in PR #$PR_NUMBER:" echo "----------------------------------------------------" @@ -128,6 +130,7 @@ echo "----------------------------------------------------" at_least_one_match_found=false for pattern in "$@"; do + echo "Checking pattern $pattern" found_match_for_current_pattern=false # Reset flag for each new pattern # Read the list of changed files line by line (from the string variable) From e51c5249a450b854b29df79e7dddd3f7d9e429ca Mon Sep 17 00:00:00 2001 From: Michael Hudgins Date: Wed, 7 May 2025 13:57:30 +0000 Subject: [PATCH 5/7] What is installed in actions env --- file_check/file_check.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/file_check/file_check.sh b/file_check/file_check.sh index cbdd4aea..3da4e3ce 100755 --- a/file_check/file_check.sh +++ b/file_check/file_check.sh @@ -58,7 +58,7 @@ if [ -z "$GITHUB_TOKEN" ]; then fi -echo "Patterns to match against \n$FILE_PATTERNS" +echo "Patterns to match against $FILE_PATTERNS" # Construct GitHub API URL OWNER_REPO="$GITHUB_REPOSITORY" @@ -100,6 +100,8 @@ if [ "$http_code" -ne 200 ]; then exit 1 fi +python3 -m pip freeze + # Parse the JSON response to get a list of filenames # Use set -o pipefail to ensure errors in the pipeline are caught # jq -r '.[]?.filename // empty' extracts filenames, handles nulls gracefully, and outputs raw strings. From 32a85ed2a78b75f5f2ffad20762957bf13aadbab Mon Sep 17 00:00:00 2001 From: Michael Hudgins Date: Wed, 7 May 2025 14:01:05 +0000 Subject: [PATCH 6/7] Add github token --- file_check/action.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/file_check/action.yaml b/file_check/action.yaml index 4b86c436..8f2011e5 100644 --- a/file_check/action.yaml +++ b/file_check/action.yaml @@ -26,5 +26,6 @@ runs: env: FILE_PATTERNS: ${{ inputs.patterns }} GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | "$GITHUB_ACTION_PATH/file_check.sh" From 0b19878962ed057b98d700114c90cc7b05ad6612 Mon Sep 17 00:00:00 2001 From: Michael Hudgins Date: Wed, 7 May 2025 14:07:16 +0000 Subject: [PATCH 7/7] Use the other method of getting token --- file_check/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/file_check/action.yaml b/file_check/action.yaml index 8f2011e5..031ca269 100644 --- a/file_check/action.yaml +++ b/file_check/action.yaml @@ -26,6 +26,6 @@ runs: env: FILE_PATTERNS: ${{ inputs.patterns }} GITHUB_PULL_REQUEST_NUMBER: ${{ github.event.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} run: | "$GITHUB_ACTION_PATH/file_check.sh"