From 1b2b6c60bb7ec52ffc199cb2b38306aae485b9d6 Mon Sep 17 00:00:00 2001 From: Clint Chester Date: Fri, 28 May 2021 15:55:48 +0000 Subject: [PATCH] Produces sarif reports to upload to GitHub --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++------ action.yml | 27 ++++++++++++++++++++++--- pmd-analyser.sh | 51 +++++++++++++++++++++-------------------------- 3 files changed, 94 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 03da4c8..4c13072 100644 --- a/README.md +++ b/README.md @@ -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 ``` @@ -12,9 +15,11 @@ 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 @@ -22,12 +27,24 @@ jobs: # 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 @@ -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. @@ -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. diff --git a/action.yml b/action.yml index 0ff1a0e..09d78c2 100644 --- a/action.yml +++ b/action.yml @@ -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: @@ -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 }} @@ -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 }} \ No newline at end of file + 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 }}/ \ No newline at end of file diff --git a/pmd-analyser.sh b/pmd-analyser.sh index f49c0bb..87175ef 100755 --- a/pmd-analyser.sh +++ b/pmd-analyser.sh @@ -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 \ No newline at end of file +# 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 \ No newline at end of file