From 23f2e1882d8763801c3c400628a566d893bc7596 Mon Sep 17 00:00:00 2001 From: SATYAsasini Date: Mon, 2 Jun 2025 16:28:30 +0530 Subject: [PATCH 1/3] feat: support for multiple issue --- .github/workflows/validateIssue.sh | 200 ++++++++++++++++++----------- 1 file changed, 125 insertions(+), 75 deletions(-) diff --git a/.github/workflows/validateIssue.sh b/.github/workflows/validateIssue.sh index 5f61707..c1ab445 100644 --- a/.github/workflows/validateIssue.sh +++ b/.github/workflows/validateIssue.sh @@ -1,9 +1,11 @@ #!/bin/bash +# Print base and head repository information echo "base or target repo : $BASE_REPO" echo "head or source repo : $HEAD_REPO" -if [[ $HEAD_REPO == $BASE_REPO ]]; then +# Determine if the PR is from a forked repository +if [[ $HEAD_REPO == $BASE_REPO ]]; then export forked=false else export forked=true @@ -14,6 +16,7 @@ if [[ "$TITLE" =~ ^(doc:|docs:|chore:|misc:|Release:|release:|Sync:|sync:) ]]; t echo "Skipping validation for docs/chore PR." echo "PR NUMBER-: $PRNUM " if [[ "$forked" == "false" ]]; then + # If not a forked PR, remove 'Issue-verification-failed' and add 'Ready-to-Review' label gh pr edit $PRNUM --remove-label "PR:Issue-verification-failed" gh pr edit $PRNUM --add-label "PR:Ready-to-Review" fi @@ -21,105 +24,152 @@ if [[ "$TITLE" =~ ^(doc:|docs:|chore:|misc:|Release:|release:|Sync:|sync:) ]]; t fi # Define all issue matching patterns +# These patterns cover various ways issues can be linked in a PR body patterns=( - "((Fixes|fixes|Resolves|resolves) #[0-9]+)" - "((Fixes|fixes|Resolves|resolves) https://github.com/devtron-labs/(devtron|sprint-tasks|devops-sprint|devtron-enterprise)/issues/[0-9]+)" - "((Fixes|fixes|Resolves|resolves):? https://github.com/devtron-labs/(devtron|sprint-tasks|devops-sprint|devtron-enterprise)/issues/[0-9]+)" - "((Fixes|fixes|Resolves|resolves) devtron-labs/devtron#[0-9]+)" + "((Fixes|fixes|Resolves|resolves) #[0-9]+)" # e.g., Fixes #123 + "((Fixes|fixes|Resolves|resolves) https://github.com/devtron-labs/(devtron|sprint-tasks|devops-sprint|devtron-enterprise)/issues/[0-9]+)" # e.g., Fixes https://github.com/devtron-labs/devtron/issues/123 + "((Fixes|fixes|Resolves|resolves):? https://github.com/devtron-labs/(devtron|sprint-tasks|devops-sprint|devtron-enterprise)/issues/[0-9]+)" # e.g., Fixes: https://github.com/devtron-labs/devtron/issues/123 + "((Fixes|fixes|Resolves|resolves) devtron-labs/devtron#[0-9]+)" # e.g., Fixes devtron-labs/devtron#123 "((Fixes|fixes|Resolves|resolves) devtron-labs/sprint-tasks#[0-9]+)" "((Fixes|fixes|Resolves|resolves) devtron-labs/devops-sprint#[0-9]+)" - "(Fixes|fixes|Resolves|resolves):?\\s+\\[#([0-9]+)\\]" - "((Fixes|fixes|Resolves|resolves):? #devtron-labs/devops-sprint/issues/[0-9]+)" + "(Fixes|fixes|Resolves|resolves):?\\s+\\[#([0-9]+)\\]" # e.g., Fixes [#123] + "((Fixes|fixes|Resolves|resolves):? #devtron-labs/devops-sprint/issues/[0-9]+)" # e.g., Fixes: #devtron-labs/devops-sprint/issues/123 "((Fixes|fixes|Resolves|resolves):? #devtron-labs/sprint-tasks/issues/[0-9]+)" ) -# Extract issue number and repo from PR body -extract_issue_number() { - local pattern="$1" # Get the pattern as the first argument to the function - - # Check if PR_BODY matches the provided pattern using Bash's =~ regex operator - if [[ "$PR_BODY" =~ $pattern ]]; then - echo "matched for this pattern $pattern" - - issue_num=$(echo "$PR_BODY" | grep -oE "$pattern" | grep -oE "[0-9]+") - - # Extract the repository name (e.g., devtron-labs/devtron) from PR_BODY using grep - repo=$(echo "$PR_BODY" | grep -oE "devtron-labs/[a-zA-Z0-9_-]+") - echo "Extracted issue number: $issue_num from repo: $repo" - - return 0 # Return success - else - echo "No match for the pattern $pattern" - fi - return 1 # Return failure if no match +# Function to extract all unique issue numbers and their corresponding repositories from PR_BODY +# It iterates through defined patterns and extracts all matches. +# Returns a list of "issue_num,repo" pairs, one per line. +extract_all_issues() { + local -a found_issues=() + local default_repo="devtron-labs/devtron" # Default repository if not explicitly mentioned in the link + + # Loop through each defined pattern + for pattern in "${patterns[@]}"; do + # Use grep -oE to find all non-overlapping matches for the current pattern in PR_BODY + matches=$(echo "$PR_BODY" | grep -oE "$pattern") + + # If matches are found for the current pattern + if [[ -n "$matches" ]]; then + echo "Matched for pattern: $pattern" + # Read each match into the 'match' variable + while IFS= read -r match; do + # Extract the issue number (sequence of digits) from the matched string + local current_issue_num=$(echo "$match" | grep -oE "[0-9]+") + # Extract the repository name (e.g., devtron-labs/devtron) from the matched string + local current_repo=$(echo "$match" | grep -oE "devtron-labs/[a-zA-Z0-9_-]+") + + # If no specific repository is found in the link, use the default + if [[ -z "$current_repo" ]]; then + current_repo="$default_repo" + fi + + # If a valid issue number was extracted, add it to the list + if [[ -n "$current_issue_num" ]]; then + found_issues+=("$current_issue_num,$current_repo") + echo "Extracted issue: $current_issue_num from repo: $current_repo" + fi + done <<< "$matches" # Use a here-string to feed matches into the while loop + fi + done + # Print unique issue-repo pairs, sorted, to avoid duplicate validations + printf "%s\n" "${found_issues[@]}" | sort -u } -issue_num="" -repo="devtron-labs/devtron" # Default repo -for pattern in "${patterns[@]}"; do - echo "Now checking for $pattern" - extract_issue_number "$pattern" && break -done +# Call the function to extract all unique issue-repo pairs and store them in an array +readarray -t all_issues < <(extract_all_issues) -if [[ -z "$issue_num" ]]; then - echo "No valid issue number found." +# Check if any issues were found in the PR body +if [[ ${#all_issues[@]} -eq 0 ]]; then + echo "No valid issue number found in PR body." if [[ "$forked" == "false" ]]; then + # If not a forked PR, add 'Issue-verification-failed' and remove 'Ready-to-Review' label gh pr edit $PRNUM --add-label "PR:Issue-verification-failed" gh pr edit $PRNUM --remove-label "PR:Ready-to-Review" fi exit 1 fi -# Form the issue API URL dynamically -issue_api_url="https://api.github.com/repos/$repo/issues/$issue_num" -echo "API URL: $issue_api_url" +# Initialize a flag to track overall validation status and a string to collect failed links +all_issues_valid=true +failed_issue_links="" -if [[ $repo == "devtron-labs/devtron" || $repo == "devtron-labs/devtron-services" || $repo == "devtron-labs/dashboard" ]]; then - echo "No extra arguments needed: public repository detected." - response=$(curl -s -w "%{http_code}" "$issue_api_url") # Get the response body and status code in one go -else - echo "Adding extra arguments for authentication: private repository detected." - response=$(curl -s -w "%{http_code}" --header "Authorization: Bearer $GH_PR_VALIDATOR_TOKEN" \ - --header "Accept: application/vnd.github+json" "$issue_api_url") -fi +# Loop through each unique issue-repo pair found +for issue_repo_pair in "${all_issues[@]}"; do + # Split the pair into issue_num and repo using comma as delimiter + IFS=',' read -r issue_num repo <<< "$issue_repo_pair" -# Extract HTTP status code from the response -response_code=$(echo "$response" | tail -n 1) # Status code is the last line -response_body=$(echo "$response" | head -n -1) # The body is everything except the last line (status code) + echo "Validating issue number: #$issue_num in repo: $repo" -echo "Response Code: $response_code" -html_url=$(echo "$response_body" | jq -r '.html_url') # Extract html_url from the JSON response + # Form the GitHub API URL for the issue + issue_api_url="https://api.github.com/repos/$repo/issues/$issue_num" + echo "API URL: $issue_api_url" -# Check if the html_url contains "pull-request" -if [[ "$html_url" == *"pull"* ]]; then - echo "The issue URL contains a pull-request link, marking as invalid." - gh pr comment $PRNUM --body "PR is linked to a pull request URL, which is invalid. Please update the issue link." + local response="" + local response_code="" + local response_body="" - if [[ "$forked" == "false" ]]; then - # Apply 'Issue-verification-failed' label and remove 'Ready-to-Review' label. - gh pr edit $PRNUM --add-label "PR:Issue-verification-failed" - gh pr edit $PRNUM --remove-label "PR:Ready-to-Review" + # Determine if the repository is public or private to apply authentication + if [[ "$repo" == "devtron-labs/devtron" || "$repo" == "devtron-labs/devtron-services" || "$repo" == "devtron-labs/dashboard" ]]; then + echo "No extra arguments needed: public repository detected." + # Use curl to get the response body and HTTP status code + response=$(curl -s -w "%{http_code}" "$issue_api_url") + else + echo "Adding extra arguments for authentication: private repository detected." + # Use curl with authentication headers for private repositories + response=$(curl -s -w "%{http_code}" --header "Authorization: Bearer $GH_PR_VALIDATOR_TOKEN" \ + --header "Accept: application/vnd.github+json" "$issue_api_url") fi - exit 1 -fi -# If response_code is 200, proceed with validating the issue -if [[ "$response_code" -eq 200 ]]; then - echo "Issue Number: #$issue_num is valid and exists in Repo: $repo." - if [[ "$forked" == "false" ]]; then - gh pr edit $PRNUM --remove-label "PR:Issue-verification-failed" - gh pr edit $PRNUM --add-label "PR:Ready-to-Review" + # Extract HTTP status code (last line of curl output) and response body + response_code=$(echo "$response" | tail -n 1) + response_body=$(echo "$response" | head -n -1) + + echo "Response Code: $response_code" + # Extract the 'html_url' from the JSON response body using jq + local html_url=$(echo "$response_body" | jq -r '.html_url') + + # Check if the extracted URL points to a pull request instead of an issue + if [[ "$html_url" == *"pull"* ]]; then + echo "The issue URL contains a pull-request link, marking as invalid." + # Append error message to the failed_issue_links string + failed_issue_links+="Issue #$issue_num in $repo is linked to a pull request URL, which is invalid.\n" + all_issues_valid=false # Set overall status to invalid + elif [[ "$response_code" -eq 200 ]]; then + # If HTTP status code is 200, the issue is valid + echo "Issue Number: #$issue_num is valid and exists in Repo: $repo." + else + # If issue not found or invalid HTTP status code + echo "Issue not found. Invalid URL or issue number: #$issue_num in $repo." + failed_issue_links+="Issue #$issue_num in $repo is not found or invalid (HTTP $response_code).\n" + all_issues_valid=false # Set overall status to invalid fi - echo "PR:Ready-to-Review, exiting gracefully" - exit 0 -else - echo "Issue not found. Invalid URL or issue number." - gh pr comment $PRNUM --body "PR is not linked to a valid issue. Please update the issue link." +done - if [[ "$forked" == "false" ]]; then - # Apply 'Issue-verification-failed' label and remove 'Ready-to-Review' label. +# Final label application and comments based on the overall validation status +if [[ "$forked" == "false" ]]; then + # If not a forked PR, modify labels and add comments + if [[ "$all_issues_valid" == "true" ]]; then + # All issues are valid, remove 'Issue-verification-failed' and add 'Ready-to-Review' + gh pr edit $PRNUM --remove-label "PR:Issue-verification-failed" + gh pr edit $PRNUM --add-label "PR:Ready-to-Review" + echo "All linked issues are valid. PR:Ready-to-Review." + exit 0 + else + # Some issues are invalid, add a comment with details and update labels + gh pr comment $PRNUM --body "Some linked issues are invalid. Please update the issue links:\n$failed_issue_links" gh pr edit $PRNUM --add-label "PR:Issue-verification-failed" gh pr edit $PRNUM --remove-label "PR:Ready-to-Review" + echo "Some linked issues are invalid. PR:Issue-verification-failed." + exit 1 fi - exit 1 -fi +else + # For forked PRs, just output the status, do not modify labels + if [[ "$all_issues_valid" == "true" ]]; then + echo "All linked issues are valid for forked PR." + exit 0 + else + echo "Some linked issues are invalid for forked PR:\n$failed_issue_links" + exit 1 + fi +fi \ No newline at end of file From a78d8f3bc28553b9e5c9ec1611c3a9100cd092de Mon Sep 17 00:00:00 2001 From: SATYAsasini Date: Mon, 2 Jun 2025 17:47:29 +0530 Subject: [PATCH 2/3] fix: imporper pattern matching --- .github/workflows/validateIssue.sh | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/validateIssue.sh b/.github/workflows/validateIssue.sh index c1ab445..14d7ea4 100644 --- a/.github/workflows/validateIssue.sh +++ b/.github/workflows/validateIssue.sh @@ -39,7 +39,8 @@ patterns=( # Function to extract all unique issue numbers and their corresponding repositories from PR_BODY # It iterates through defined patterns and extracts all matches. -# Returns a list of "issue_num,repo" pairs, one per line. +# Returns a list of "issue_num,repo" pairs, one per line, to standard output. +# All diagnostic messages are redirected to stderr to prevent interference with readarray. extract_all_issues() { local -a found_issues=() local default_repo="devtron-labs/devtron" # Default repository if not explicitly mentioned in the link @@ -51,7 +52,7 @@ extract_all_issues() { # If matches are found for the current pattern if [[ -n "$matches" ]]; then - echo "Matched for pattern: $pattern" + echo "Matched for pattern: $pattern" >&2 # Redirect diagnostic output to stderr # Read each match into the 'match' variable while IFS= read -r match; do # Extract the issue number (sequence of digits) from the matched string @@ -67,12 +68,12 @@ extract_all_issues() { # If a valid issue number was extracted, add it to the list if [[ -n "$current_issue_num" ]]; then found_issues+=("$current_issue_num,$current_repo") - echo "Extracted issue: $current_issue_num from repo: $current_repo" + echo "Extracted issue: $current_issue_num from repo: $current_repo" >&2 # Redirect diagnostic output to stderr fi done <<< "$matches" # Use a here-string to feed matches into the while loop fi done - # Print unique issue-repo pairs, sorted, to avoid duplicate validations + # Print unique issue-repo pairs, sorted, to standard output for readarray printf "%s\n" "${found_issues[@]}" | sort -u } @@ -97,6 +98,7 @@ failed_issue_links="" # Loop through each unique issue-repo pair found for issue_repo_pair in "${all_issues[@]}"; do # Split the pair into issue_num and repo using comma as delimiter + # Removed 'local' keyword as these variables are not within a function scope IFS=',' read -r issue_num repo <<< "$issue_repo_pair" echo "Validating issue number: #$issue_num in repo: $repo" @@ -105,9 +107,10 @@ for issue_repo_pair in "${all_issues[@]}"; do issue_api_url="https://api.github.com/repos/$repo/issues/$issue_num" echo "API URL: $issue_api_url" - local response="" - local response_code="" - local response_body="" + # Removed 'local' keyword for these variable declarations + response="" + response_code="" + response_body="" # Determine if the repository is public or private to apply authentication if [[ "$repo" == "devtron-labs/devtron" || "$repo" == "devtron-labs/devtron-services" || "$repo" == "devtron-labs/dashboard" ]]; then @@ -127,7 +130,8 @@ for issue_repo_pair in "${all_issues[@]}"; do echo "Response Code: $response_code" # Extract the 'html_url' from the JSON response body using jq - local html_url=$(echo "$response_body" | jq -r '.html_url') + # Removed 'local' keyword for this variable declaration + html_url=$(echo "$response_body" | jq -r '.html_url') # Check if the extracted URL points to a pull request instead of an issue if [[ "$html_url" == *"pull"* ]]; then From 99b7851ed44fa1282ad9cb89085527f4543e9e0e Mon Sep 17 00:00:00 2001 From: SATYAsasini Date: Mon, 2 Jun 2025 17:53:52 +0530 Subject: [PATCH 3/3] fix: imporper pattern matching --- .github/workflows/validateIssue.sh | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/validateIssue.sh b/.github/workflows/validateIssue.sh index 14d7ea4..e0a8065 100644 --- a/.github/workflows/validateIssue.sh +++ b/.github/workflows/validateIssue.sh @@ -42,8 +42,10 @@ patterns=( # Returns a list of "issue_num,repo" pairs, one per line, to standard output. # All diagnostic messages are redirected to stderr to prevent interference with readarray. extract_all_issues() { - local -a found_issues=() - local default_repo="devtron-labs/devtron" # Default repository if not explicitly mentioned in the link + # Removed 'local' keyword for array declaration + found_issues=() + # Removed 'local' keyword for variable declaration + default_repo="devtron-labs/devtron" # Default repository if not explicitly mentioned in the link # Loop through each defined pattern for pattern in "${patterns[@]}"; do @@ -52,13 +54,16 @@ extract_all_issues() { # If matches are found for the current pattern if [[ -n "$matches" ]]; then - echo "Matched for pattern: $pattern" >&2 # Redirect diagnostic output to stderr + # IMPORTANT: Redirect all diagnostic echo statements to stderr (>&2) + echo "Matched for pattern: $pattern" >&2 # Read each match into the 'match' variable while IFS= read -r match; do # Extract the issue number (sequence of digits) from the matched string - local current_issue_num=$(echo "$match" | grep -oE "[0-9]+") + # Removed 'local' keyword for variable declaration + current_issue_num=$(echo "$match" | grep -oE "[0-9]+") # Extract the repository name (e.g., devtron-labs/devtron) from the matched string - local current_repo=$(echo "$match" | grep -oE "devtron-labs/[a-zA-Z0-9_-]+") + # Removed 'local' keyword for variable declaration + current_repo=$(echo "$match" | grep -oE "devtron-labs/[a-zA-Z0-9_-]+") # If no specific repository is found in the link, use the default if [[ -z "$current_repo" ]]; then @@ -68,7 +73,8 @@ extract_all_issues() { # If a valid issue number was extracted, add it to the list if [[ -n "$current_issue_num" ]]; then found_issues+=("$current_issue_num,$current_repo") - echo "Extracted issue: $current_issue_num from repo: $current_repo" >&2 # Redirect diagnostic output to stderr + # IMPORTANT: Redirect all diagnostic echo statements to stderr (>&2) + echo "Extracted issue: $current_issue_num from repo: $current_repo" >&2 fi done <<< "$matches" # Use a here-string to feed matches into the while loop fi @@ -98,7 +104,7 @@ failed_issue_links="" # Loop through each unique issue-repo pair found for issue_repo_pair in "${all_issues[@]}"; do # Split the pair into issue_num and repo using comma as delimiter - # Removed 'local' keyword as these variables are not within a function scope + # Variables are now global, no 'local' needed here IFS=',' read -r issue_num repo <<< "$issue_repo_pair" echo "Validating issue number: #$issue_num in repo: $repo" @@ -107,7 +113,7 @@ for issue_repo_pair in "${all_issues[@]}"; do issue_api_url="https://api.github.com/repos/$repo/issues/$issue_num" echo "API URL: $issue_api_url" - # Removed 'local' keyword for these variable declarations + # Variables are now global, no 'local' needed here response="" response_code="" response_body="" @@ -130,7 +136,7 @@ for issue_repo_pair in "${all_issues[@]}"; do echo "Response Code: $response_code" # Extract the 'html_url' from the JSON response body using jq - # Removed 'local' keyword for this variable declaration + # Variable is now global, no 'local' needed here html_url=$(echo "$response_body" | jq -r '.html_url') # Check if the extracted URL points to a pull request instead of an issue @@ -176,4 +182,4 @@ else echo "Some linked issues are invalid for forked PR:\n$failed_issue_links" exit 1 fi -fi \ No newline at end of file +fi