Python Code Quality: GitLab, SonarQube, OWASP, Dependency-Check, and Essential Dev Tools Integration

Overview

Purpose

In the fast-evolving landscape of software development, ensuring code quality and security is paramount. To streamline this process, integrating a robust set of tools becomes imperative. In this blog, we embark on a journey to seamlessly integrate GitLab, SonarQube, pytest, coverage.py, and dependency-check into a unified ecosystem. By combining version control, code analysis, testing, code coverage evaluation, and dependency scanning, we aim to create a powerful and efficient pipeline that not only enhances the overall quality of the codebase but also fortifies it against potential vulnerabilities. Join us as we explore each tool's role in this integration, demonstrating how they collectively contribute to a more resilient and high-performing software development lifecycle.

Requirements

After the code is committed, these reports should be generated by CI/CD pipelines automatically.

  1. Test result and test coverage report by pytest and Coverage.py.
  2. Dependency check report by Dependency-Check
  3. Code quality by SonarQube
  4. Sommarize these reports into GitLab & SonarQube.

Procedures

Test result and coverage

Test result and coverage in console and HTML reports (Pytest and Coverage.py)

  1. Test result (Console):

    You can add -rAR in pyproject.toml to show test result on console.

  2. Test result (HTML):

    I don't try it, you can try the following 2 tools. The 1st is provided by pytest but it seems simple. The 2nd seems beautifuler, but the last commit date is 2022/04/29.

  3. Test coverage (Console):

    You can add --cov-report=term (term means terminal) in pyproject.toml to show test coverage on console.

  4. Test coverage (HTML):

    You can add --cov-report=html in pyproject.toml to generate test coverage HTML report.

Test Result and coverage on GitLab

  1. If you want to show test results in GitLab, you need to add --junitxml=junit_report.xml (GitLab supports JUnit format) and --cov-report=xml (GitLab supports Cobertura format.) in pyproject.toml to ask pytest to generate corresponding xml files.

    1[tool.pytest.ini_options]
    2python_files = ["tests/*.py"]
    3addopts = "-R --cov=. --cov-report=term --cov-report=xml --cov-report=html --junitxml=junit_report.xml"
    
  2. Then modify .gitlab-ci.yml to upload JUnit and Coverage XML files into GitLab artifacts.

    1artifacts:
    2  reports:
    3    junit: junit_report.xml
    4    coverage_report: # coverage_report is supported in GitLab 14.10
    5      coverage_format: cobertura
    6      path: coverage.xml
    
  3. When you run GitLab CI/CD pipelines, you can see the following log in pytest stage.

    1- generated xml file: /builds/-i39jfYQ/0/ai/service_test/junit_report.xml -
    2...
    3Uploading artifacts...
    4junit_report.xml: found 1 matching artifact files and directories
    5Uploading artifacts as "junit" to coordinator... 201 Created  id=3169 responseStatus=201 Created token=********
    
  4. After the GitLab CI/CD pipeline is completed, you can see the test summary.

  5. And you can click the job to see the detail.

Test Result and coverage on SonarQube

SonarQube can show the test results in old version, but I'm not sure when SonarQube removed it. You can see test coverage only on SonarQube now.

  1. You need to add --cov-report=xml in pyproject.toml to generate coverage.xml

  2. You need to add -D"sonar.python.coverage.reportPaths=./coverage.xml" in .gitlab-ci.yml to ask SonarQube to load coverage.xml into SonarQube.

  3. When you run GitLab CI/CD pipelines, you can see the following logs in pytest stage.

    1Coverage XML written to file coverage.xml
    
  4. And you can see the coverage report in SonarQube now.

