GitHub Action, Bash Script, and PR Comment - Oh My!

The Use Case

Recently I was tasked with parsing some XML, returning non-conforming URL rewrite rules, and alerting on them within a GitHub Action. You see, in a project I was working on we had a URL rule that was a partial match for another route, and it caused some data issues in an API call. This was an edge case - but we wanted to strengthen our URL rewrite rules .. so we added ^ and $ to the existing rules, and the follow up task was to create something automated to make sure if we write anything loose .. that it gets reported on. Having never written my own GitHub Action, I dove down the rabbit hole of examples, documentation, and seeing what path I could take.

After digging through JavaScript, Docker, and composite runs .. I went with the simplest/easiest possible method: A bash script that returns results, formats them, and then outputs them to a PR comment. Why not fail the PR? Well - in some cases, you may have a legitimate reason for bypassing the warning.

The Bash Script

First step, creating the bash script, script.sh. This will go into .github/workflows in your GitHub repository.

#!/bin/bash
# Detect issues in URL Rewrite Rules 
output=""

for file in $(find /home/runner/work/project/project -name 'Web.config')
do
        lineNumber=1
        for line in $(xmlstarlet sel -T -t -m /configuration/system.webServer/rewrite/rules/rule/match/@url -v . -n "$file")
        do
                if [[ $line != \^* ]] || [[ $line != *\$ ]]
                then
                        output+="Line Item: <br /><br /> $line <br /><br /> in $file on line $lineNumber;"
                fi

                ((lineNumber+=1))
        done
done

echo "::set-output name=ERROR_LINES::${output::-1}"

So what's going on here?

for file in $(find /home/runner/work/project/project -name 'Web.config')

Loop through ANY Web.config in our repository. This can be customized to target a specific file. We have multiple configs that could have rules to check.

 for line in $(xmlstarlet sel -T -t -m /configuration/system.webServer/rewrite/rules/rule/match/@url -v . -n "$file")

xmlstarlet allows us to target specific XML nodes and get the value.

 if [[ $line != \^* ]] || [[ $line != *\$ ]]
                then
                        output+="Line Item: <br /><br /> $line <br /><br /> in $file on line $lineNumber;"
                fi

We're checking to make sure every single line starts with ^ and ends with $. If either of these are false, we add the line and line number to the output.

And finally ...

echo "::set-output name=ERROR_LINES::${output::-1}"

Set a variable, ERROR_LINES, for consumption outside of the subshell.

The GitHub Action File

The next file is validate_rewrite_rules.yml, which we will put in the same directory as the script.sh file.

name: URLRewriteValidator

on:
  pull_request:
    branches:
      - main
      - master
      - release
      - develop

jobs:
  url_rule_checker:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@01aecccf739ca6ff86c0539fbc67a7a5007bbc81

      - name: Install xmlstarlet
        run: sudo apt-get install xmlstarlet

      - name: Query against Web.config
        id: url_rule_checker
        run: |
          # Grant perms
          chmod +x "${GITHUB_WORKSPACE}/.github/workflows/script.sh"

          # Run Script
          "${GITHUB_WORKSPACE}/.github/workflows/script.sh"

      - name: Get Errors
        run: echo "Errors in \n ${{ steps.url_rule_checker.outputs.ERROR_LINES }}"

      - name: Format comment for PR
        id: format_comment
        if: steps.url_rule_checker.outputs.ERROR_LINES
        run: |
          COMMENT_BODY=`echo "${{ steps.url_rule_checker.outputs.ERROR_LINES }}" | sed 's/*/\\\*/g;s/;/<\/li><li>/g'`
          COMMENT_BODY="There are URL rewrite URLs that dont start/end with ^ and $.  Verify that the URL rules are what you expect.<br /><ul><li>${COMMENT_BODY}</li></ul>"
          echo "::set-output name=formatted_comment::$COMMENT_BODY"
          echo "$COMMENT_BODY" | sed 's/*/\\\*/g;s/;/<\/li><li>/g'

      - uses: mshick/add-pr-comment@5cd99bf9c186219af43341076f1fe9c09e5a9934 # v1
        if: steps.url_rule_checker.outputs.ERROR_LINES
        with:
          message: ${{ steps.format_comment.outputs.formatted_comment }}
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          repo-token-user-login: 'github-actions[bot]'
          allow-repeats: false

So let's break it down..

on:
  pull_request:
    branches:
      - main

We want to fire this action off on Pull Request.

      - uses: actions/checkout@01aecccf739ca6ff86c0539fbc67a7a5007bbc81

To quote from the Action GitHub repo:

"This action checks-out your repository under $GITHUB_WORKSPACE , so your workflow can access it. Only a single commit is fetched by default, for the ref/SHA that triggered the workflow."

- name: Install xmlstarlet
        run: sudo apt-get install xmlstarlet

This installs xmlstarlet within the shell.

- name: Query against Web.config
        id: url_rule_checker
        run: |
          # Grant perms
          chmod +x "${GITHUB_WORKSPACE}/.github/workflows/script.sh"

          # Run Script
          "${GITHUB_WORKSPACE}/.github/workflows/script.sh"

Here we execute our script that we created.

- name: Get Errors
        run: echo "Errors in \n ${{ steps.url_rule_checker.outputs.ERROR_LINES }}"

After the script runs, we now display/confirm our ERROR_LINES variable is set.

      - name: Format comment for PR
        id: format_comment
        if: steps.url_rule_checker.outputs.ERROR_LINES
        run: |
          COMMENT_BODY=`echo "${{ steps.url_rule_checker.outputs.ERROR_LINES }}" | sed 's/*/\\\*/g;s/;/<\/li><li>/g'`
          COMMENT_BODY="There are URL rewrite URLs that dont start/end with ^ and $.  Verify that the URL rules are what you expect.<br /><ul><li>${COMMENT_BODY}</li></ul>"
          echo "::set-output name=formatted_comment::$COMMENT_BODY"
          echo "$COMMENT_BODY" | sed 's/*/\\\*/g;s/;/<\/li><li>/g'

If we have ERROR_LINES, we want to parse/set the body of our comment. We are going to split on the semi-colon we insert in the script output, and insert it into List Item html tags.

      - uses: mshick/add-pr-comment@5cd99bf9c186219af43341076f1fe9c09e5a9934 # v1
        if: steps.url_rule_checker.outputs.ERROR_LINES
        with:
          message: ${{ steps.format_comment.outputs.formatted_comment }}
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          repo-token-user-login: 'github-actions[bot]'
          allow-repeats: false

And finally, we use a GitHub action called Add-Pr-Comment by mshick. We set the message to our formatted comment, pull the GITHUB_TOKEN from the secrets that are available via the GitHub Actions, and set the login.

The Results

image.png

Looking for a working example?

Repository: github.com/MattTheDev/UrlRewriteRuleCheckin..

Pull Request with Comments: github.com/MattTheDev/UrlRewriteRuleCheckin..

No Comments Yet