Charm authors need a way to easily share and reuse logic, charms make that even more important given the two-sided nature of relations. That is, a given interface type needs logic on the providing side and on the required side, this is better handled when the responsibility lies on the same entity.
The Charmcraft tool supports a first-class mechanism to reuse logic in a form of python modules named libraries which are published on Charmhub for easy consumption.
This model diverges from generic versioning systems (as git/Github) and packages publishing systems (like PyPI) because we aim for simplicity as there is no need to create further structures to distribute and install the library, nor we need to have registered users in other platforms.
Furthermore, libraries shared through this mechanism are directly integrated with Charmhub, allowing other users to find our shared libraries (including their documentation) when exploring our charms on that platform.
The structure on disk
Libraries are located in a specific directory inside a charm project with the following structure:
$CHARMDIR is the project’s root (contains
hooks/, etc.), and the
<charm> placeholder represents the charm responsible for the library named as
<libname>.py with API version
<API>. So, as a more concrete example:
In this case, the author of charm mysql is publishing the library db with major version 3. This file may be used both by the author and by any other charm authors that are interested in the offered functionality.
The library may then be imported by its fully qualified path:
db.py the following fields must be defined:
LIBID = "abcdef1234" LIBAPI = 3 # Must match the major version in the import path. LIBPATCH = 2 # The current patch version. Must be updated when changing.
LIBID is a unique identifier for the library across the entire universe of charms. This is assigned by Charmhub to this particular library automatically at library creation time. That id 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.
Creating and sharing a charm library
Let’s start from a charm that has no libraries at all:
jdoe@machine:/home/john/blogsystem$ ll lib ls: cannot access 'lib': No such file or directory
The library we will be creating belongs to a charm, so we will not only be working inside a charm’s directory, but this needs to be registered in Charmhub (later we’ll see that we can ask Charmhub to list all libraries belonging to a given charm).
The first step is create the bare library template:
jdoe@machine:/home/john/blogsystem$ charmcraft create-lib superlib Library charms.blogsystem.v0.superlib created with id e76db596f4bb44fd9c0ce068669fc2ac. Consider 'git add lib/charms/blogsystem/v0/superlib.py'.
That command created a file on disk, inside the proper directory structure we mentioned above. It’s a good idea to incorporate this new file now to your code versioning system.
jdoe@machine:/home/john/blogsystem$ ll lib/charms/blogsystem/v0/superlib.py -rw-rw-r-- 1 jdoe jdoe 1048 Dec 17 13:46 lib/charms/blogsystem/v0/superlib.py
That file is just a template we must fill, though. We can separate the content in three parts:
the module’s docstring: this will be the library’s documentation, automatically presented on Charmhub and updated whenever a new version of the library is published; markdown is supported, following the CommonMark specification.
the three metadata fields:
LIBIDis already assigned by Charmhub, never change it;
LIBPATCHare set to an initial state (
1correspondingly), which is fine for now, below we’ll see how/when to update these.
the rest of the file: here is where we will add all the library’s code.
Now we’re ready to publish our library. This is done automatically with one command, both uploading the library content and making it available for everybody.
jdoe@machine:/home/john/blogsystem$ charmcraft publish-lib charms.blogsystem.v0.superlib Library charms.blogsystem.v0.superlib sent to the store with version 0.1
After this step, our library is ready to be used by other developers.
We would eventually need to evolve the library’s content (new functionalities, bug fixing, documentation improvements, etc.). Every time we want to offer a new version of our library, we will need to call the
However, before publishing new versions, we need to update the
LIBPATCH metadata fields inside the library file. Most times it is enough to just increment
LIBPATCH, but if we’re introducing breaking changes we must work with the major API version.
jdoe@machine:/home/john/blogsystem$ charmcraft publish-lib charms.blogsystem.v0.superlib Library charms.blogsystem.v0.superlib sent to the store with version 0.2
We need to take in consideration that users of our library will update it automatically to the latest PATCH version with the same API version.
To avoid breaking other people’s library usage we should increment the
LIBPAPI version and reset
0. But before adding the breaking changes and updating these values, we should copy the library to the new path:
jdoe@machine:/home/john/blogsystem$ mkdir lib/charms/blogsystem/v1 jdoe@machine:/home/john/blogsystem$ cp lib/charms/blogsystem/v0/superlib.py lib/charms/blogsystem/v1/
This way we can maintain different major API versions independently, being able to update our v0 after we published v1.
jdoe@machine:/home/john/blogsystem$ charmcraft publish-lib charms.blogsystem.v1.superlib Library charms.blogsystem.v1.superlib sent to the store with version 1.0
If the charm we’re writing needs to interact with another service, we should check if that service provides any library that would help in that interaction.
The easiest way to see this from the command line is using
list-lib command, which will query Charmhub and show which libraries are published for the specified charm, indicating also the API/patch versions for the corresponding tips.
jdoe@machine:/home/jane/autoblog$ charmcraft list-lib blogsystem Library name API Patch superlib 1 0
Listing does not show older API versions (above we published
superlib also in v0.2), this ensures that new users always start with the latest version.
Another good entry point for searching libraries is to explore The Open Operator Collection.
Using a shared charm library
The moment we decided that we need to use another charm’s library in our charm, we need to fetch it. This is done through the
fetch-lib command in the Charmcraft tool:
jdoe@machine:/home/jane/autoblog$ charmcraft fetch-lib charms.blogsystem.v1.superlib Library charms.blogsystem.v1.superlib version 1.0 downloaded.
That command will download the library itself, creating the required directory layout:
jdoe@machine:/home/jane/autoblog$ ll lib/charms/blogsystem/v1/superlib.py -rw-rw-r-- 1 jdoe jdoe 1061 Dec 17 15:24 lib/charms/blogsystem/v1/superlib.py
This file is now part of our charm’s project. It will be packed inside our charm and distributed with it. And all we need to use it is to directly import it from our charm’s code (note that the charm automatically has the
lib directory as part of the Python import paths).
from charms.blogsystem.v1 import superlib
In the future we may want to update the library we’re using: all we need to do is run the same
fetch-lib command (as we already have the library, it will only update it if necessary):
jdoe@machine:/home/jane/autoblog$ charmcraft fetch-lib charms.blogsystem.v1.superlib Library charms.blogsystem.v1.superlib was already up to date in version 1.0.