From 3c9baf21be458fb4e865094fc8b38689ce0922df Mon Sep 17 00:00:00 2001 From: Whisperity Date: Mon, 29 Nov 2021 16:22:28 +0100 Subject: [PATCH] feat: Expose and wire in the `store` command --- .github/workflows/test.yml | 72 +++++++++++++++++++++++++++++++++++ README.md | 38 ++++++++++++------ action.yml | 55 +++++++++++++++++++++++++- src/store-pre.sh | 45 ++++++++++++++++++++++ src/store.sh | 42 ++++++++++++++++++++ test/codechecker.server.json | 13 +++++++ test/prepare-docker-server.sh | 33 ++++++++++++++++ 7 files changed, 285 insertions(+), 13 deletions(-) create mode 100755 src/store-pre.sh create mode 100755 src/store.sh create mode 100644 test/codechecker.server.json create mode 100755 test/prepare-docker-server.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26c2051..9cf7c7f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,3 +121,75 @@ jobs: with: name: "Parse HTML test results" path: ${{ steps.codechecker.outputs.result-html-dir }} + + store: + name: "Store: Authenticated local store of single result" + runs-on: ubuntu-20.04 + env: + CODECHECKER_VERSION: '6.18.0' + steps: + - uses: actions/checkout@v2 + # Need to do this manually because the server for this test has to have + # authentication on, with a known username and password. + - name: "Set up CodeChecker server" + run: | + set -x + + sudo apt-get -y update + sudo apt-get -y install --no-install-recommends \ + netcat \ + wget + + mkdir -pv ~/codechecker-server-data + chmod 1777 ~/codechecker-server-data + + cp test/codechecker.server.json ~/codechecker-server-data/server_config.json + test/prepare-docker-server.sh + + echo "::group::CodeChecker server configuration" + cd ~/codechecker-server-data + chown 950:950 server_config.json && chmod 0600 server_config.json + chown 950:950 root.user && chmod 0600 root.user + ls -alh + cat docker-compose.yml + cat root.user + cat server_config.json + echo "::endgroup::" + + docker-compose up -d + + wget -qO- http://raw.githubusercontent.com/eficode/wait-for/v2.1.3/wait-for | sh -s -- --timeout=30 http://0.0.0.0:8001/ -- echo "CodeChecker up" + + docker ps -a + echo "::group::CodeChecker server initial log output" + docker logs codechecker-server + echo "::endgroup::" + - run: test/fix_compile_json_paths.sh + - uses: ./ + id: codechecker + continue-on-error: true + with: + version: "v${{ env.CODECHECKER_VERSION }}" + logfile: 'test/simple/compile_commands.json' + store: true + store-url: 'http://0.0.0.0:8001/Default' + store-username: 'root' + store-password: 'root' + - name: "Test if server logged store action" + id: test + continue-on-error: true + run: docker logs codechecker-server | grep "stored results" + - name: "Teardown CodeChecker server" + run: | + set -x + + echo "::group::CodeChecker server log output" + docker logs codechecker-server + echo "::endgroup::" + + cd ~/codechecker-server-data + docker-compose down + docker ps -a + - name: "Fail the build if the test execution failed" + if: ${{ steps.test.outcome == 'failure' || steps.codechecker.outcome == 'failure' }} + run: exit 1 diff --git a/README.md b/README.md index 628b86d..c0a7092 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# [CodeChecker](http://github.com/Ericsson/CodeChecker/) C++ static analysis action +# [CodeChecker](http://github.com/Ericsson/CodeChecker/) C++ Static Analysis action GitHub Action to execute static analysis over C-family projects (C, C++, Objective-C) using the [Clang](http://clang.llvm.org/) infrastructure and @@ -26,7 +26,7 @@ Hence, it's recommended to configure your project in a **`Debug`** configuration Add the job into your CI as follows. The two versions are mutually exclusive — you either can give a compilation database, or you instruct CodeChecker to create one. -### Project can generate a [JSON Compilation Database](http://clang.llvm.org/docs/JSONCompilationDatabase.html) and build cleanly (no generated code) +### Projects that can generate a [JSON Compilation Database](http://clang.llvm.org/docs/JSONCompilationDatabase.html) and build cleanly (no generated code) Some projects are trivial enough in their build configuration that no additional steps need to be taken after executing `configure.sh`, `cmake`, or similar tools. If you are able to generate a _compilation database_ from your build system **without** running the build itself, you can save some time, and go to the analysis immediately. @@ -137,19 +137,33 @@ runs: â„šī¸ **Note:** Due to static analysis being potentially noisy and the reports being unwieldy to fix, the default behaviour is to only report the findings but do not break the CI. -| Variable | Default | Description | -|-------------------------|---------|-----------------------------------------------------------------------------------| -| `fail-build-if-reports` | `false` | If set to `true`, the build will be set to broken if the static analysers report. | +| Variable | Default | Description | +|-------------------------|---------|---------------------------------------------------------------------------------------------------| +| `fail-build-if-reports` | `false` | If set to `true`, the build will be set to broken if the static analysers reports _any_ findings. | +### Store settings + +🔖 Read more about [`CodeChecker store`](http://codechecker.readthedocs.io/en/latest/web/user_guide/#store) in the official documentation. + + + +| Variable | Default | Description | +|------------------|---------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `store` | `false` | If set to `true`, the script will upload the findings to a CodeChecker server. Usually, other flags need to be configured too! | +| `store-url` | | The URL of the CodeChecker product to store to, **including** the [endpoint](http://codechecker.readthedocs.io/en/latest/web/user_guide/#product_url-format). Usually in the format of `http://example.com/ProductName`. Specifying this variable is **required** if `store` was set to `true`. | +| `store-username` | | If the server requires authentication to access, specify the username which the upload should log in with. | +| `store-password` | | The password or [generated access token](http://codechecker.readthedocs.io/en/latest/web/authentication/#personal-access-token) corresponding to the user. 🔐 **Note:** It is recommended that this is configured as a repository secret, and given as such: `${{ secrets.CODECHECKER_PASSWORD }}` when configuring the action. | +| `store-run-name` | (auto-generated, in the format `user/repo: branchname`) | CodeChecker analysis executions are collected into _runs_. A run usually correlates to one configuration of the analysis. Runs can be stored incrementally, in which case CodeChecker is able to annotate that reports got fixed. | ## Action *`outputs`* to use in further steps The action exposes the following outputs which may be used in a workflow's steps succeeding the analysis. -| Variable | Value | Description | -|-------------------|-------------------------------------------|-----------------------------------------------------------------------------------| -| `analyze-output` | Auto-generated, or `analyze-output` input | The directory where the **raw** analysis output files are available. | -| `logfile` | Auto-generated, or `logfile` input | The JSON Compilation Database of the analysis that was executed. | -| `result-html-dir` | Auto-generated. | The directory where the **user-friendly HTML** bug reports were generated to. | -| `result-log` | Auto-generated. | `CodeChecker parse`'s output log file which contains the findings dumped into it. | -| `warnings` | `true` or `false` | Whether the static analysers reported any findings. | +| Variable | Value | Description | +|-------------------|-------------------------------------------|----------------------------------------------------------------------------------------------| +| `analyze-output` | Auto-generated, or `analyze-output` input | The directory where the **raw** analysis output files are available. | +| `logfile` | Auto-generated, or `logfile` input | The JSON Compilation Database of the analysis that was executed. | +| `result-html-dir` | Auto-generated. | The directory where the **user-friendly HTML** bug reports were generated to. | +| `result-log` | Auto-generated. | `CodeChecker parse`'s output log file which contains the findings dumped into it. | +| `store-run-name` | Auto-generated, or `store-run-name` input | The name of the analysis run (if `store` was enabled) to which the results were uploaded to. | +| `warnings` | `true` or `false` | Whether the static analysers reported any findings. | diff --git a/action.yml b/action.yml index bc3838b..56f29c6 100644 --- a/action.yml +++ b/action.yml @@ -44,6 +44,24 @@ inputs: default: 'false' required: true + store: + description: 'Whether to enable storing the results to a CodeChecker server. If enabled, other flags, such as `store-url` must also be set.' + default: 'false' + required: true + store-url: + description: 'The CodeChecker product URL (usually in the format of http://example.com/ProductName) where the store should connect to. Mandatory if `store` is true.' + required: false + store-username: + description: 'If the server requires authentication, the username to authenticate with.' + required: false + store-password: + description: 'The password (or generated private access token) corresponding to the user.' + required: false + store-run-name: + description: 'An identifying name of the analysis run. A run usually correlates to a set of configuration, e.g. analysis mode, branch, etc. If left default, the name is automatically generated from the current repository and branch name.' + default: '__DEFAULT__' + required: true + outputs: logfile: description: 'The location of the JSON Compilation Database that was used for the analysis.' @@ -63,6 +81,10 @@ outputs: description: 'The output directory where the user-friendly HTML reports were stored to.' value: ${{ steps.parse.outputs.HTML_DIR }} + store-run-name: + description: 'The name of the analysis run that the results were uploaded to.' + value: ${{ steps.store-pre.outputs.RUN_NAME }} + runs: using: "composite" steps: @@ -92,7 +114,7 @@ runs: - name: "Build and Package CodeChecker" id: codechecker env: - CODECHECKER_WILL_USE_WEB_API: "false" # TODO: Add support for this later. + CODECHECKER_WILL_USE_WEB_API: ${{ inputs.store == 'true' }} shell: bash run: | set -ex @@ -169,3 +191,34 @@ runs: run: | echo "Static analysis reported warnings, and user requested build breaking." exit 1 + + - name: "Generate the configuration for uploading results" + id: store-pre + if: ${{ inputs.store == 'true' }} + env: + IN_STORE_URL: ${{ inputs.store-url }} + IN_STORE_USERNAME: ${{ inputs.store-username }} + IN_STORE_PASSWORD: ${{ inputs.store-password }} + IN_STORE_RUN_NAME: ${{ inputs.store-run-name }} + + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_REF_TYPE: ${{ github.ref_type }} + GITHUB_SHA: ${{ github.sha }} + shell: bash + run: ${{ github.action_path }}/src/store-pre.sh + + - name: "Store analysis results to server" + id: store + if: ${{ steps.store-pre.outputs.STORE_CONFIGURED == 'true' }} + env: + PROJECT_PATH: ${{ github.workspace }} + CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} + CODECHECKER_STORE_RUN_NAME: ${{ steps.store-pre.outputs.RUN_NAME }} + CODECHECKER_STORE_RUN_TAG: ${{ steps.store-pre.outputs.RUN_TAG }} + RAW_RESULT_DIR: ${{ steps.analyze.outputs.OUTPUT_DIR }} + + IN_CONFIGFILE: ${{ inputs.config }} + IN_STORE_URL: ${{ inputs.store-url }} + shell: bash + run: ${{ github.action_path }}/src/store.sh diff --git a/src/store-pre.sh b/src/store-pre.sh new file mode 100755 index 0000000..78a8909 --- /dev/null +++ b/src/store-pre.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -x + +if [[ -z "$IN_STORE_URL" ]]; then + echo "::error title=Configuration error::Uploading results to a server was enabled, but the upload URL is not configured." + exit 1 +fi + +if [[ ! -z "$IN_STORE_USERNAME" && ! -z "$IN_STORE_PASSWORD" ]]; then + echo "Configuring credentials..." + cat < ~/.codechecker.passwords.json + { + "client_autologin": true, + "credentials": { + "$IN_STORE_URL": "$IN_STORE_USERNAME:$IN_STORE_PASSWORD" + } + } +EOF + chmod 0600 ~/.codechecker.passwords.json +fi + +if [[ ! -z "$IN_STORE_RUN_NAME" && "$IN_STORE_RUN_NAME" != "__DEFAULT__" ]]; then + echo "Using user-requested run name." + echo "::set-output name=RUN_NAME::$IN_STORE_RUN_NAME" + echo "::set-output name=RUN_TAG::" + echo "::set-output name=STORE_CONFIGURED::true" + exit 0 +fi + +if [[ "$GITHUB_REF_TYPE" == "branch" ]]; then + echo "Auto-generating run name for a BRANCH." + echo "::set-output name=RUN_NAME::$GITHUB_REPOSITORY: $GITHUB_REF_NAME" + echo "::set-output name=RUN_TAG::$GITHUB_SHA" + echo "::set-output name=STORE_CONFIGURED::true" + exit 0 +elif [[ "$GITHUB_REF_TYPE" == "tag" ]]; then + echo "Auto-generating run name for a TAG." + echo "::set-output name=RUN_NAME::$GITHUB_REPOSITORY tags" + echo "::set-output name=RUN_TAG::$GITHUB_REF_NAME" + echo "::set-output name=STORE_CONFIGURED::true" + exit 0 +fi + +echo "::notice title=Preparation for store::Failed to generate a run name. Implementation error?" +echo "::set-output name=STORE_CONFIGURED::false" diff --git a/src/store.sh b/src/store.sh new file mode 100755 index 0000000..d058d56 --- /dev/null +++ b/src/store.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -x + +if [[ -z "$IN_STORE_URL" ]]; then + echo "::error title=Internal error::environment variable 'IN_STORE_URL' missing!" + exit 1 +fi + +if [[ -z "$PROJECT_PATH" ]]; then + echo "::error title=Internal error::environment variable 'PROJECT_PATH' missing!" + exit 1 +fi + +if [[ -z "$RAW_RESULT_DIR" ]]; then + echo "::error title=Internal error::environment variable 'RAW_RESULT_DIR' missing!" + exit 1 +fi + +if [[ -z "$CODECHECKER_STORE_RUN_NAME" ]]; then + echo "::error title=Internal error::environment variable 'CODECHECKER_STORE_RUN_NAME' missing!" + exit 1 +fi + +if [[ ! -z "$IN_CONFIGFILE" ]]; then + CONFIG_FLAG_1="--config" + CONFIG_FLAG_2=$IN_CONFIGFILE + echo "Using configuration file \"$IN_CONFIGFILE\"!" +fi + +if [[ ! -z "$CODECHECKER_STORE_RUN_TAG" ]]; then + RUN_TAG_FLAG_1="--tag" + RUN_TAG_FLAG_2=$CODECHECKER_STORE_RUN_TAG +fi + +"$CODECHECKER_PATH"/CodeChecker \ + store \ + "$RAW_RESULT_DIR" \ + --url "$IN_STORE_URL" \ + --name "$CODECHECKER_STORE_RUN_NAME" \ + --trim-path-prefix "$PROJECT_PATH" \ + $RUN_TAG_FLAG_1 $RUN_TAG_FLAG_2 \ + $CONFIG_FLAG_1 $CONFIG_FLAG_2 diff --git a/test/codechecker.server.json b/test/codechecker.server.json new file mode 100644 index 0000000..7508da4 --- /dev/null +++ b/test/codechecker.server.json @@ -0,0 +1,13 @@ +{ + "authentication": { + "enabled": true, + "session_lifetime": 86400, + "refresh_time": 86400, + "logins_until_cleanup": 42, + "method_dictionary": { + "enabled": true, + "auths": [], + "groups": {} + } + } +} diff --git a/test/prepare-docker-server.sh b/test/prepare-docker-server.sh new file mode 100755 index 0000000..5288446 --- /dev/null +++ b/test/prepare-docker-server.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -ex + +if [[ -z "$CODECHECKER_VERSION" ]]; then + echo "::error title=Test configuration error::Missing environment variable 'CODECHECKER_VERSION'" + exit 1 +fi + +cat < ~/codechecker-server-data/docker-compose.yml + version: '3' + + services: + codechecker-server: + container_name: codechecker-server + image: "codechecker/codechecker-web:$CODECHECKER_VERSION" + ports: + - '8001:8001/tcp' + networks: + - codechecker-network + volumes: + - $HOME/codechecker-server-data:/workspace + + networks: + codechecker-network: + driver: bridge +EOF + +python3 - <<- EOF + import hashlib + with open("$HOME/codechecker-server-data/root.user", 'w', + encoding='utf-8', errors='ignore') as rootf: + rootf.write(f"root:{hashlib.sha256(b'root:root').hexdigest()}") +EOF