Subject: [PATCH v2 0/6] Add a GitHub workflow to submit builds to Coverity Scan

From: "Johannes Schindelin via GitGitGadget" <[email-masked]>
Date: Mon, 25 Sep 2023 11:50:56 +0000

Coverity [https://scan.coverity.com/] is a powerful static analysis tool
that helps prevent vulnerabilities. It is free to use by open source
projects, and Git benefits from this, as well as Git for Windows. As is the
case with many powerful tools, using Coverity comes with its own set of
challenges, one of which being that submitting a build is quite laborious.

The help with this, the Git for Windows project created an Azure Pipeline to
automate submitting builds to Coverity Scan
[https://dev.azure.com/git-for-windows/git/_build/index?definitionId=35]
which is ported to a GitHub workflow in this here patch series, keeping an
eye specifically on allowing both the Git and the Git for Windows project to
use this workflow.

Since Coverity build submissions require authentication, this workflow is
skipped by default. To enable it, the repository variable
[https://docs.github.com/en/actions/learn-github-actions/variables]
ENABLE_COVERITY_SCAN_FOR_BRANCHES needs to be added. Its value needs to be a
JSON string array containing the branch names, e.g. ["master", "next"].
Further, two repository secrets
[https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions]
need to be set: COVERITY_SCAN_EMAIL and COVERITY_SCAN_TOKEN.

An example run in the Git for Windows project can be admired here
[https://github.com/git-for-windows/git/actions/runs/6298399881].

Note: While inspired by vapier/coverity-scan-action
[https://github.com/vapier/coverity-scan-action], I found too many
limitations in that Action to be used here. However, I would be willing to
fork that Action into the git organization on GitHub, improve the code to
accommodate Git's needs, and maintain that Action, if there is enough
support for taking that route instead of simply hard-coding the steps in
Git's .github/workflows/coverity.yml.

This patch series is based on v2.42.0, but would apply literally everywhere
because it adds a new file and modifies no existing one.

Changes since v1:

 * After verifying that cURL's --fail option does what we need in Coverity's
   context, I switched to using that in every curl invocation.
 * Adjusted quoting (the ${{ ... }} constructs do not require double quotes
   because they are interpolated before the script is run).
 * Touched up a few commit messages, based on the feedback I received.
 * Addressed an actionlint [https://rhysd.github.io/actionlint/] warning.

Johannes Schindelin (6):
  ci: add a GitHub workflow to submit Coverity scans
  coverity: cache the Coverity Build Tool
  coverity: allow overriding the Coverity project
  coverity: support building on Windows
  coverity: allow running on macOS
  coverity: detect and report when the token or project is incorrect

 .github/workflows/coverity.yml | 163 +++++++++++++++++++++++++++++++++
 1 file changed, 163 insertions(+)
 create mode 100644 .github/workflows/coverity.yml

base-commit: 43c8a30d150ecede9709c1f2527c8fba92c65f40
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1588%2Fdscho%2Fcoverity-workflow-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1588/dscho/coverity-workflow-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1588

Range-diff vs v1:

 1:  8cb92968c5e ! 1:  46fb6b583d3 ci: add a GitHub workflow to submit Coverity scans
     @@ .github/workflows/coverity.yml (new)
      +      - name: download the Coverity Build Tool (${{ env.COVERITY_LANGUAGE }} / ${{ env.COVERITY_PLATFORM}})
      +        run: |
      +          curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \
     -+            --no-progress-meter \
     ++            --fail --no-progress-meter \
      +            --output $RUNNER_TEMP/cov-analysis.tgz \
     -+            --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=$COVERITY_PROJECT"
     ++            --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \
     ++            --form project="$COVERITY_PROJECT"
      +      - name: extract the Coverity Build Tool
      +        run: |
      +          mkdir $RUNNER_TEMP/cov-analysis &&
     @@ .github/workflows/coverity.yml (new)
      +      - name: submit the build to Coverity Scan
      +        run: |
      +          curl \
     -+            --form token="${{ secrets.COVERITY_SCAN_TOKEN }}" \
     -+            --form email="${{ secrets.COVERITY_SCAN_EMAIL }}" \
     ++            --fail \
     ++            --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \
     ++            --form email='${{ secrets.COVERITY_SCAN_EMAIL }}' \
      +            --form file=@cov-int.tgz \
     -+            --form version="${{ github.sha }}" \
     ++            --form version='${{ github.sha }}' \
      +            "https://scan.coverity.com/builds?project=$COVERITY_PROJECT"
 2:  8420a76eba3 ! 2:  e26545b1ed9 coverity: cache the Coverity Build Tool
     @@ .github/workflows/coverity.yml: jobs:
      +        id: lookup
      +        run: |
      +          MD5=$(curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \
     -+                   --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=$COVERITY_PROJECT&md5=1")
     ++                   --fail \
     ++                   --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \
     ++                   --form project="$COVERITY_PROJECT" \
     ++                   --form md5=1) &&
      +          echo "hash=$MD5" >>$GITHUB_OUTPUT
      +
      +      # Try to cache the tool to avoid downloading 1GB+ on every run.
     @@ .github/workflows/coverity.yml: jobs:
      +        if: steps.cache.outputs.cache-hit != 'true'
               run: |
                 curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \
     -             --no-progress-meter \
     -             --output $RUNNER_TEMP/cov-analysis.tgz \
     -             --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=$COVERITY_PROJECT"
     +             --fail --no-progress-meter \
     +@@ .github/workflows/coverity.yml: jobs:
     +             --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \
     +             --form project="$COVERITY_PROJECT"
             - name: extract the Coverity Build Tool
      +        if: steps.cache.outputs.cache-hit != 'true'
               run: |
 3:  6c1c8286281 = 3:  ea85e351233 coverity: allow overriding the Coverity project
 4:  14cdefff082 ! 4:  84e1c3eede8 coverity: support building on Windows
     @@ Commit message
          value, say, `["windows-latest"]`, this GitHub workflow now runs on
          Windows, allowing to analyze Windows-specific issues.
      
     +    This allows, say, the Git for Windows fork to submit Windows builds to
     +    Coverity Scan instead of Linux builds.
     +
          Signed-off-by: Johannes Schindelin <[email-masked]>
      
       ## .github/workflows/coverity.yml ##
     @@ .github/workflows/coverity.yml: name: Coverity
             COVERITY_PROJECT: ${{ vars.COVERITY_PROJECT || 'git' }}
             COVERITY_LANGUAGE: cxx
      -      COVERITY_PLATFORM: linux64
     ++      COVERITY_PLATFORM: overridden-below
           steps:
             - uses: actions/checkout@v3
      +      - name: install minimal Git for Windows SDK
     @@ .github/workflows/coverity.yml: name: Coverity
      +          echo "COVERITY_PLATFORM=$COVERITY_PLATFORM" >>$GITHUB_ENV
      +          echo "COVERITY_TOOL_FILENAME=$COVERITY_TOOL_FILENAME" >>$GITHUB_ENV
                 MD5=$(curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \
     -                    --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=$COVERITY_PROJECT&md5=1")
     -           echo "hash=$MD5" >>$GITHUB_OUTPUT
     +                    --fail \
     +                    --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \
      @@ .github/workflows/coverity.yml: jobs:
               run: |
                 curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \
     -             --no-progress-meter \
     +             --fail --no-progress-meter \
      -            --output $RUNNER_TEMP/cov-analysis.tgz \
      +            --output $RUNNER_TEMP/$COVERITY_TOOL_FILENAME \
     -             --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=$COVERITY_PROJECT"
     +             --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \
     +             --form project="$COVERITY_PROJECT"
             - name: extract the Coverity Build Tool
               if: steps.cache.outputs.cache-hit != 'true'
               run: |
 5:  782cf2b4403 ! 5:  3d24b6f3b22 coverity: allow running on macOS
     @@ .github/workflows/coverity.yml: jobs:
                 echo "COVERITY_TOOL_FILENAME=$COVERITY_TOOL_FILENAME" >>$GITHUB_ENV
      +          echo "MAKEFLAGS=$MAKEFLAGS" >>$GITHUB_ENV
                 MD5=$(curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \
     -                    --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=$COVERITY_PROJECT&md5=1")
     -           echo "hash=$MD5" >>$GITHUB_OUTPUT
     +                    --fail \
     +                    --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \
      @@ .github/workflows/coverity.yml: jobs:
                   mkdir $RUNNER_TEMP/cov-analysis &&
                   tar -xzf $RUNNER_TEMP/$COVERITY_TOOL_FILENAME --strip 1 -C $RUNNER_TEMP/cov-analysis
 6:  458bc2ea91f ! 6:  b45cc4b8a25 coverity: detect and report when the token or project is incorrect
     @@ Commit message
          either an incorrect token, or even more likely due to an incorrect
          Coverity project name.
      
     -    Let's detect that scenario and provide a helpful error message instead
     -    of trying to go forward with an empty string instead of the correct MD5.
     +    Seeing an authorization failure that is caused by an incorrect project
     +    name was somewhat surprising to me when developing the Coverity
     +    workflow, as I found such a failure suggestive of an incorrect token
     +    instead.
     +
     +    So let's provide a helpful error message about that specifically when
     +    encountering authentication issues.
      
          Signed-off-by: Johannes Schindelin <[email-masked]>
      
       ## .github/workflows/coverity.yml ##
      @@ .github/workflows/coverity.yml: jobs:
     -           echo "COVERITY_TOOL_FILENAME=$COVERITY_TOOL_FILENAME" >>$GITHUB_ENV
     -           echo "MAKEFLAGS=$MAKEFLAGS" >>$GITHUB_ENV
     -           MD5=$(curl https://scan.coverity.com/download/$COVERITY_LANGUAGE/$COVERITY_PLATFORM \
     -+                   -D "$RUNNER_TEMP"/headers.txt \
     -                    --data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=$COVERITY_PROJECT&md5=1")
     -+          http_code="$(sed -n 1p <"$RUNNER_TEMP"/headers.txt)"
     -+          case "$http_code" in
     -+          *200*) ;; # okay
     -+          *401*) # access denied
     -+            echo "::error::incorrect token or project? ($http_code)" >&2
     +                    --fail \
     +                    --form token='${{ secrets.COVERITY_SCAN_TOKEN }}' \
     +                    --form project="$COVERITY_PROJECT" \
     +-                   --form md5=1) &&
     ++                   --form md5=1)
     ++          case $? in
     ++          0) ;; # okay
     ++          *22*) # 40x, i.e. access denied
     ++            echo "::error::incorrect token or project?" >&2
      +            exit 1
      +            ;;
      +          *) # other error
     -+            echo "::error::HTTP error $http_code" >&2
     ++            echo "::error::Failed to retrieve MD5" >&2
      +            exit 1
      +            ;;
      +          esac

-- 
gitgitgadget