Introducing charm-ci 1.0.0: run charm integration tests locally and on GitHub Actions

charm-ci 1.0.0 is now available. It gives charm developers one workflow for building artifacts, preparing test environments, and running existing tox and pytest integration suites locally and on GitHub Actions. It works with Jubilant and pytest-operator.

Running a charm integration test involves much more than running pytest. The charm and its resources must be built, Juju and a test environment such as LXD or Kubernetes must be prepared, and the resulting artifacts must be made available to the tests. Larger repositories may repeat this across several charms, architectures, bases, and test environments.

Much of this setup traditionally lives in GitHub Actions workflows and repository-specific scripts. As those workflows grow, they become harder to understand, maintain, and reproduce locally. A test can fail in CI without an easy way to run the same build, environment, and test job on a developer machine.

charm-ci moves this process into three repository configuration files and a command-line tool, opcli:

  • artifacts.yaml describes the charms, rocks, and snaps to build.
  • concierge.yaml describes the test environment.
  • spread.yaml describes the test jobs and execution matrix.

During a build, opcli generates build/artifacts.build.yaml to pass the resulting artifact references to the test and publishing steps. This file is generated and is not edited by developers.

opcli uses Concierge to prepare the environment and Spread to execute the test jobs.

Example: HAProxy operator

canonical/haproxy-operator contains four charms and several integration test suites, including cross-charm tests. It builds for amd64 and arm64 and tests using both LXD and Canonical Kubernetes environments.

After cloning the repository, install opcli:

uv tool install \   
  "opcli[cli] @ git+https://github.com/canonical/charm-ci.git@v1.0.0"              

On a fresh Ubuntu machine, opcli install all installs the local tools needed for building and testing.

Build the required artifacts locally:

opcli artifacts build

Alternatively, with an authenticated GitHub CLI, reuse the artifacts from a successful integration-test workflow run. Set RUN_ID to the selected run:

opcli artifacts fetch \
  --repo canonical/haproxy-operator \
  --run-id "$RUN_ID"

List the available jobs and run one locally:

opcli spread run -- -list
opcli spread run -- \
  integration-test-local:ubuntu-24.04:build/haproxy-spoe-auth-operator/tests/integration/run:test_charm

The corresponding GitHub Actions workflow is small:

jobs:
  integration-test:
    uses: canonical/charm-ci/.github/workflows/integration-test.yml@v1.0.0
    permissions:
      contents: read
      packages: write
      actions: read

Fast CI

The reusable workflow builds each artifact and architecture in parallel. Test jobs also run as a matrix, with each Spread task assigned to its own runner. Test environments prepare in parallel with artifact builds, then download the completed artifacts before executing.

Artifacts are built once and reused by every test that needs them. In registry mode, unchanged rock builds can also be reused from a cache. Each GitHub Actions matrix job can select GitHub-hosted or self-hosted runner labels, including architecture-specific runners.

Publishing tested artifacts

The publishing workflow finds a successful integration-test run for the same source tree, downloads its build outputs, and publishes them without rebuilding them. By default, it adds version information to charm archives before upload.

This keeps building, testing, and publishing connected: a release starts from the build outputs used by the successful integration tests.

What 1.0.0 means

Version 1.0.0 marks the opcli commands, configuration formats, Spread virtual backends, and reusable workflow inputs as stable. Future releases will follow semantic versioning.

See the documentation and examples for setup details, configuration options, and local testing without Spread. Please report problems or missing use cases through the GitHub issue tracker.

3 Likes