Jubilant
Jubilant is a Python library that provides a high-level, asynchronous wrapper around the Juju CLI, primarily designed for integration testing of Juju charms. It simplifies programmatic interactions with Juju controllers and models, allowing for automation of deployments, configurations, and status checks. The library is actively maintained by Canonical, with frequent minor releases, and is currently at version 1.8.0.
Common errors
-
ModuleNotFoundError: No module named 'jubilant'
cause The 'jubilant' Python package has not been installed in your current Python environment.fixOpen your terminal or command prompt and run `pip install jubilant` to install the library. -
jubilant.errors.JujuError: Command 'juju' not found in PATH. Make sure Juju CLI is installed.
cause Jubilant could not find the 'juju' executable. This indicates the Juju CLI is either not installed or not configured correctly in your system's environment PATH.fixInstall the Juju CLI according to the official Juju documentation for your operating system (e.g., `snap install juju --classic` on Linux), and ensure its installation directory is added to your system's PATH environment variable. Verify by running `juju --version` in a new terminal session. -
juju status --format json: application 'my-app' not found (stderr: ) (exit code 1)
cause This error, originating from the underlying Juju CLI, usually means that an application, model, or other Juju resource specified in your Jubilant code does not exist, is misspelled, or is not in the expected state within the targeted Juju model.fixCarefully check the names of applications, models, and other Juju entities used in your Jubilant calls. Ensure they match your Juju environment exactly and that the Juju controller and model are in the expected operational state before the call is made. -
SyntaxError: invalid syntax
cause Jubilant is built on Python's `asyncio` framework and uses `async/await` syntax. `asyncio.run()` also requires Python 3.7+. Jubilant itself requires Python 3.8+.fixEnsure you are running your Python code with Python 3.8 or a newer version to support the required syntax and `asyncio` features.
Warnings
- breaking The `Juju.offer()` method's API was updated in v1.8.0 to explicitly respect `self.model`. If your code previously relied on implicit model targeting behavior when calling `offer()`, it might now target a different model or require an explicit `model` argument to ensure the correct context.
- gotcha Jubilant acts as a programmatic wrapper around the Juju command-line interface (CLI). For Jubilant to function, the `juju` CLI must be installed on your system and accessible via the system's PATH. This is an essential external dependency, not a Python package managed by pip.
- gotcha If you need to interact with Juju 2.9 controllers, you must use the `jubilant-backports` package instead of the standard `jubilant` package. `jubilant-backports` is specifically designed for Juju 2.9 compatibility, while `jubilant` targets Juju 3.x+.
- gotcha In v1.2.0, the `all_*` and `any_*` helper methods (e.g., `jubilant.all_active`) were updated to correctly include subordinate units in their evaluations. While considered a bug fix, this behavioral change might affect tests or logic that implicitly relied on subordinate units being excluded.
Install
-
pip install jubilant
Imports
- Juju
from jubilant import Juju
- jubilant_backports
import jubilant_backports as jubilant
Quickstart
import asyncio
from jubilant import Juju
async def main():
juju = Juju()
model_name = "my-temp-test-model"
print(f"Attempting to add Juju model: {model_name}")
try:
# Add a temporary model. For real tests, consider juju.temp_model()
# which uses a context manager for automatic cleanup.
await juju.add_model(model_name)
print(f"Successfully added model '{model_name}'. Deploying charm...")
# Deploy a simple charm
await juju.deploy("ch:ubuntu", model=model_name)
print("Ubuntu charm deployment initiated.")
# Wait for the charm to be active
await juju.wait_for_idle(apps=["ubuntu"], model=model_name)
print("Ubuntu charm is active and idle.")
status = await juju.get_status(model=model_name)
print(f"Model '{model_name}' status: {status.model.status}")
except Exception as e:
print(f"An error occurred during Juju operations: {e}")
finally:
print(f"Attempting to destroy Juju model: {model_name}")
await juju.destroy_model(model_name)
print(f"Model '{model_name}' destroyed.")
if __name__ == "__main__":
# This example requires the Juju CLI to be installed and configured
# on your system, and supports Python 3.8+.
asyncio.run(main())