Skip to content

Commit

Permalink
Merge pull request #2 from synergy-au/feature/sarif-report
Browse files Browse the repository at this point in the history
Produces SARIF reports to upload to GitHub
  • Loading branch information
Clint-Chester authored May 30, 2021
2 parents 2e7ddf0 + 1b2b6c6 commit 8d35772
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 37 deletions.
53 changes: 47 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# PMD Analyser - GitHub Action

GitHub Action to run [PMD Analyser](https://pmd.github.io/) based on the ruleset defined.
GitHub Action to run [PMD Analyser](https://pmd.github.io/) based on the ruleset defined. This action generates a SARIF report which can be uploaded to GitHub.

By default this will generate warning notifications for any rule violations specified in the ruleset on a pull request or a push, but the check won't fail. If you wish for some rule violations to cause error notifications and for the check to fail, you can specify the rule names in a comma separated input in the workflow file.
Features of this action include:

- Set the severity level you want rules reported at. Levels include error, warning and note (default level is warning).
- Run PMD Analyser on the files changed. File comparison can be done either based on a git diff or based on the files changed specified on the GitHub pull request.

## Example GitHub Action Workflow File
```
Expand All @@ -12,22 +15,36 @@ on:
push:
jobs:
build:
pmd-analyser-check:
name: PMD Static Code Analysis
permissions:
security-events: write
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
with:
# Incremental diffs require fetch depth to be at 0 to grab the target branch
fetch-depth: '0'
- name: Run PMD Analyser
uses: synergy-au/pmd-analyser-action@v1
id: pmd-analysis
uses: synergy-au/pmd-analyser-action@v2
with:
pmd-version: '6.33.0'
pmd-version: '6.34.0'
file-path: './src'
rules-path: './pmd-ruleset.xml'
error-rules: 'AvoidDirectAccessTriggerMap,AvoidDmlStatementsInLoops,AvoidHardcodingId'
note-rules: 'ApexDoc'
- name: Upload results to GitHub
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: pmd-output.sarif
- name: No PMD Errors?
run: |
if ${{ steps.pmd-analysis.outputs.error-found }}
then
exit 3
fi
```

## Inputs
Expand All @@ -39,18 +56,36 @@ Used to determine whether you just want to analyse the files changed or the whol
- required: false
- default: 'false'

### auth-token:
If you are looking to compare the file difference based on the GitHub pull request, you will need to specify the [GitHub secrets token](https://docs.github.com/en/actions/reference/authentication-in-a-workflow)'

- required: false

### error-rules

If you wish to define rules that log as an error, enter each rule name separated with a comma and no spaces. Note that if an error is identified the run will fail. e.g. ClassNamingConventions,GuardLogStatement

- required: false

### file-diff-type

Choose whether you want the file comparison to be based on a git diff or based on the files changed specified on the GitHub pull request. Note that if you use the GitHub pull request option, this action will only work on a pull request event. Options to set this are either `git` or `github`.

- required: false
- default: 'git'

### file-path

Path to the sources to analyse. This can be a file name, a directory, or a jar or zip file containing the sources.

- required: true

### note-rules

If you wish to define rules that log as a note, enter each rule name separated with a comma and no spaces. Note that if a note is identified the run will not fail. e.g. ClassNamingConventions,GuardLogStatement

- required: false

### pmd-version

The version of PMD you would like to run.
Expand All @@ -63,3 +98,9 @@ The version of PMD you would like to run.
The ruleset file you want to use. PMD uses xml configuration files, called rulesets, which specify which rules to execute on your sources. You can also run a single rule by referencing it using its category and name (more details here). For example, you can check for unnecessary modifiers on Java sources with -R category/java/codestyle.xml/UnnecessaryModifier.

- required: true

## Outputs

### error-found

Identifies whether an error has been found based on the error ruleset. If an error is found 'true' is returned.
27 changes: 24 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,33 @@ inputs:
description: 'Used to determine whether you just want to analyse the files changed or the whole repository.'
required: false
default: 'false'
auth-token:
description: 'If you are looking to compare the file difference based on the GitHub pull request, you will need to specify the [GitHub secrets token](https://docs.github.com/en/actions/reference/authentication-in-a-workflow)'
required: false
error-rules:
description: 'If you wish to define rules that log as an error, enter each rule name separated with a comma and no spaces. Note that if an error is identified the run will fail. e.g. ClassNamingConventions,GuardLogStatement'
required: false
file-diff-type:
description: 'Choose whether you want the file comparison to be based on a git diff or based on the files changed specified on the GitHub pull request. Note that if you use the GitHub pull request option, this action will only work on a pull request event. Options to set this are either `git` or `github`.'
required: false
default: 'git'
file-path:
description: 'Path to the sources to analyse. This can be a file name, a directory, or a jar or zip file containing the sources.'
required: true
note-rules:
description: 'If you wish to define rules that log as a note, enter each rule name separated with a comma and no spaces. Note that if a note is identified the run will not fail. e.g. ClassNamingConventions,GuardLogStatement'
required: false
pmd-version:
description: 'The version of PMD you would like to run.'
required: false
default: '6.33.0'
default: '6.34.0'
rules-path:
description: 'The ruleset file you want to use. PMD uses xml configuration files, called rulesets, which specify which rules to execute on your sources. You can also run a single rule by referencing it using its category and name (more details here). For example, you can check for unnecessary modifiers on Java sources with -R category/java/codestyle.xml/UnnecessaryModifier.'
required: true
outputs:
error-found:
description: 'Identifies whether an error has been found based on the ruleset.'
value: ${{ steps.pmd-analysis.outputs.error-found }}
runs:
using: "composite"
steps:
Expand All @@ -34,7 +48,8 @@ runs:
echo "::set-output name=source::${{ github.ref }}"
fi
shell: bash
- run: ${{ github.action_path }}/pmd-analyser.sh
- id: pmd-analysis
run: ${{ github.action_path }}/pmd-analyser.sh
shell: bash
env:
PMD_VERSION: ${{ inputs.pmd-version }}
Expand All @@ -43,4 +58,10 @@ runs:
ANALYSE_ALL_CODE: ${{ inputs.analyse-all-code }}
TARGET_BRANCH: ${{ steps.branches.outputs.target }}
SOURCE_BRANCH: ${{ steps.branches.outputs.source }}
ERROR_RULES: ${{ inputs.error-rules }}
ERROR_RULES: ${{ inputs.error-rules }}
NOTE_RULES: ${{ inputs.note-rules }}
REPO_NAME: ${{ github.event.repository.full_name }}
PR_NUMBER: ${{ github.event.number }}
AUTH_TOKEN: ${{ inputs.auth-token }}
FILE_DIFF_TYPE: ${{ inputs.file-diff-type }}
WORKSPACE: ${{ github.workspace }}/
51 changes: 23 additions & 28 deletions pmd-analyser.sh
Original file line number Diff line number Diff line change
@@ -1,38 +1,33 @@
# shellcheck shell=sh

ERROR_COUNT=0

# Download PMD
wget https://github.com/pmd/pmd/releases/download/pmd_releases%2F"${PMD_VERSION}"/pmd-bin-"${PMD_VERSION}".zip
unzip pmd-bin-"${PMD_VERSION}".zip
# Now either run the full analysis or files changed based on the settings defined
if [ "$ANALYSE_ALL_CODE" == 'true' ]; then
pmd-bin-"${PMD_VERSION}"/bin/run.sh pmd -d "$FILE_PATH" -R "$RULES_PATH" -failOnViolation false -f json > pmd-output.json
pmd-bin-"${PMD_VERSION}"/bin/run.sh pmd -d "$FILE_PATH" -R "$RULES_PATH" -failOnViolation false -f sarif > pmd-raw-output.sarif
else
# Generate a CSV file first with the files changed
git diff --name-only --diff-filter=d origin/"$TARGET_BRANCH"..origin/"${SOURCE_BRANCH#"refs/heads/"}" | paste -s -d "," >> diff-file.csv
# Now to determine whether to get the files changed from a git diff or using the files changed in a GitHub Pull Request
# Both options will generate a CSV file first with the files changed
if [ "$FILE_DIFF_TYPE" == 'git' ]; then
git diff --name-only --diff-filter=d origin/"$TARGET_BRANCH"..origin/"${SOURCE_BRANCH#"refs/heads/"}" | paste -s -d "," >> diff-file.csv
else
curl -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${AUTH_TOKEN}" https://api.github.com/repos/"$REPO_NAME"/pulls/"$PR_NUMBER"/files | jq --raw-output '.[] .filename' | paste -s -d "," >> diff-file.csv
fi
# Run the analysis
pmd-bin-"${PMD_VERSION}"/bin/run.sh pmd -filelist diff-file.csv -R "$RULES_PATH" -failOnViolation false -f json > pmd-output.json
pmd-bin-"${PMD_VERSION}"/bin/run.sh pmd -filelist diff-file.csv -R "$RULES_PATH" -failOnViolation false -f sarif > pmd-raw-output.sarif
fi
# Loop through each file and then loop through each violation identified
while read -r file; do
FILENAME="$(echo "$file" | jq --raw-output '.filename | ltrimstr("${{ github.workspace }}/")')"
while read -r violation; do
MESSAGE="$(echo "$violation" | jq --raw-output '" \(.ruleset) - \(.rule): \(.description). This applies from line \(.beginline) to \(.endline) and from column \(.begincolumn) to \(.endcolumn). For more information on this rule visit \(.externalInfoUrl)"')"
LINE="$(echo "$violation" | jq --raw-output '.beginline')"
COLUMN="$(echo "$violation" | jq --raw-output '.begincolumn')"
RULE="$(echo "$violation" | jq --raw-output '.rule')"
if [ -n "$RULE" ]; then
if [[ "$ERROR_RULES" == *"$RULE"* ]]; then
echo ::error file="$FILENAME",line="$LINE",col="$COLUMN"::"$MESSAGE"
ERROR_COUNT=$((ERROR_COUNT + 1))
else
echo ::warning file="$FILENAME",line="$LINE",col="$COLUMN"::"$MESSAGE"
fi
fi
done <<< "$(echo "$file" | jq --compact-output '.violations[]')"
done <<< "$(cat pmd-output.json | jq --compact-output '.files[]')"
# If there are any errors logged we want this to fail (warnings don't count)
if [ "$ERROR_COUNT" -gt 0 ]; then
exit 3
fi
# Loop through each rule and see if an error should be thrown
echo "::set-output name=error-found::false"
while read -r rule; do
RULE="$(echo "$rule" | jq --raw-output '.id')"
if [[ $RULE && "$ERROR_RULES" == *"$RULE"* ]]; then
echo "::set-output name=error-found::true"
break
fi
done <<< "$(cat pmd-raw-output.sarif | jq --compact-output '.runs[] .tool .driver .rules[]')"
# Set the correct file location for the report
cat pmd-raw-output.sarif | jq --arg workspace "$WORKSPACE" '(.runs[] .results[] .locations[] .physicalLocation .artifactLocation .uri) |= ltrimstr($workspace)' > pmd-file-locations-output.sarif
# Set the rule level configurations for whether they are notes or errors
cat pmd-file-locations-output.sarif | jq --arg errors "$ERROR_RULES" '((.runs[] .tool .driver .rules[]) | select(.id==($errors | split(",")[]))) += {"defaultConfiguration": {"level": "error"}}' > pmd-errors-output.sarif
cat pmd-errors-output.sarif | jq --arg notes "$NOTE_RULES" '((.runs[] .tool .driver .rules[]) | select(.id==($notes | split(",")[]))) += {"defaultConfiguration": {"level": "note"}}' > pmd-output.sarif

0 comments on commit 8d35772

Please sign in to comment.