Library

See also:

A charm library is a Python file (or a Python module) in your charm project that allows charm developers to easily share and reuse auxiliary logic related to charms – for example, logic related to the relations between charms. They are a great way to help other authors to write charms that can relate to yours.

Authors associate libraries with a specific charm, and publish them to Charmhub with a reference to the origin charm. This does not prevent re-use, modification, and sharing.

The publishing tools around libraries are deliberately kept simple. Libraries are however versioned and uniquely identified.

A library must comprise a single Python file. If you write a library that feels too “big” for a single file, it is likely that the library should be split up, or that you are actually writing a full on charm.

Contents:

Location

Charm libraries are located in a directory inside the charm with the following structure:

$CHARMDIR/lib/charms/<charm>/v<API>/<libname>.py

where $CHARMDIR is the project’s root (contains src/, hooks/, etc.), and the <charm> placeholder represents the charm responsible for the library named as <libname>.py with API version <API>.

For example, inside a charm mysql, the library db with major version 3 will be in a directory with the structure below:

$CHARMDIR/lib/charms/mysql/v3/db.py

When you pack your charm, Charmcraft copies the top lib directory into the root directory of the charm. Thus, to import this library in Python use the full path minus the top lib directory, as below:

import charms.mysql.v3.db

Structure

A charm library is a Python file with the following structure:

Docstring

Your charm file begins with a long docstring. This docstring describes your library. Charmcraft publishes it as your library’s documentation on Charmhub. This documentation is updated whenever a new version of the library is published.

The docstring supports Markdown, specifically, CommonMark.

Metadata

After the docstring, there are a few metadata keys, as below.

LIBID

The LIBID key controls the unique identifier for a library across the entire universe of charms. The value is assigned by Charmhub to this particular library automatically at library creation time. This key enables Charmhub and charmcraft to track the library uniquely even if the charm or the library are renamed, which allows updates to warn and guide users through the process.

LIBAPI

LIBAPI is set to an initial state of 0. In general, LIBAPI must match the major version in the import path.

LIBPATCH

LIBPATCH is set to an initial state of 1. In general, it must match the current patch version (needs to be updated when changing).

PYDEPS

The PYDEPS key is an optional key that allows you to declare external Python dependencies. Charmcraft will make sure to add them to the dependencies required for your charm.

The key maps to a list of strings. Each string is a regular “pip installable” Python dependency that will be retrieved from PyPI in the usual way (subject to the user’s system configuration) and which supports all dependency formats (just the package name, a link to a Github project, etc.).

Some examples of possible PYDEPS declarations are as below:

PYDEPS=["jinja2"]

PYDEPS = ["pyyaml", "httpcore<0.15.0,>=0.14.5"]

PYDEPS = [
    "git+https://github.com/canonical/operator/#egg=ops",
    "httpcore<0.15.0,>=0.14.5",
    "requests",
]

Of course, only one PYDEPS declaration is allowed.

Charmcraft will collect the dependencies from all libraries included in the charm and install them from source in the virtual environment inside the .charm file, together with all the other Python dependencies declared by the charm itself.

Note that, when called to install all the dependencies from the charm and all the used libraries, pip may detect conflicts between the requested packages and their versions. This is a feature, because it’s always better to detect incompatibilities between dependencies at this moment than when the charm is being deployed or run after deployment.

Code

After the docstring and the metadata, there’s the library code. For some great examples of libraries, confirming to best practices, see:

If you’d like to highlight other libraries that you have found useful, or share a library that you’re working on, drop a comment using the feedback link below.

grafana_dashboard

The grafana_dashboard library is a great example of the core use case for charm libraries: a charm wants to tell other charms how to interact with it. This library demonstrates how to integrate the grafana dashboard with a charmed service.

redis

The redis library implements a requires/provides pattern for redis. It’s useful both for interacting with redis, and as a reference for implementing this commonly used pattern yourself!

kubernetes_service_patch

The kubernetes_service_patch library is maintained by the Observability team, but is useful for most Kubernetes charms. It implements a workaround for an issue with service naming in Juju Core, and is an excellent example of how libraries can be used to share known good workarounds with the community, and extend or modify the core Juju feature set.

ingress

Another fantastic and re-usable library for Kubernetes deploys, the ingress library implements the Requires/Provides pattern for relations with the nginx-ingress-integrator charm.

systemd

Stepping over to the world of “legacy estate” (bare metal machines and vms), the systemd library wraps calls to the systemctl command on the host. It provides quick access to a core set of commands for enabling, disabling, restarting, and reconfiguring services on bare metal and VMs. The library also integrates the output from systemctl with the charm logs, formalizes error checking, and turns some common multi step tasks, like masking and stopping a service, into a single call to the lib.

(If you’ve used charmhelpers in your reactive charms, you may recognize some of the code here, and in the other libraries that live at that link – we’re working on pulling selected bits of charmhelpers into standalone libraries, so that charms built with the Operator Framework can take advantage of the community knowledge built up in charmhelpers, without needing to take on the weight of the entire charmhelpers lib.)