Dependency Check

  1. There are several docker images, please use the first one, or it will download all CVE updates (from 2002 to now) every time. (reference: https://hub.docker.com/r/owasp/dependency-check-action/)

  2. Because Python is still in experientmal, please add --enableExperimental parameter.

  3. Please add -n in the parameter to stop downloading updates, or you will see the following log, it takes several minutes to execute.

     1...
     2[INFO] Checking for updates
     3[INFO] NVD CVE requires several updates; this could take a couple of minutes.
     4[INFO] Download Started for NVD CVE - 2002
     5[INFO] Download Complete for NVD CVE - 2002  (1841 ms)
     6[INFO] Processing Started for NVD CVE - 2002
     7[INFO] Download Started for NVD CVE - 2003
     8[INFO] Download Complete for NVD CVE - 2003  (1425 ms)
     9[INFO] Processing Started for NVD CVE - 2003
    10...
    11[INFO] Download Started for NVD CVE - 2023
    12[INFO] Download Complete for NVD CVE - 2023  (2710 ms)
    13[INFO] Processing Started for NVD CVE - 2023
    14...
    15[INFO] Updated the CPE ecosystem on 136030 NVD records
    16...
    
  4. Reports (on GitLab)

    Depends on your requirement, you can put dependency check result on GitLab CI/CD pipelines, HTML reports, or SonarQube.

    1. If there is a security vulnerability, you can see it in GitLab CI/CD pipeline logs, for example, I use bootstrap 3.2 and it shows the error.

      1[ERROR]
      2One or more dependencies were identified with vulnerabilities that have a CVSS score greater than or equal to '0.0':
      3bootstrap.min.js: Bootstrap before 4.0.0 is end-of-life and no longer maintained.(3.9)
      4requirements.txt: CVE-2022-25882(7.5)
      
    2. If you want to download HTML report, you can add dependency-check-report.html in artifacts of .gitlab-ci.yml.

      1artifacts:
      2  when: always
      3  paths:
      4    - "./dependency-check-report.html"
      
  5. Reports (on SonarQube)

    1. You need to install Dependency-Check Plugin for SonarQube

    2. Add dependency-check-report.json, sonar.dependencyCheck.jsonReportPath, and sonar.dependencyCheck.htmlReportPath in .gitlab-ci.yml.

      1artifacts:
      2  when: always
      3  paths:
      4    - "./dependency-check-report.html"
      5    - "./dependency-check-report.json"
      
      1-D"sonar.dependencyCheck.jsonReportPath=../dependency-check-report.json"
      2-D"sonar.dependencyCheck.htmlReportPath=../dependency-check-report.html"
      
    3. The plugin will insert security vulnerabilities into SonarQube's result, you can see the detail in the issues.

    4. If you want to see the HTML report in SonarQube, you can click the More tab and see it.

Code quality (SonarQube)

The dashboard in SonarQube 10.0 is a little different than the previous version.

  1. SonarQube:

Configuration files

pyproject.toml

  1. The following is the completed pyproject.toml

     1[tool.pytest.ini_options]
     2python_files = ["tests/*.py"]
     3addopts = "-rAR --cov=. --cov-report=term --cov-report=xml --cov-report=html --junitxml=junit_report.xml"
     4log_cli = true
     5log_cli_level = "INFO"
     6
     7[tool.coverage.run]
     8branch = true
     9omit = [
    10    "*/tests/*",
    11    "config.py",   # it seems a bug, refer https://github.com/nedbat/coveragepy/issues/1653
    12    "config-3.py",
    13]
    14
    15[tool.coverage.paths]
    16source = ["."]
    17
    18[tool.coverage.html]
    19directory = "coverage_html_report"
    

.gitlab-ci.yml

  1. The following is the completed .gitlab-ci.yml

     1variables:
     2    PROJECT_NAME: "Service_test"
     3    VER: 1.0.0
     4    DOCKER_FILE_NAME: service_test
     5    SONARQUBE_URL: https://example.com:9000/
     6    SONARQUBE_TOKEN: squ_****************************************
     7    SONARQUBE_PROJECT_KEY: service_test
     8    PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache.pip"
     9
    10image: python:3.11.6
    11
    12cache:
    13    paths:
    14        - .cache/pip
    15        - venv/
    16
    17stages:
    18    - test
    19    - dependency_check
    20    - quality_check
    21
    22test:
    23    stage: test
    24    script:
    25        # Show basic information
    26        - python --version
    27        - pip list
    28
    29        # Prepare virtual environment
    30        - pip install --upgrade pip
    31        - pip install virtualenv
    32        - virtualenv venv
    33        - source venv/bin/activate
    34
    35        # Install libraries
    36        - pip install -r requirements.txt
    37        - pip list
    38
    39        # Test
    40        - pytest --version
    41        - pytest
    42    artifacts:
    43        reports:
    44            junit: junit_report.xml
    45            coverage_report: # coverage_report is supported after GitLab 14.10
    46                coverage_format: cobertura
    47                path: coverage.xml
    48
    49        paths:
    50            - coverage_html_report
    51            - coverage.xml # Save coverage XML report as an artifact
    52
    53dependency_check:
    54    stage: dependency_check
    55    image:
    56        #name: registry.gitlab.com/gitlab-ci-utils/docker-dependency-check:latest
    57        name: owasp/dependency-check-action:latest
    58        entrypoint: [""]
    59    script:
    60        - - /usr/share/dependency-check/bin/dependency-check.sh -n --scan "." --format ALL --project "$PROJECT_NAME" --failOnCVSS 0 --enableExperimental --log ./dependency-check.log
    61    artifacts:
    62        when: always
    63        paths:
    64            - "./dependency-check-report.html"
    65            - "./dependency-check-report.json"
    66            - "./dependency-check.log"
    67
    68quality_check:
    69    stage: quality_check
    70    image:
    71        name: sonarsource/sonar-scanner-cli:5.0.1
    72    script:
    73        - echo $PROJECT_NAME
    74        - echo $SONARQUBE_URL
    75        - echo $SONARQUBE_TOKEN
    76        - sonar-scanner -D"sonar.host.url=$SONARQUBE_URL"
    77        -D"sonar.token=$SONARQUBE_TOKEN"
    78        -D"sonar.projectKey=$SONARQUBE_PROJECT_KEY"
    79        -D"sonar.projectName=$PROJECT_NAME"
    80        -D"sonar.sourceEncoding=utf-8"
    81        -D"sonar.exclusions=test/**/*, coverage/**/*, test-report/**/*, dependency-check-report.html"
    82        -D"sonar.javascript.lcov.reportPaths=./coverage/lcov.info"
    83        -D"sonar.python.coverage.reportPaths=./coverage.xml"
    84        -D"sonar.dependencyCheck.jsonReportPath=../dependency-check-report.json"
    85        -D"sonar.dependencyCheck.htmlReportPath=../dependency-check-report.html"
    86        -D"sonar.python.version=3.8,3.11"
    

Posts in this Series