jubilant¶
Jubilant is a Pythonic wrapper around the Juju CLI for writing charm integration tests.
- exception jubilant.CLIError(returncode, cmd, output=None, stderr=None)¶
Bases:
CalledProcessError
Subclass of
CalledProcessError
that includes stdout and stderr in the__str__
.
- class jubilant.Juju(
- *,
- model: str | None = None,
- wait_timeout: float = 180.0,
- cli_binary: str | PathLike[str] | None = None,
Bases:
object
Instantiate this class to run Juju commands.
Most methods directly call a single Juju CLI command. If a CLI command doesn’t yet exist as a method, use
cli()
.Example:
juju = jubilant.Juju() juju.deploy('snappass-test')
- Parameters:
model – If specified, operate on this Juju model, otherwise use the current Juju model.
wait_timeout – The default timeout for
wait()
(in seconds) if that method’s timeout parameter is not specified.cli_binary – Path to the Juju CLI binary. If not specified, uses
juju
and assumes it is in the PATH.
- add_model(
- model: str,
- *,
- controller: str | None = None,
- config: Mapping[str, bool | int | float | str | SecretURI] | None = None,
Add a named model and set this instance’s model to it.
To avoid interfering with CLI users, this won’t switch the Juju CLI to the newly-created model. However, because
model
is set to the name of the new model, all subsequent operations on this instance will use the new model.- Parameters:
model – Name of model to add.
controller – Name of controller to operate in. If not specified, use the current controller.
config – Model configuration as key-value pairs, for example,
{'image-stream': 'daily'}
.
- add_unit(
- app: str,
- *,
- attach_storage: str | Iterable[str] | None = None,
- num_units: int = 1,
- to: str | Iterable[str] | None = None,
Add one or more units to a deployed application.
- Parameters:
app – Name of application to add units to.
attach_storage – Existing storage(s) to attach to the deployed unit, for example,
foo/0
ormydisk/1
. Not available for Kubernetes models.num_units – Number of units to add.
to – Machine or container to deploy the unit in (bypasses constraints). For example, to deploy to a new LXD container on machine 25, use
lxd:25
.
- cli(
- *args: str,
- include_model: bool = True,
- stdin: str | None = None,
Run a Juju CLI command and return its standard output.
- Parameters:
args – Command-line arguments (excluding
juju
).include_model – If true and
model
is set, insert the--model
argument after the first argument in args.stdin – Standard input to send to the process, if any.
- cli_binary: str¶
Path to the Juju CLI binary. If None, uses
juju
and assumes it is in the PATH.
- config(
- app: str,
- *,
- app_config: bool = False,
- config(
- app: str,
- values: Mapping[str, bool | int | float | str | SecretURI | None],
Get or set the configuration of a deployed application.
If called with only the app argument, get the config and return it. If called with the values argument, set the config values and return
None
.- Parameters:
app – Application name to get or set config for.
values – Mapping of config names to values. Reset values that are
None
.app_config – When getting config, set this to True to get the (poorly-named) “application-config” values instead of charm config.
- debug_log(*, limit: int = 0) str ¶
Return debug log messages from a model.
- Parameters:
limit – Limit the result to the most recent limit lines. Defaults to 0, meaning return all lines in the log.
- deploy(
- charm: str | PathLike[str],
- app: str | None = None,
- *,
- attach_storage: str | Iterable[str] | None = None,
- base: str | None = None,
- channel: str | None = None,
- config: Mapping[str, bool | int | float | str | SecretURI] | None = None,
- constraints: Mapping[str, str] | None = None,
- force: bool = False,
- num_units: int = 1,
- resources: Mapping[str, str] | None = None,
- revision: int | None = None,
- storage: Mapping[str, str] | None = None,
- to: str | Iterable[str] | None = None,
- trust: bool = False,
Deploy an application or bundle.
- Parameters:
charm – Name of charm or bundle to deploy, or path to a local file (must start with
/
or.
).app – Optional application name within the model; defaults to the charm name.
attach_storage – Existing storage(s) to attach to the deployed unit, for example,
foo/0
ormydisk/1
. Not available for Kubernetes models.base – The base on which to deploy, for example,
ubuntu@22.04
.channel – Channel to use when deploying from Charmhub, for example,
latest/edge
.config – Application configuration as key-value pairs, for example,
{'name': 'My Wiki'}
.constraints – Hardware constraints for new machines, for example,
{'mem': '8G'}
.force – If true, bypass checks such as supported bases.
num_units – Number of units to deploy for principal charms.
resources – Specify named resources to use for deployment, for example:
{'bin': '/path/to/some/binary'}
.revision – Charmhub revision number to deploy.
storage – Constraints for named storage(s), for example,
{'data': 'tmpfs,1G'}
.to – Machine or container to deploy the unit in (bypasses constraints). For example, to deploy to a new LXD container on machine 25, use
lxd:25
.trust – If true, allows charm to run hooks that require access to cloud credentials.
- destroy_model(
- model: str,
- *,
- destroy_storage: bool = False,
- force: bool = False,
Terminate all machines (or containers) and resources for a model.
If the given model is this instance’s model, also sets this instance’s
model
to None.- Parameters:
model – Name of model to destroy.
destroy_storage – If true, destroy all storage instances in the model.
force – If true, force model destruction and ignore any errors.
- exec(
- *command: str,
- machine: int,
- wait: float | None = None,
- exec(
- *command: str,
- unit: str,
- wait: float | None = None,
Run the command on the remote target specified.
You must specify either machine or unit, but not both.
Note: this method does not support running a command on multiple units at once. If you need that, let us know, and we’ll consider adding it with a new
exec_multiple
method or similar.- Parameters:
command – Command to run, along with its arguments.
machine – ID of machine to run the command on.
unit – Name of unit to run the command on, for example
mysql/0
ormysql/leader
.wait – Maximum time to wait for command to finish;
TimeoutError
is raised if this is reached. Default is to wait indefinitely.
- Returns:
The task created to run the command, including logs, failure message, and so on.
- Raises:
ValueError – if the machine or unit doesn’t exist.
TaskError – if the command failed.
TimeoutError – if wait was specified and the wait time was reached.
- integrate(
- app1: str,
- app2: str,
- *,
- via: str | Iterable[str] | None = None,
Integrate two applications, creating a relation between them.
The order of app1 and app2 is not significant. Each of them should be in the format
<application>[:<endpoint>]
. The endpoint is only required if there’s more than one possible integration between the two applications.To integrate an application in the current model with an application in another model (cross-model), prefix app1 or app2 with
<model>.
. To integrate with an application on another controller, app1 or app2 must be an offer endpoint. Seejuju integrate --help
for details.- Parameters:
app1 – One of the applications (and endpoints) to integrate.
app2 – The other of the applications (and endpoints) to integrate.
via – Inform the offering side (the remote application) of the source of traffic, to enable network ports to be opened. This is in CIDR notation, for example
192.0.2.0/24
.
- model: str | None¶
If not None, operate on this Juju model, otherwise use the current Juju model.
- remove_application(
- *app: str,
- destroy_storage: bool = False,
- force: bool = False,
Remove applications from the model.
- Parameters:
app – Name of the application or applications to remove.
destroy_storage – If True, also destroy storage attached to application units.
force – Force removal even if an application is in an error state.
- remove_relation(app1: str, app2: str, *, force: bool = False) None ¶
Remove an existing relation between two applications (opposite of
integrate()
).The order of app1 and app2 is not significant. Each of them should be in the format
<application>[:<endpoint>]
. The endpoint is only required if there’s more than one possible integration between the two applications.- Parameters:
app1 – One of the applications (and endpoints) to integrate.
app2 – The other of the applications (and endpoints) to integrate.
force – Force removal, ignoring operational errors.
- remove_unit(
- app_or_unit: str | Iterable[str],
- *,
- destroy_storage: bool = False,
- force: bool = False,
- num_units: int = 0,
Remove application units from the model.
Examples:
# Kubernetes model: juju.remove_unit('wordpress', num_units=2) # Machine model: juju.remove_unit('wordpress/1') juju.remove_unit(['wordpress/2', 'wordpress/3'])
- Parameters:
app_or_unit – On machine models, this is the name of the unit or units to remove. On Kubernetes models, this is actually the application name (a single string), as individual units are not named; you must use num_units to remove more than one unit on a Kubernetes model.
destroy_storage – If True, also destroy storage attached to units.
force – Force removal even if a unit is in an error state.
num_units – Number of units to remove (Kubernetes models only).
- run(
- unit: str,
- action: str,
- params: Mapping[str, Any] | None = None,
- *,
- wait: float | None = None,
Run an action on the given unit and wait for the result.
Note: this method does not support running an action on multiple units at once. If you need that, let us know, and we’ll consider adding it with a new
run_multiple
method or similar.Example:
juju = jubilant.Juju() result = juju.run('mysql/0', 'get-password') assert result.results['username'] == 'USER0'
- Parameters:
unit – Name of unit to run the action on, for example
mysql/0
ormysql/leader
.action – Name of action to run.
params – Optional named parameters to pass to the action.
wait – Maximum time to wait for action to finish;
TimeoutError
is raised if this is reached. Default is to wait indefinitely.
- Returns:
The task created to run the action, including logs, failure message, and so on.
- Raises:
ValueError – if the action or the unit doesn’t exist.
TaskError – if the action failed.
TimeoutError – if wait was specified and the wait time was reached.
- trust(
- app: str,
- *,
- remove: bool = False,
- scope: Literal['cluster'] | None = None,
Set the trust status of a deployed application.
- Parameters:
app – Application name to set trust status for.
remove – Set to True to remove trust status.
scope – On Kubernetes models, this must be set to “cluster”, as the trust operation grants the charm full access to the cluster.
- wait(
- ready: Callable[[Status], bool],
- *,
- error: Callable[[Status], bool] | None = None,
- delay: float = 1.0,
- timeout: float | None = None,
- successes: int = 3,
Wait until
ready(status)
returns true.This fetches the Juju status repeatedly (waiting delay seconds between each call), and returns the last status after the ready callable returns true for successes times in a row.
This function logs the status object after the first status call, and after subsequent calls if the status object has changed.
Example:
juju = jubilant.Juju() juju.deploy('snappass-test') juju.wait( lambda status: status.apps['snappass-test'].is_active, error=jubilant.any_error, )
- Parameters:
ready – Callable that takes a
Status
object and returns true when the wait should be considered ready. It needs to return true successes times in a row beforewait
returns.error – Callable that takes a
Status
object and returns true whenwait
should raise an error (WaitError
).delay – Delay in seconds between status calls.
timeout – Overall timeout;
TimeoutError
is raised if this is reached. If not specified, uses the wait_timeout specified when the instance was created.successes – Number of times ready must return true for the wait to succeed.
- Raises:
TimeoutError – If the timeout is reached. A string representation of the last status, if any, is added as an exception note.
WaitError – If the error callable returns True. A string representation of the last status is added as an exception note.
- class jubilant.SecretURI¶
Bases:
str
A string subclass that represents a secret URI (“secret:…”).
- class jubilant.Status(model: ~jubilant.statustypes.ModelStatus, machines: dict[str, ~jubilant.statustypes.MachineStatus], apps: dict[str, ~jubilant.statustypes.AppStatus], app_endpoints: dict[str, ~jubilant.statustypes.RemoteAppStatus] = <factory>, offers: dict[str, ~jubilant.statustypes.OfferStatus] = <factory>, storage: ~jubilant.statustypes.CombinedStorage = <factory>, controller: ~jubilant.statustypes.ControllerStatus = <factory>)¶
Bases:
object
Parsed version of the status object returned by
juju status --format=json
.- app_endpoints: dict[str, RemoteAppStatus]¶
- controller: ControllerStatus¶
- machines: dict[str, MachineStatus]¶
- model: ModelStatus¶
- offers: dict[str, OfferStatus]¶
- storage: CombinedStorage¶
- class jubilant.Task(id: str, status: ~typing.Literal['aborted', 'cancelled', 'completed', 'error', 'failed'], results: dict[str, ~typing.Any] = <factory>, return_code: int = 0, stdout: str = '', stderr: str = '', message: str = '', log: list[str] = <factory>)¶
Bases:
object
A task holds the results of Juju running an action or exec command on a single unit.
- id: str¶
Task ID of the action, for use with
juju show-task
.
- log: list[str]¶
List of messages logged by the action hook.
- message: str = ''¶
Failure message, if the charm provided a message when it failed the action.
- results: dict[str, Any]¶
Results of the action provided by the charm.
This excludes the special “return-code”, “stdout”, and “stderr” keys inserted by Juju; those values are provided by separate attributes.
- return_code: int = 0¶
Return code from executing the charm action hook.
- status: Literal['aborted', 'cancelled', 'completed', 'error', 'failed']¶
Status of the action (Juju operation). Typically “completed” or “failed”.
- stderr: str = ''¶
Stderr printed by the action hook.
- stdout: str = ''¶
Stdout printed by the action hook.
- property success: bool¶
Whether the action was successful.
- exception jubilant.TaskError(task: Task)¶
Bases:
Exception
Exception raised when an action or exec command fails.
- exception jubilant.WaitError¶
Bases:
Exception
Raised when
Juju.wait()
’s error callable returns False.
- jubilant.all_active(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether all applications or units in status are in “active” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.all_blocked(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether all applications or units in status are in “blocked” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.all_error(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether all applications or units in status are in “error” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.all_maintenance(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether all applications or units in status are in “maintenance” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.all_waiting(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether all applications or units in status are in “waiting” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.any_active(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether any application or unit in status is in “active” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.any_blocked(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether any application or unit in status is in “blocked” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.any_error(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether any application or unit in status is in “error” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.any_maintenance(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether any application or unit in status is in “maintenance” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.any_waiting(
- status: Status,
- apps: Iterable[str] | None = None,
Report whether any application or unit in status is in “waiting” status.
- Parameters:
status – The status object being tested.
apps – An optional list of application names. If provided, only these applications (and their units) are tested.
- jubilant.temp_model(
- keep: bool = False,
Context manager to create a temporary model for running tests in.
This creates a new model with a random name in the format
jubilant-abcd1234
, and destroys it and its storage when the context manager exits.Provides a
Juju
instance to operate on.- Parameters:
keep – If true, keep the created model around when the context manager exits.