CI/CD¶
This chapter describes the CI/CD pipelines used in the project. The pipelines automate various tasks such as testing, building, and deploying.
Overview¶
GitHub Actions are used to manage automated workflows. Below is an overview of the key workflows included in the project:
Behave Pipeline: Runs behave tests and uploads the results.
Codecov Pipeline: Runs unit tests via pytest and uploads the test coverage.
Pytest Pipeline: Runs unit tests via pytest and uploads the results.
mypy Pipeline: Runs mypy checks and uploads the results.
Ruff Pipeline: Runs ruff checks and uploads the results.
Sequence Diagram CI/CD Pipeline¶
Sequence Diagram of the automatically triggered pipelines¶
Component Diagram CI/CD Pipeline¶
Component Diagram of the automatically triggered pipelines¶
Composite Setup action¶
Since we are using uv as package manager and the setup is the same everytime, it makes sense to create a separate action that can be used from every other action. The so called “composite action” looks like the following:
name: Setup Environment
description: Checks out code and installs the current uv version and it's dependencies defined inside the pyproject.toml file.
inputs:
run_migrations:
description: "Boolean to run the database migrations or not."
required: false
default: 'false'
runs:
using: "composite"
steps:
- name: Install and setup uv
uses: astral-sh/setup-uv@v6 # Sets up `uv` package manager.
with:
enable-cache: true # Caches `uv` installation for faster runs.
cache-dependency-glob: "uv.lock" # Invalidate cache when the lockfile changes.
- name: Set up Python
uses: actions/setup-python@v6 # Sets up Python environment.
with:
python-version-file: "pyproject.toml" # Ensures the correct Python version is used.
- name: Run database migrations # Resets the database before running tests.
if: ${{ inputs.run_migrations == 'true' }} # Only run migrations if input is true.
shell: bash
run: |
echo "y" | uv run trustpoint/manage.py reset_db
As said, this action sets up the environment by installing uv via Astrals github action
setup-uv and uses a pinned version, as well as caching.
After this, the setup-python action
is called which takes the python version from the pyproject file.
At the time of writing this, that action may be faster than uv’s own action because of GitHubs caching mechanism.
The action ends with maybe running database migrations depending on the switch provided by the input run_migrations.
Behave Pipeline¶
The following workflow file is a reusable template workflow
because we want to have exactly one result for every feature file executed.
That is to show the progress inside the README.md file which features are working and which do not work just yet.
This workflow template uses a string as an input value which just specifies which feature file to run.
First of all, we checkout the code via actions/checkout.
Then, we are using the previously defined action (see Composite Setup action) to make uv usable inside this workflow.
Having uv activated, the behave action is triggered for the given feature file.
Note that we need to use uv run trustpoint/manage.py behave instead of uv run behave to make Django available for behave.
Once the tests are ran, an artifact with the test reports is uploaded.
name: Behave Tests
# This workflow is designed to be reusable by other workflows via `workflow_call`.
on:
workflow_call:
inputs:
feature_file:
description: 'Feature file to test' # The specific feature file to be tested.
required: true # This input is mandatory.
type: string # The input type is a string.
jobs:
behave:
runs-on: ubuntu-latest # Use the latest Ubuntu runner for compatibility and performance.
steps:
- name: Checkout Code
uses: actions/checkout@v5 # Ensures the repository code is available.
- name: Setup uv environment
uses: ./.github/actions/setup-uv-action # Call the reusable setup step.
with:
run_migrations: true # Enable database migrations.
- name: Run Behave Tests and Generate HTML Report for ${{ inputs.feature_file }}.feature
run: |
uv run trustpoint/manage.py behave \
--format behave_html_pretty_formatter:PrettyHTMLFormatter \
--outfile behave-report.html \
trustpoint/features/${{ inputs.feature_file }}.feature
- name: Upload Test Report
uses: actions/upload-artifact@v4 # Uploads the test report for review.
if: always() # Ensures the report is uploaded even if tests fail.
with:
name: ${{ inputs.feature_file }}-html-report # Name report based on the feature file tested.
path: behave-report.html # Ensure this file is generated correctly before uploading.
We provide an example on how to use this workflow below:
name: R_013
on:
pull_request:
push:
branches:
- main
jobs:
test:
uses: ./.github/workflows/behave-test-template.yml
with:
feature_file: R_013_remote_credential_download
Codecov Pipeline¶
We are using Codecov for analyzing our pytest code coverage and showing this with a badge. This workflow is also setting up uv as in Composite Setup action and using it to run pytest with a coverage report which will be uploaded to codecov in the next step.
name: Codecov upload
# Trigger this workflow on pull requests to ensure tests are executed before merging.
on:
pull_request:
push:
branches:
- main
jobs:
codecov-upload:
runs-on: ubuntu-latest # Use the latest Ubuntu runner for compatibility and performance.
steps:
- name: Checkout Code
uses: actions/checkout@v5 # Ensures the repository code is available.
- name: Setup uv environment
uses: ./.github/actions/setup-uv-action # Call the reusable setup step.
with:
run_migrations: true # Enable database migrations.
- name: Run Pytest with Coverage
run: uv run pytest --cov=trustpoint --cov-report=xml -o junit_family=legacy trustpoint/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
Pytest Pipeline¶
This pipeline/workflow is kind of the same as the one above except from not running the coverage reports and therefore also not uploading them. Here, we use a git flavored markdown report for printing the report nicely to the job summary. After this, there is the full report uploaded first and lastly, if one or more tests fail, we add a comment to the current pull request.
name: Pytest
# Trigger this workflow on pull requests to ensure tests are executed before merging.
on:
pull_request:
push:
branches:
- main
jobs:
pytest:
runs-on: ubuntu-latest # Use the latest Ubuntu runner for compatibility and performance.
permissions:
contents: read # Grants read access to repository contents.
pull-requests: write # Allows posting test reports as comments on pull requests.
steps:
- name: Checkout Code
uses: actions/checkout@v5 # Ensures the repository code is available.
- name: Setup uv environment
uses: ./.github/actions/setup-uv-action # Call the reusable setup step.
with:
run_migrations: true # Enable database migrations.
- name: Run Pytest and create reports
run: |
mkdir -p reports
uv run pytest \
--md-report-flavor github \
--md-report-color never \
--html=reports/pytest-report.html \
--junitxml=reports/junit-report.xml \
trustpoint/
- name: Display Summary in GitHub Actions even if tests fail
if: always() # Ensures this step runs even if pytest fails.
run: |
echo "<details><summary>Pytest Report</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
cat "md-report.md" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
- name: Upload Test Reports even if tests fail
uses: actions/upload-artifact@v4 # Uploads reports for review.
if: always() # Ensures this step runs even if pytest fails.
with:
name: pytest-reports
path: reports/
- name: Render the report to the PR
uses: marocchino/sticky-pull-request-comment@v2 # Posts test results as a comment on PRs.
if: always() # Always runs.
with:
header: test-report
recreate: true # Replaces previous test reports to keep PR comments clean.
path: md-report.md # Posts the markdown test report in the PR.
mypy Pipeline¶
We use mypy for static type checking in python. This pipeline is actually really short because it just sets up uv from Composite Setup action and then runs mypy.
name: MyPy
# Trigger this workflow on pull requests, ensuring type checks are performed before merging.
on:
pull_request:
push:
branches:
- main
jobs:
mypy:
runs-on: ubuntu-latest # Use the latest Ubuntu runner for compatibility and performance.
steps:
- name: Checkout Code
uses: actions/checkout@v5 # Ensures the repository code is available.
- name: Setup uv environment
uses: ./.github/actions/setup-uv-action # Call the reusable setup step.
- name: Run MyPy
run: uv run mypy . # Runs MyPy type checker using `uv` package manager.
Ruff Pipeline¶
Also, the ruff action is nearly as short as the mypy Pipeline. The only difference is that we now run ruff and upload the report if there are any errors.
name: Ruff
# Trigger this workflow on pull requests to ensure tests are executed before merging.
on:
pull_request:
push:
branches:
- main
jobs:
ruff:
runs-on: ubuntu-latest # Use the latest Ubuntu runner for compatibility and performance.
steps:
- name: Checkout Code
uses: actions/checkout@v5 # Ensures the repository code is available.
- name: Setup uv environment
uses: ./.github/actions/setup-uv-action # Call the reusable setup step.
- name: Run Ruff Linting
run: uv run ruff check .