Welcome to The Operator Framework’s documentation!¶
ops package¶
The Charmed Operator Framework.
The Charmed Operator Framework allows the development of operators in a simple and straightforward way, using standard Python structures to allow for clean, maintainable, and reusable code.
A Kubernetes operator is a container that drives lifecycle management, configuration, integration and daily actions for an application. Operators simplify software management and operations. They capture reusable app domain knowledge from experts in a software component that can be shared.
The Charmed Operator Framework extends the “operator pattern” to enable Charmed Operators, packaged as and often referred to as “charms”. Charms are not just for Kubernetes but also operators for traditional Linux application management. Operators use an Operator Lifecycle Manager (OLM), like Juju, to coordinate their work in a cluster. The system uses Golang for concurrent event processing under the hood, but enables the operators to be written in Python.
Operators should do one thing and do it well. Each operator drives a single application or service and can be composed with other operators to deliver a complex application or service. An operator handles instantiation, scaling, configuration, optimisation, networking, service mesh, observability, and day-2 operations specific to that application.
Full developer documentation is available at https://juju.is/docs/sdk.
Submodules¶
ops.charm module¶
Base objects for the Charm, events and metadata.
-
class
ops.charm.
HookEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.framework.EventBase
Events raised by Juju to progress a charm’s lifecycle.
Hooks are callback methods of a charm class (a subclass of
CharmBase
) that are invoked in response to events raised by Juju. These callback methods are the means by which a charm governs the lifecycle of its application.The
HookEvent
class is the base of a type hierarchy of events related to the charm’s lifecycle.HookEvent
subtypes are grouped into the following categories- Core lifecycle events
- Relation events
- Storage events
- Metric events
-
class
ops.charm.
ActionEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.framework.EventBase
Events raised by Juju when an administrator invokes a Juju Action.
This class is the data type of events triggered when an administrator invokes a Juju Action. Callbacks bound to these events may be used for responding to the administrator’s Juju Action request.
To read the parameters for the action, see the instance variable
params
. To respond with the result of the action, callset_results()
. To add progress messages that are visible as the action is progressing uselog()
.-
params
¶ The parameters passed to the action.
-
defer
()[source]¶ Action events are not deferable like other events.
This is because an action runs synchronously and the administrator is waiting for the result.
-
restore
(snapshot: JsonObject)[source]¶ Used by the operator framework to record the action.
Not meant to be called directly by charm code.
-
set_results
(results: _SerializedData)[source]¶ Report the result of the action.
Parameters: - results – The result of the action as a Dict
- eventually only accepts a str (Juju) – str mapping, so we will attempt
- flatten any more complex data structure like so (to) –
- {'a' (>>>) – ‘b’} # becomes: ‘a’=’b’
- {'a' – {‘b’: ‘c’}} # becomes: ‘a.b’=’c’
- {'a' – {‘b’: ‘c’, ‘d’: ‘e’}} # becomes: ‘a.b’=’c’, ‘a.d’ = ‘e’
- {'a.b' (>>>) – ‘c’, ‘a.d’: ‘e’} # equivalent to previous
- that duplicate keys are not allowed, so (Note) –
- {'a' – {‘b’: ‘c’}, ‘a.b’: ‘c’} # invalid!
- that the resulting keys must start and end with lowercase (Note) –
- and can only contain lowercase alphanumeric, hyphens (alphanumeric,) –
- periods. (and) –
- any exceptions occur whilst the action is being handled, juju will (If) –
- any stdout/stderr data (gather) –
- object. Thus, the results object might contain the following keys, (results) –
- to those specified by the charm code (additionally) –
- Stdout
- Stderr
- Stdout-encoding
- Stderr-encoding
- ReturnCode
-
-
class
ops.charm.
InstallEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered when a charm is installed.
This event is triggered at the beginning of a charm’s lifecycle. Any associated callback method should be used to perform one-time setup operations, such as installing prerequisite software.
-
class
ops.charm.
StartEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered immediately after first configuration change.
This event is triggered immediately after the first
ConfigChangedEvent
. Callback methods bound to the event should be used to ensure that the charm’s software is in a running state. Note that the charm’s software should be configured so as to persist in this state through reboots without further intervention on Juju’s part.
-
class
ops.charm.
StopEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered when a charm is shut down.
This event is triggered when an application’s removal is requested by the client. The event fires immediately before the end of the unit’s destruction sequence. Callback methods bound to this event should be used to ensure that the charm’s software is not running, and that it will not start again on reboot.
-
class
ops.charm.
RemoveEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered when a unit is about to be terminated.
This event fires prior to Juju removing the charm and terminating its unit.
-
class
ops.charm.
ConfigChangedEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered when a configuration change occurs.
This event can fire in several situations:
- Right after the unit starts up for the first time.
This event notifies the charm of its initial configuration.
Typically, this event will fire between a
install
and astarts
during the startup sequence (when you first deploy a unit), but more in general it will fire whenever the unit is (re)started, e.g. after pod churn on kubernetes, on unit rescheduling, on unit upgrade/refresh, etc… - As a specific instance of the above point: when networking changes (if the machine reboots and comes up with a different IP).
- When the cloud admin reconfigures the charm via the Juju CLI, i.e. juju config my-charm foo=bar. This event notifies the charm of its new configuration. (The event itself, however, is not aware of what specifically has changed in the config).
Any callback method bound to this event cannot assume that the software has already been started; it should not start stopped software, but should (if appropriate) restart running software to take configuration changes into account.
- Right after the unit starts up for the first time.
This event notifies the charm of its initial configuration.
Typically, this event will fire between a
-
class
ops.charm.
UpdateStatusEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered by a status update request from Juju.
This event is periodically triggered by Juju so that it can provide constant feedback to the administrator about the status of the application the charm is modeling. Any callback method bound to this event should determine the “health” of the application and set the status appropriately.
The interval between
update-status
events can be configured model-wide, e.g.juju model-config update-status-hook-interval=1m
.
-
class
ops.charm.
UpgradeCharmEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered by request to upgrade the charm.
This event will be triggered when an administrator executes
juju upgrade-charm
. The event fires after Juju has unpacked the upgraded charm code, and so this event will be handled by the callback method bound to the event in the new codebase. The associated callback method is invoked provided there is no existing error state. The callback method should be used to reconcile current state written by an older version of the charm into whatever form that is needed by the current charm version.
-
class
ops.charm.
PreSeriesUpgradeEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered to prepare a unit for series upgrade.
This event triggers when an administrator executes
juju upgrade-series MACHINE prepare
. The event will fire for each unit that is running on the specified machine. Any callback method bound to this event must prepare the charm for an upgrade to the series. This may include things like exporting database content to a version neutral format, or evacuating running instances to other machines.It can be assumed that only after all units on a machine have executed the callback method associated with this event, the administrator will initiate steps to actually upgrade the series. After the upgrade has been completed, the
PostSeriesUpgradeEvent
will fire.
-
class
ops.charm.
PostSeriesUpgradeEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered after a series upgrade.
This event is triggered after the administrator has done a distribution upgrade (or rolled back and kept the same series). It is called in response to
juju upgrade-series MACHINE complete
. Associated charm callback methods are expected to do whatever steps are necessary to reconfigure their applications for the new series. This may include things like populating the upgraded version of a database. Note however charms are expected to check if the series has actually changed or whether it was rolled back to the original series.
-
class
ops.charm.
LeaderElectedEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered when a new leader has been elected.
Juju will trigger this event when a new leader unit is chosen for a given application.
This event fires at least once after Juju selects a leader unit. Callback methods bound to this event may take any action required for the elected unit to assert leadership. Note that only the elected leader unit will receive this event.
-
class
ops.charm.
LeaderSettingsChangedEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered when leader changes any settings.
DEPRECATED NOTICE
This event has been deprecated in favor of using a Peer relation, and having the leader set a value in the Application data bag for that peer relation. (see
RelationChangedEvent
).
-
class
ops.charm.
CollectMetricsEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.charm.HookEvent
Event triggered by Juju to collect metrics.
Juju fires this event every five minutes for the lifetime of the unit. Callback methods bound to this event may use the
add_metrics()
method of this class to send measurements to Juju.Note that associated callback methods are currently sandboxed in how they can interact with Juju.
-
add_metrics
(metrics: Mapping[str, Numerical], labels: Optional[Mapping[str, str]] = None)[source]¶ Record metrics that have been gathered by the charm for this unit.
Parameters: - metrics – A collection of {key: float} pairs that contains the metrics that have been gathered
- labels – {key:value} strings that can be applied to the metrics that are being gathered
-
-
class
ops.charm.
RelationEvent
(handle: Handle, relation: Relation, app: Optional[ops.model.Application] = None, unit: Optional[ops.model.Unit] = None)[source]¶ Bases:
ops.charm.HookEvent
A base class representing the various relation lifecycle events.
Relation lifecycle events are generated when application units participate in relations. Units can only participate in relations after they have been “started”, and before they have been “stopped”. Within that time window, the unit may participate in several different relations at a time, including multiple relations with the same name.
-
app
¶ The remote
Application
that has triggered this event
-
unit
¶ The remote
Unit
that has triggered this event. This may beNone
if the relation event was triggered as anApplication
level event
-
-
class
ops.charm.
RelationCreatedEvent
(handle: Handle, relation: Relation, app: Optional[ops.model.Application] = None, unit: Optional[ops.model.Unit] = None)[source]¶ Bases:
ops.charm.RelationEvent
Event triggered when a new relation is created.
This is triggered when a new relation to another app is added in Juju. This can occur before units for those applications have started. All existing relations should be established before start.
-
class
ops.charm.
RelationJoinedEvent
(handle: Handle, relation: Relation, app: Optional[ops.model.Application] = None, unit: Optional[ops.model.Unit] = None)[source]¶ Bases:
ops.charm.RelationEvent
Event triggered when a new unit joins a relation.
This event is triggered whenever a new unit of a related application joins the relation. The event fires only when that remote unit is first observed by the unit. Callback methods bound to this event may set any local unit settings that can be determined using no more than the name of the joining unit and the remote
private-address
setting, which is always available when the relation is created and is by convention not deleted.
-
class
ops.charm.
RelationChangedEvent
(handle: Handle, relation: Relation, app: Optional[ops.model.Application] = None, unit: Optional[ops.model.Unit] = None)[source]¶ Bases:
ops.charm.RelationEvent
Event triggered when relation data changes.
This event is triggered whenever there is a change to the data bucket for a related application or unit. Look at
event.relation.data[event.unit/app]
to see the new information, whereevent
is the event object passed to the callback method bound to this event.This event always fires once, after
RelationJoinedEvent
, and will subsequently fire whenever that remote unit changes its settings for the relation. Callback methods bound to this event should be the only ones that rely on remote relation settings. They should not error if the settings are incomplete, since it can be guaranteed that when the remote unit or application changes its settings, the event will fire again.The settings that may be queried, or set, are determined by the relation’s interface.
-
class
ops.charm.
RelationDepartedEvent
(handle: Handle, relation: Relation, app: Optional[ops.model.Application] = None, unit: Optional[ops.model.Unit] = None, departing_unit_name: Optional[str] = None)[source]¶ Bases:
ops.charm.RelationEvent
Event triggered when a unit leaves a relation.
This is the inverse of the
RelationJoinedEvent
, representing when a unit is leaving the relation (the unit is being removed, the app is being removed, the relation is being removed). For remaining units, this event is emitted once for each departing unit. For departing units, this event is emitted once for each remaining unit.Callback methods bound to this event may be used to remove all references to the departing remote unit, because there’s no guarantee that it’s still part of the system; it’s perfectly probable (although not guaranteed) that the system running that unit has already shut down.
Once all callback methods bound to this event have been run for such a relation, the unit agent will fire the
RelationBrokenEvent
.-
departing_unit
¶ The
Unit
that is departing. This can facilitate determining e.g. whether you are the departing unit.
-
snapshot
() → _RelationDepartedEventSnapshot[source]¶ Used by the framework to serialize the event to disk.
Not meant to be called by charm code.
-
departing_unit
The ops.model.Unit that is departing, if any.
-
-
class
ops.charm.
RelationBrokenEvent
(handle: Handle, relation: Relation, app: Optional[ops.model.Application] = None, unit: Optional[ops.model.Unit] = None)[source]¶ Bases:
ops.charm.RelationEvent
Event triggered when a relation is removed.
If a relation is being removed (
juju remove-relation
orjuju remove-application
), once all the units have been removed, this event will fire to signal that the relationship has been fully terminated.The event indicates that the current relation is no longer valid, and that the charm’s software must be configured as though the relation had never existed. It will only be called after every callback method bound to
RelationDepartedEvent
has been run. If a callback method bound to this event is being executed, it is guaranteed that no remote units are currently known locally.
-
class
ops.charm.
StorageEvent
(handle: Handle, storage: Storage)[source]¶ Bases:
ops.charm.HookEvent
Base class representing storage-related events.
Juju can provide a variety of storage types to a charms. The charms can define several different types of storage that are allocated from Juju. Changes in state of storage trigger sub-types of
StorageEvent
.
-
class
ops.charm.
StorageAttachedEvent
(handle: Handle, storage: Storage)[source]¶ Bases:
ops.charm.StorageEvent
Event triggered when new storage becomes available.
This event is triggered when new storage is available for the charm to use.
Callback methods bound to this event allow the charm to run code when storage has been added. Such methods will be run before the
InstallEvent
fires, so that the installation routine may use the storage. The name prefix of this hook will depend on the storage key defined in themetadata.yaml
file.
-
class
ops.charm.
StorageDetachingEvent
(handle: Handle, storage: Storage)[source]¶ Bases:
ops.charm.StorageEvent
Event triggered prior to removal of storage.
This event is triggered when storage a charm has been using is going away.
Callback methods bound to this event allow the charm to run code before storage is removed. Such methods will be run before storage is detached, and always before the
StopEvent
fires, thereby allowing the charm to gracefully release resources before they are removed and before the unit terminates. The name prefix of the hook will depend on the storage key defined in themetadata.yaml
file.
-
class
ops.charm.
WorkloadEvent
(handle: Handle, workload: Container)[source]¶ Bases:
ops.charm.HookEvent
Base class representing workload-related events.
Workload events are generated for all containers that the charm expects in metadata. Workload containers currently only trigger a PebbleReadyEvent.
-
workload
¶ The
Container
involved in this event. Workload currently only can be a Container but in future may be other types that represent the specific workload type e.g. a Machine.
-
-
class
ops.charm.
PebbleReadyEvent
(handle: Handle, workload: Container)[source]¶ Bases:
ops.charm.WorkloadEvent
Event triggered when pebble is ready for a workload.
This event is triggered when the Pebble process for a workload/container starts up, allowing the charm to configure how services should be launched.
Callback methods bound to this event allow the charm to run code after a workload has started its Pebble instance and is ready to receive instructions regarding what services should be started. The name prefix of the hook will depend on the container key defined in the
metadata.yaml
file.
-
class
ops.charm.
SecretEvent
(handle: Handle, id: str, label: Optional[str])[source]¶ Bases:
ops.charm.HookEvent
Base class for all secret events.
-
secret
¶ The secret instance this event refers to.
-
-
class
ops.charm.
SecretChangedEvent
(handle: Handle, id: str, label: Optional[str])[source]¶ Bases:
ops.charm.SecretEvent
Event raised by Juju on the observer when the secret owner changes its contents.
When the owner of a secret changes the secret’s contents, Juju will create a new secret revision, and all applications or units that are tracking this secret will be notified via this event that a new revision is available.
Typically, you will want to fetch the new content by calling
ops.model.Secret.get_content()
withrefresh=True
to tell Juju to start tracking the new revision.
-
class
ops.charm.
SecretRotateEvent
(handle: Handle, id: str, label: Optional[str])[source]¶ Bases:
ops.charm.SecretEvent
Event raised by Juju on the owner when the secret’s rotation policy elapses.
This event is fired on the secret owner to inform it that the secret must be rotated. The event will keep firing until the owner creates a new revision by calling
ops.model.Secret.set_content()
.
-
class
ops.charm.
SecretRemoveEvent
(handle: Handle, id: str, label: Optional[str], revision: int)[source]¶ Bases:
ops.charm.SecretEvent
Event raised by Juju on the owner when a secret revision can be removed.
When the owner of a secret creates a new revision, and after all observers have updated to that new revision, this event will be fired to inform the secret owner that the old revision can be removed.
Typically, you will want to call
ops.model.Secret.remove_revision()
to remove the now-unused revision.-
revision
¶ The secret revision this event refers to.
-
-
class
ops.charm.
SecretExpiredEvent
(handle: Handle, id: str, label: Optional[str], revision: int)[source]¶ Bases:
ops.charm.SecretEvent
Event raised by Juju on the owner when a secret’s expiration time elapses.
This event is fired on the secret owner to inform it that the secret revision must be removed. The event will keep firing until the owner removes the revision by calling
model.Secret.remove_revision()
.-
revision
¶ The secret revision this event refers to.
-
snapshot
() → _SerializedData[source]¶ Used by the framework to serialize the event to disk.
Not meant to be called by charm code.
-
-
class
ops.charm.
CharmEvents
(parent: Optional[ops.framework.Object] = None, key: Optional[str] = None)[source]¶ Bases:
ops.framework.ObjectEvents
Events generated by Juju pertaining to application lifecycle.
This class is used to create an event descriptor (
self.on
) attribute for a charm class that inherits fromCharmBase
. The event descriptor may be used to set up event handlers for corresponding events.By default the following events will be provided through
CharmBase
:self.on.install self.on.start self.on.remove self.on.update_status self.on.config_changed self.on.upgrade_charm self.on.pre_series_upgrade self.on.post_series_upgrade self.on.leader_elected self.on.collect_metrics
In addition to these, depending on the charm’s metadata (
metadata.yaml
), named relation and storage events may also be defined. These named events are created byCharmBase
using charm metadata. The named events may be accessed asself.on[<name>].<relation_or_storage_event>
-
install
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
start
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
stop
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
remove
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
update_status
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
config_changed
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
upgrade_charm
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
pre_series_upgrade
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
post_series_upgrade
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
leader_elected
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
leader_settings_changed
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
collect_metrics
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
secret_changed
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
secret_expired
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
secret_rotate
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
secret_remove
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
-
class
ops.charm.
CharmBase
(framework: ops.framework.Framework)[source]¶ Bases:
ops.framework.Object
Base class that represents the charm overall.
CharmBase
is used to create a charm. This is done by inheriting fromCharmBase
and customising the sub class as required. So to create your own charm, sayMyCharm
, define a charm class and set up the required event handlers (“hooks”) in its constructor:import logging from ops.charm import CharmBase from ops.main import main logger = logging.getLogger(__name__) def MyCharm(CharmBase): def __init__(self, *args): logger.debug('Initializing Charm') super().__init__(*args) self.framework.observe(self.on.config_changed, self._on_config_changed) self.framework.observe(self.on.stop, self._on_stop) # ... if __name__ == "__main__": main(MyCharm)
As shown in the example above, a charm class is instantiated by
main()
rather than charm authors directly instantiating a charm.Parameters: framework – The framework responsible for managing the Model and events for this charm. -
on
[source]¶ Events generated by Juju pertaining to application lifecycle.
This class is used to create an event descriptor (
self.on
) attribute for a charm class that inherits fromCharmBase
. The event descriptor may be used to set up event handlers for corresponding events.By default the following events will be provided through
CharmBase
:self.on.install self.on.start self.on.remove self.on.update_status self.on.config_changed self.on.upgrade_charm self.on.pre_series_upgrade self.on.post_series_upgrade self.on.leader_elected self.on.collect_metrics
In addition to these, depending on the charm’s metadata (
metadata.yaml
), named relation and storage events may also be defined. These named events are created byCharmBase
using charm metadata. The named events may be accessed asself.on[<name>].<relation_or_storage_event>
-
app
¶ Application that this unit is part of.
-
unit
¶ Unit that this execution is responsible for.
-
meta
¶ Metadata of this charm.
-
charm_dir
¶ Root directory of the charm as it is running.
-
config
¶ A mapping containing the charm’s config and current values.
-
-
class
ops.charm.
CharmMeta
(raw: Optional[_CharmMetaDict] = None, actions_raw: _ActionsRaw = None)[source]¶ Bases:
object
Object containing the metadata for the charm.
This is read from
metadata.yaml
and/oractions.yaml
. Generally charms will define this information, rather than reading it at runtime. This class is mostly for the framework to understand what the charm has defined.The
maintainers
,tags
,terms
,series
, andextra_bindings
attributes are all lists of strings. Thecontainers
,requires
,provides
,peers
,relations
,storages
,resources
, andpayloads
attributes are all mappings of names to instances of the respectiveRelationMeta
,StorageMeta
,ResourceMeta
, orPayloadMeta
.The
relations
attribute is a convenience accessor which includes all of therequires
,provides
, andpeers
RelationMeta
items. If needed, the role of the relation definition can be obtained from itsrole
attribute.-
name
¶ The name of this charm
-
summary
¶ Short description of what this charm does
-
description
¶ Long description for this charm
-
maintainers
¶ A list of strings of the email addresses of the maintainers of this charm.
Charm store tag metadata for categories associated with this charm.
-
terms
¶ Charm store terms that should be agreed to before this charm can be deployed. (Used for things like licensing issues.)
-
series
¶ The list of supported OS series that this charm can support. The first entry in the list is the default series that will be used by deploy if no other series is requested by the user.
-
subordinate
¶ True/False whether this charm is intended to be used as a subordinate charm.
-
min_juju_version
¶ If supplied, indicates this charm needs features that are not available in older versions of Juju.
-
containers
¶ A dict of {name:
ContainerMeta
} for each of the ‘containers’ declared by this charm in the matadata.yaml file.
-
requires
¶ A dict of {name:
RelationMeta
} for each ‘requires’ relation.
-
provides
¶ A dict of {name:
RelationMeta
} for each ‘provides’ relation.
-
peers
¶ A dict of {name:
RelationMeta
} for each ‘peer’ relation.
-
relations
¶ A dict containing all
RelationMeta
attributes (merged from other sections)
-
storages
¶ A dict of {name:
StorageMeta
} for each defined storage.
-
resources
¶ A dict of {name:
ResourceMeta
} for each defined resource.
-
payloads
¶ A dict of {name:
PayloadMeta
} for each defined payload.
-
extra_bindings
¶ A dict of additional named bindings that a charm can use for network configuration.
-
actions
¶ A dict of {name:
ActionMeta
} for actions that the charm has defined.
Parameters: - raw – a mapping containing the contents of metadata.yaml
- actions_raw – a mapping containing the contents of actions.yaml
-
classmethod
from_yaml
(metadata: Union[str, TextIO], actions: Union[str, TextIO, None] = None)[source]¶ Instantiate a CharmMeta from a YAML description of metadata.yaml.
Parameters: - metadata – A YAML description of charm metadata (name, relations, etc.) This can be a simple string, or a file-like object. (passed to yaml.safe_load).
- actions – YAML description of Actions for this charm (eg actions.yaml)
-
-
class
ops.charm.
RelationRole
[source]¶ Bases:
enum.Enum
An annotation for a charm’s role in a relation.
For each relation a charm’s role may be
- A Peer
- A service consumer in the relation (‘requires’)
- A service provider in the relation (‘provides’)
-
peer
= 'peer'¶
-
requires
= 'requires'¶
-
provides
= 'provides'¶
-
class
ops.charm.
RelationMeta
(role: ops.charm.RelationRole, relation_name: str, raw: _RelationMetaDict)[source]¶ Bases:
object
Object containing metadata about a relation definition.
Should not be constructed directly by charm code. Is gotten from one of
CharmMeta.peers
,CharmMeta.requires
,CharmMeta.provides
, orCharmMeta.relations
.-
role
¶ This is
RelationRole
; one of peer/requires/provides
-
relation_name
¶ Name of this relation from metadata.yaml
-
interface_name
¶ Optional definition of the interface protocol.
-
limit
¶ Optional definition of maximum number of connections to this relation endpoint.
-
scope
¶ “global” (default) or “container” scope based on how the relation should be used.
-
VALID_SCOPES
= ['global', 'container']¶
-
-
class
ops.charm.
StorageMeta
(name: str, raw: _StorageMetaDict)[source]¶ Bases:
object
Object containing metadata about a storage definition.
-
storage_name
¶ Name of storage
-
type
¶ Storage type
-
description
¶ A text description of the storage
-
read_only
¶ Whether or not the storage is read only
-
minimum_size
¶ Minimum size of storage
-
location
¶ Mount point of storage
-
multiple_range
¶ Range of numeric qualifiers when multiple storage units are used
-
-
class
ops.charm.
ResourceMeta
(name: str, raw: _ResourceMetaDict)[source]¶ Bases:
object
Object containing metadata about a resource definition.
-
resource_name
¶ Name of resource
-
filename
¶ Name of file
-
description
¶ A text description of resource
-
-
class
ops.charm.
PayloadMeta
(name: str, raw: _PayloadMetaDict)[source]¶ Bases:
object
Object containing metadata about a payload definition.
-
payload_name
¶ Name of payload
-
type
¶ Payload type
-
-
class
ops.charm.
ActionMeta
(name: str, raw: Optional[_ActionMetaDict] = None)[source]¶ Bases:
object
Object containing metadata about an action’s definition.
-
class
ops.charm.
ContainerMeta
(name: str, raw: _ContainerMetaDict)[source]¶ Bases:
object
Metadata about an individual container.
NOTE: this is extremely lightweight right now, and just includes the fields we need for Pebble interaction.
-
name
¶ Name of container (key in the YAML)
-
mounts
¶ ContainerStorageMeta
mounts available to the container
-
mounts
An accessor for the mounts in a container.
Dict keys match key name in
StorageMeta
Example:
storage: foo: type: filesystem location: /test containers: bar: mounts: - storage: foo - location: /test/mount
-
-
class
ops.charm.
ContainerStorageMeta
(storage: str, location: str)[source]¶ Bases:
object
Metadata about storage for an individual container.
-
storage
¶ a name for the mountpoint, which should exist the keys for
StorageMeta
for the charm
-
location
¶ the location storage is mounted at
-
locations
¶ a list of mountpoints for the key
If multiple locations are specified for the same storage, such as Kubernetes subPath mounts, location will not be an accessible attribute, as it would not be possible to determine which mount point was desired, and locations should be iterated over.
-
locations
An accessor for the list of locations for a mount.
-
ops.framework module¶
The Operator Framework infrastructure.
-
class
ops.framework.
Handle
(parent: Union[Handle, Object, None], kind: str, key: str)[source]¶ Bases:
object
Handle defines a name for an object in the form of a hierarchical path.
The provided parent is the object (or that object’s handle) that this handle sits under, or None if the object identified by this handle stands by itself as the root of its own hierarchy.
The handle kind is a string that defines a namespace so objects with the same parent and kind will have unique keys.
The handle key is a string uniquely identifying the object. No other objects under the same parent and kind may have the same key.
-
nest
(kind: str, key: str) → ops.framework.Handle[source]¶ Create a new handle as child of the current one.
-
parent
¶ Return own parent handle.
-
kind
¶ Return the handle’s kind.
-
key
¶ Return the handle’s key.
-
path
¶ Return the handle’s path.
-
-
class
ops.framework.
EventBase
(handle: ops.framework.Handle)[source]¶ Bases:
object
The base for all the different Events.
Inherit this and override ‘snapshot’ and ‘restore’ methods to build a custom event.
-
framework
= None¶
-
defer
()[source]¶ Defer the event to the future.
Deferring an event from a handler puts that handler into a queue, to be called again the next time the charm is invoked. This invocation may be the result of an action, or any event other than metric events. The queue of events will be dispatched before the new event is processed.
From the above you may deduce, but it’s important to point out:
defer()
does not interrupt the execution of the current event handler. In almost all cases, a call todefer()
should be followed by an explicitreturn
from the handler;- the re-execution of the deferred event handler starts from the top of the handler method (not where defer was called);
- only the handlers that actually called
defer()
are called again (that is: despite talking about “deferring an event” it is actually the handler/event combination that is deferred); and - any deferred events get processed before the event (or action) that caused the current invocation of the charm.
The general desire to call
defer()
happens when some precondition isn’t yet met. However, care should be exercised as to whether it is better to defer this event so that you see it again, or whether it is better to just wait for the event that indicates the precondition has been met.For example, if
config-changed
is fired, and you are waiting for different config, there is no reason to defer the event because there will be a differentconfig-changed
event when the config actually changes, rather than checking to see if maybe config has changed prior to every other event that occurs.Similarly, if you need 2 events to occur before you are ready to proceed (say event A and B). When you see event A, you could chose to
defer()
it because you haven’t seen B yet. However, that leads to:- event A fires, calls defer()
- event B fires, event A handler is called first, still hasn’t seen B happen, so is deferred again. Then B happens, which progresses since it has seen A.
- At some future time, event C happens, which also checks if A can proceed.
-
-
class
ops.framework.
EventSource
(event_type: Type[_EventType])[source]¶ Bases:
typing.Generic
EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
class
ops.framework.
BoundEvent
(emitter: Object, event_type: Type[EventBase], event_kind: str)[source]¶ Bases:
typing.Generic
Event bound to an Object.
-
class
ops.framework.
HandleKind
[source]¶ Bases:
object
Helper descriptor to define the Object.handle_kind field.
The handle_kind for an object defaults to its type name, but it may be explicitly overridden if desired.
-
class
ops.framework.
Object
(parent: Union[Framework, Object], key: Optional[str])[source]¶ Bases:
object
Initialize an Object as a new leaf in
Framework
, identified by key.Parameters: - parent – parent node in the tree.
- key – unique identifier for this object.
Every object belongs to exactly one framework.
Every object has a parent, which might be a framework.
We track a “path to object,” which is the path to the parent, plus the object’s unique identifier. Event handlers use this identity to track the destination of their events, and the Framework uses this id to track persisted state between event executions.
The Framework should raise an error if it ever detects that two objects with the same id have been created.
-
handle_kind
¶ Helper descriptor to define the Object.handle_kind field.
The handle_kind for an object defaults to its type name, but it may be explicitly overridden if desired.
-
model
¶ Shortcut for more simple access the model.
-
class
ops.framework.
ObjectEvents
(parent: Optional[ops.framework.Object] = None, key: Optional[str] = None)[source]¶ Bases:
ops.framework.Object
Convenience type to allow defining .on attributes at class level.
-
handle_kind
= 'on'¶
-
classmethod
define_event
(event_kind: str, event_type: Type[EventBase])[source]¶ Define an event on this type at runtime.
cls: a type to define an event on.
- event_kind: an attribute name that will be used to access the
- event. Must be a valid python identifier, not be a keyword or an existing attribute.
event_type: a type of the event to define.
Note that attempting to define the same event kind more than once will raise a ‘overlaps with existing type’ runtime error. Ops uses a labeling system to track and reconstruct events between hook executions (each time a hook runs, the Juju Agent invokes a fresh instance of ops; there is no ops process that persists on the host between hooks). Having duplicate Python objects creates duplicate labels. Overwriting a previously created label means that only the latter code path will be run when the current event, if it does get deferred, is reemitted. This is usually not what is desired, and is error-prone and ambigous.
-
-
class
ops.framework.
PrefixedEvents
(emitter: ops.framework.Object, key: str)[source]¶ Bases:
object
Events to be found in all events using a specific prefix.
-
class
ops.framework.
LifecycleEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.framework.EventBase
Events tied to the lifecycle of the Framework object.
-
class
ops.framework.
PreCommitEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.framework.LifecycleEvent
Events that will be emitted first on commit.
-
class
ops.framework.
CommitEvent
(handle: ops.framework.Handle)[source]¶ Bases:
ops.framework.LifecycleEvent
Events that will be emitted second on commit.
-
class
ops.framework.
FrameworkEvents
(parent: Optional[ops.framework.Object] = None, key: Optional[str] = None)[source]¶ Bases:
ops.framework.ObjectEvents
Manager of all framework events.
-
pre_commit
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
commit
¶ EventSource wraps an event type with a descriptor to facilitate observing and emitting.
It is generally used as:
- class SomethingHappened(EventBase):
- pass
- class SomeObject(Object):
- something_happened = EventSource(SomethingHappened)
With that, instances of that type will offer the someobj.something_happened attribute which is a BoundEvent and may be used to emit and observe the event.
-
-
exception
ops.framework.
NoTypeError
(handle_path: str)[source]¶ Bases:
Exception
No class to hold it was found when restoring an event.
-
class
ops.framework.
Framework
(storage: Union[ops.storage.SQLiteStorage, ops.storage.JujuStorage], charm_dir: Union[str, pathlib.Path], meta: CharmMeta, model: Model, event_name: Optional[str] = None)[source]¶ Bases:
ops.framework.Object
Main interface from the Charm to the Operator Framework internals.
-
charm_dir
= None¶
-
meta
= None¶
-
model
= None¶
-
set_breakpointhook
()[source]¶ Hook into sys.breakpointhook so the builtin breakpoint() works as expected.
This method is called by
main
, and is not intended to be called by users of the framework itself outside of perhaps some testing scenarios.It returns the old value of sys.excepthook.
The breakpoint function is a Python >= 3.7 feature.
This method was added in ops 1.0; before that, it was done as part of the Framework’s __init__.
-
register_type
(cls: Type[_Serializable], parent: Optional[_ParentHandle], kind: str = None)[source]¶ Register a type to a handle.
-
save_snapshot
(value: Union[StoredStateData, EventBase])[source]¶ Save a persistent snapshot of the provided value.
-
observe
(bound_event: ops.framework.BoundEvent[typing.Any][Any], observer: _ObserverCallback)[source]¶ Register observer to be called when bound_event is emitted.
The bound_event is generally provided as an attribute of the object that emits the event, and is created in this style:
class SomeObject: something_happened = Event(SomethingHappened)
That event may be observed as:
framework.observe(someobj.something_happened, self._on_something_happened)
Raises: RuntimeError
– if bound_event or observer are the wrong type.
-
reemit
()[source]¶ Reemit previously deferred events to the observers that deferred them.
Only the specific observers that have previously deferred the event will be notified again. Observers that asked to be notified about events after it’s been first emitted won’t be notified, as that would mean potentially observing events out of order.
-
breakpoint
(name: Optional[str] = None)[source]¶ Add breakpoint, optionally named, at the place where this method is called.
For the breakpoint to be activated the JUJU_DEBUG_AT environment variable must be set to “all” or to the specific name parameter provided, if any. In every other situation calling this method does nothing.
The framework also provides a standard breakpoint named “hook”, that will stop execution when a hook event is about to be handled.
For those reasons, the “all” and “hook” breakpoint names are reserved.
-
-
class
ops.framework.
StoredStateData
(parent: ops.framework.Object, attr_name: str)[source]¶ Bases:
ops.framework.Object
Manager of the stored data.
-
class
ops.framework.
BoundStoredState
(parent: ops.framework.Object, attr_name: str)[source]¶ Bases:
object
Stored state data bound to a specific Object.
-
class
ops.framework.
StoredState
[source]¶ Bases:
object
A class used to store data the charm needs persisted across invocations.
Example:
class MyClass(Object): _stored = StoredState()
Instances of MyClass can transparently save state between invocations by setting attributes on _stored. Initial state should be set with set_default on the bound object, that is:
class MyClass(Object): _stored = StoredState() def __init__(self, parent, key): super().__init__(parent, key) self._stored.set_default(seen=set()) self.framework.observe(self.on.seen, self._on_seen) def _on_seen(self, event): self._stored.seen.add(event.uuid)
-
class
ops.framework.
StoredDict
(stored_data: ops.framework.StoredStateData, under: Dict[Any, Any])[source]¶ Bases:
collections.abc.MutableMapping
,typing.Generic
A dict-like object that uses the StoredState as backend.
-
class
ops.framework.
StoredList
(stored_data: ops.framework.StoredStateData, under: List[Any])[source]¶ Bases:
collections.abc.MutableSequence
,typing.Generic
A list-like object that uses the StoredState as backend.
-
class
ops.framework.
StoredSet
(stored_data: ops.framework.StoredStateData, under: Set[Any])[source]¶ Bases:
collections.abc.MutableSet
,typing.Generic
A set-like object that uses the StoredState as backend.
ops.jujuversion module¶
A helper to work with the Juju version.
-
class
ops.jujuversion.
JujuVersion
(version: str)[source]¶ Bases:
object
Helper to work with the Juju version.
It knows how to parse the
JUJU_VERSION
environment variable, and exposes different capabilities according to the specific version, allowing also to compare with other versions.-
PATTERN
= '^\n (?P<major>\\d{1,9})\\.(?P<minor>\\d{1,9}) # <major> and <minor> numbers are always there\n ((?:\\.|-(?P<tag>[a-z]+))(?P<patch>\\d{1,9}))? # sometimes with .<patch> or -<tag><patch>\n (\\.(?P<build>\\d{1,9}))?$ # and sometimes with a <build> number.\n '¶
-
classmethod
from_environ
() → ops.jujuversion.JujuVersion[source]¶ Build a JujuVersion from JUJU_VERSION.
-
has_controller_storage
() → bool[source]¶ Determine whether this Juju version supports controller-side storage.
-
has_secrets
¶ Determine whether this Juju version supports the secrets feature.
-
ops.log module¶
Interface to emit messages to the Juju logging system.
-
class
ops.log.
JujuLogHandler
(model_backend: _ModelBackend, level: int = 10)[source]¶ Bases:
logging.Handler
A handler for sending logs to Juju via juju-log.
-
emit
(record: logging.LogRecord)[source]¶ Send the specified logging record to the Juju backend.
This method is not used directly by the Operator Framework code, but by
logging.Handler
itself as part of the logging machinery.
-
-
ops.log.
setup_root_logging
(model_backend: _ModelBackend, debug: bool = False)[source]¶ Setup python logging to forward messages to juju-log.
By default, logging is set to DEBUG level, and messages will be filtered by Juju. Charmers can also set their own default log level with:
logging.getLogger().setLevel(logging.INFO)
Parameters: - model_backend – a ModelBackend to use for juju-log
- debug – if True, write logs to stderr as well as to juju-log.
ops.main module¶
Main entry point to the Operator Framework.
Note that this module is callable, and calls the ops.main.main()
function.
This is so that import ops
followed by ops.main(MyCharm)
works
as expected.
-
ops.main.
main
(charm_class: Type[ops.charm.CharmBase], use_juju_for_storage: Optional[bool] = None)[source]¶ Setup the charm and dispatch the observed event.
The event name is based on the way this executable was called (argv[0]).
Parameters: - charm_class – your charm class.
- use_juju_for_storage – whether to use controller-side storage. If not specified then kubernetes charms that haven’t previously used local storage and that are running on a new enough Juju default to controller-side storage, otherwise local storage is used.
ops.model module¶
Representations of Juju’s model, application, unit, and other entities.
-
class
ops.model.
Model
(meta: ops.charm.CharmMeta, backend: ops.model._ModelBackend)[source]¶ Bases:
object
Represents the Juju Model as seen from this unit.
This should not be instantiated directly by Charmers, but can be accessed as self.model from any class that derives from Object.
-
app
¶ A
Application
that represents the application this unit is a part of.
-
relations
¶ Mapping of endpoint to list of
Relation
.Answers the question “what am I currently related to”. See also
get_relation()
.
-
config
¶ Return a mapping of config for the current application.
-
resources
¶ Access to resources for this charm.
Use
model.resources.fetch(resource_name)
to get the path on disk where the resource can be found.
-
pod
¶ Use
model.pod.set_spec
to set the container specification for Kubernetes charms.
-
name
¶ Return the name of the Model that this unit is running in.
This is read from the environment variable
JUJU_MODEL_NAME
.
-
uuid
¶ Return the identifier of the Model that this unit is running in.
This is read from the environment variable
JUJU_MODEL_UUID
.
-
get_unit
(unit_name: str) → ops.model.Unit[source]¶ Get an arbitrary unit by name.
Internally this uses a cache, so asking for the same unit two times will return the same object.
-
get_app
(app_name: str) → ops.model.Application[source]¶ Get an application by name.
Internally this uses a cache, so asking for the same application two times will return the same object.
-
get_relation
(relation_name: str, relation_id: Optional[int] = None) → Optional[ops.model.Relation][source]¶ Get a specific Relation instance.
If relation_id is not given, this will return the Relation instance if the relation is established only once or None if it is not established. If this same relation is established multiple times the error TooManyRelatedAppsError is raised.
Parameters: - relation_name – The name of the endpoint for this charm
- relation_id – An identifier for a specific relation. Used to disambiguate when a given application has more than one relation on a given endpoint.
Raises: TooManyRelatedAppsError
– is raised if there is more than one relation to the supplied relation_name and no relation_id was supplied
-
get_binding
(binding_key: Union[str, Relation]) → Optional[ops.model.Binding][source]¶ Get a network space binding.
Parameters: binding_key – The relation name or instance to obtain bindings for. Returns: If binding_key
is a relation name, the method returns the default binding for that relation. If a relation instance is provided, the method first looks up a more specific binding for that specific relation ID, and if none is found falls back to the default binding for the relation name.
-
get_secret
(*, id: Optional[str] = None, label: Optional[str] = None) → ops.model.Secret[source]¶ Get the
Secret
with the given ID or label.You must provide at least one of id or label. If you provide both, the secret will be fetched by ID, and the secret’s label will be updated to the label you provided.
Parameters: - id – Secret ID if fetching by ID.
- label – Secret label if fetching by label (or updating it).
Raises: SecretNotFoundError
– If a secret with this ID or label doesn’t exist.
-
-
class
ops.model.
Application
(name: str, meta: ops.charm.CharmMeta, backend: ops.model._ModelBackend, cache: ops.model._ModelCache)[source]¶ Bases:
object
Represents a named application in the model.
This might be your application, or might be an application that you are related to. Charmers should not instantiate Application objects directly, but should use
Model.get_app()
if they need a reference to a given application.-
name
¶ The name of this application (eg, ‘mysql’). This name may differ from the name of the charm, if the user has deployed it to a different name.
-
status
¶ Used to report or read the status of the overall application.
Can only be read and set by the lead unit of the application.
The status of remote units is always Unknown.
Raises: RuntimeError
– if you try to set the status of another application, or if you try to set the status of this application as a unit that is not the leader.InvalidStatusError
– if you try to set the status to something that is not aStatusBase
Example:
self.model.app.status = BlockedStatus('I need a human to come help me')
-
planned_units
() → int[source]¶ Get the number of units that Juju has “planned” for this application.
E.g., if an operator runs “juju deploy foo”, then “juju add-unit -n 2 foo”, the planned unit count for foo will be 3.
The data comes from the Juju agent, based on data it fetches from the controller. Pending units are included in the count, and scale down events may modify the count before some units have been fully torn down. The information in planned_units is up-to-date as of the start of the current hook invocation.
This method only returns data for this charm’s application – the Juju agent isn’t able to see planned unit counts for other applications in the model.
-
add_secret
(content: Dict[str, str], *, label: Optional[str] = None, description: Optional[str] = None, expire: Union[datetime.datetime, datetime.timedelta, None] = None, rotate: Optional[SecretRotate] = None) → ops.model.Secret[source]¶ Create a
Secret
owned by this application.Parameters: - content – A key-value mapping containing the payload of the secret,
for example
{"password": "foo123"}
. - label – Private label to assign to this secret, which can later be used for lookup.
- description – Description of the secret’s purpose.
- expire – Time in the future (or timedelta from now) at which the secret is due to expire. When that time elapses, Juju will notify the charm by sending a SecretExpired event. None (the default) means the secret will never expire.
- rotate – Rotation policy/time. Every time this elapses, Juju will notify the charm by sending a SecretRotate event. None (the default) means to use the Juju default, which is never rotate.
- content – A key-value mapping containing the payload of the secret,
for example
-
-
class
ops.model.
Unit
(name: str, meta: ops.charm.CharmMeta, backend: ops.model._ModelBackend, cache: ops.model._ModelCache)[source]¶ Bases:
object
Represents a named unit in the model.
This might be your unit, another unit of your application, or a unit of another application that you are related to.
-
name
¶ The name of the unit (eg, ‘mysql/0’)
-
app
¶ The Application the unit is a part of.
-
status
¶ Used to report or read the status of a specific unit.
The status of any unit other than yourself is always Unknown.
Raises: RuntimeError
– if you try to set the status of a unit other than yourself.InvalidStatusError
– if you try to set the status to something other than aStatusBase
Example:
self.model.unit.status = MaintenanceStatus('reconfiguring the frobnicators')
-
is_leader
() → bool[source]¶ Return whether this unit is the leader of its application.
This can only be called for your own unit.
Returns: True if you are the leader, False otherwise Raises: RuntimeError
– if called for a unit that is not yourself
-
set_workload_version
(version: str) → None[source]¶ Record the version of the software running as the workload.
This shouldn’t be confused with the revision of the charm. This is informative only; shown in the output of ‘juju status’.
-
containers
¶ Return a mapping of containers indexed by name.
-
get_container
(container_name: str) → ops.model.Container[source]¶ Get a single container by name.
Raises: ModelError
– if the named container doesn’t exist
-
add_secret
(content: Dict[str, str], *, label: Optional[str] = None, description: Optional[str] = None, expire: Union[datetime.datetime, datetime.timedelta, None] = None, rotate: Optional[SecretRotate] = None) → ops.model.Secret[source]¶ Create a
Secret
owned by this unit.See
Application.add_secret()
for parameter details.
-
open_port
(protocol: Literal[tcp, udp, icmp], port: Optional[int] = None)[source]¶ Open a port with the given protocol for this unit.
Calling this registers intent with Juju that the application should be accessed on the given port, but the port isn’t actually opened externally until the operator runs “juju expose”.
On Kubernetes sidecar charms, the ports opened are not strictly per-unit: Juju will open the union of ports from all units. However, normally charms should make the same open_port() call from every unit.
Parameters: - protocol – String representing the protocol; must be one of ‘tcp’, ‘udp’, or ‘icmp’ (lowercase is recommended, but uppercase is also supported).
- port – The port to open. Required for TCP and UDP; not allowed for ICMP.
-
close_port
(protocol: Literal[tcp, udp, icmp], port: Optional[int] = None)[source]¶ Close a port with the given protocol for this unit.
On Kubernetes sidecar charms, Juju will only close the port once the last unit that opened that port has closed it. However, this is usually not an issue; normally charms should make the same close_port() call from every unit.
Parameters: - protocol – String representing the protocol; must be one of ‘tcp’, ‘udp’, or ‘icmp’ (lowercase is recommended, but uppercase is also supported).
- port – The port to open. Required for TCP and UDP; not allowed for ICMP.
-
-
class
ops.model.
OpenedPort
(protocol: Literal[tcp, udp, icmp], port: Optional[int])[source]¶ Bases:
object
Represents a port opened by
Unit.open_port()
.-
protocol
= None¶ The port number. Will be None if protocol is ‘icmp’.
-
-
class
ops.model.
LazyMapping
[source]¶ Bases:
collections.abc.Mapping
,typing.Generic
,abc.ABC
Represents a dict that isn’t populated until it is accessed.
Charm authors should generally never need to use this directly, but it forms the basis for many of the dicts that the framework tracks.
-
class
ops.model.
RelationMapping
(relations_meta: _RelationsMeta_Raw, our_unit: Unit, backend: _ModelBackend, cache: _ModelCache)[source]¶ Bases:
collections.abc.Mapping
,typing.Generic
Map of relation names to lists of
Relation
instances.
-
class
ops.model.
BindingMapping
(backend: ops.model._ModelBackend)[source]¶ Bases:
collections.abc.Mapping
,typing.Generic
Mapping of endpoints to network bindings.
Charm authors should not instantiate this directly, but access it via
Model.get_binding()
-
get
(binding_key: Union[str, Relation]) → ops.model.Binding[source]¶ Get a specific Binding for an endpoint/relation.
Not used directly by Charm authors. See
Model.get_binding()
-
-
class
ops.model.
Binding
(name: str, relation_id: Optional[int], backend: ops.model._ModelBackend)[source]¶ Bases:
object
Binding to a network space.
-
name
¶ The name of the endpoint this binding represents (eg, ‘db’)
-
network
¶ The network information for this binding.
-
-
class
ops.model.
Network
(network_info: _NetworkDict)[source]¶ Bases:
object
Network space details.
Charm authors should not instantiate this directly, but should get access to the Network definition from
Model.get_binding()
and itsnetwork
attribute.-
interfaces
¶ A list of
NetworkInterface
details. This includes the information about how your application should be configured (eg, what IP addresses should you bind to.) Note that multiple addresses for a single interface are represented as multiple interfaces. (eg,[NetworkInfo('ens1', '10.1.1.1/32'), NetworkInfo('ens1', '10.1.2.1/32'])
)
-
ingress_addresses
¶ A list of
ipaddress.ip_address
objects representing the IP addresses that other units should use to get in touch with you.
-
egress_subnets
¶ A list of
ipaddress.ip_network
representing the subnets that other units will see you connecting from. Due to things like NAT it isn’t always possible to narrow it down to a single address, but when it is clear, the CIDRs will be constrained to a single address. (eg, 10.0.0.1/32)
Parameters: network_info – A dict of network information as returned by network-get
.-
bind_address
¶ A single address that your application should bind() to.
For the common case where there is a single answer. This represents a single address from
interfaces
that can be used to configure where your application should bind() and listen().
-
ingress_address
¶ The address other applications should use to connect to your unit.
Due to things like public/private addresses, NAT and tunneling, the address you bind() to is not always the address other people can use to connect() to you. This is just the first address from
ingress_addresses
.
-
-
class
ops.model.
NetworkInterface
(name: str, address_info: _AddressDict)[source]¶ Bases:
object
Represents a single network interface that the charm needs to know about.
Charmers should not instantiate this type directly. Instead use
Model.get_binding()
to get the network information for a given endpoint.-
name
¶ The name of the interface (eg. ‘eth0’, or ‘ens1’)
-
subnet
¶ An
ipaddress.ip_network
representation of the IP for the network interface. This may be a single address (eg ‘10.0.1.2/32’)
-
-
class
ops.model.
SecretRotate
[source]¶ Bases:
enum.Enum
Secret rotation policies.
-
NEVER
= 'never'¶
-
HOURLY
= 'hourly'¶
-
DAILY
= 'daily'¶
-
WEEKLY
= 'weekly'¶
-
MONTHLY
= 'monthly'¶
-
QUARTERLY
= 'quarterly'¶
-
YEARLY
= 'yearly'¶
-
-
class
ops.model.
SecretInfo
(id: str, label: Optional[str], revision: int, expires: Optional[datetime.datetime], rotation: Optional[ops.model.SecretRotate], rotates: Optional[datetime.datetime])[source]¶ Bases:
object
Secret information (metadata).
-
class
ops.model.
Secret
(backend: ops.model._ModelBackend, id: Optional[str] = None, label: Optional[str] = None, content: Optional[Dict[str, str]] = None)[source]¶ Bases:
object
Represents a single secret in the model.
This class should not be instantiated directly, instead use
Model.get_secret()
(for observers and owners), orApplication.add_secret()
orUnit.add_secret()
(for owners).All secret events have a
.secret
attribute which provides theSecret
associated with that event.-
id
¶ Unique identifier for this secret.
This will be None if you obtained the secret using
Model.get_secret()
with a label but no ID.
-
label
¶ Label used to reference this secret locally.
This label is locally unique, that is, Juju will ensure that the entity (the owner or observer) only has one secret with this label at once.
This will be None if you obtained the secret using
Model.get_secret()
with an ID but no label.
-
get_content
(*, refresh: bool = False) → Dict[str, str][source]¶ Get the secret’s content.
Parameters: refresh – If true, fetch the latest revision’s content and tell Juju to update to tracking that revision. The default is to get the content of the currently-tracked revision. This parameter is only meaningful for secret observers, not owners.
-
peek_content
() → Dict[str, str][source]¶ Get the content of the latest revision of this secret.
This returns the content of the latest revision without updating the tracking.
-
get_info
() → ops.model.SecretInfo[source]¶ Get this secret’s information (metadata).
Only secret owners can fetch this information.
-
set_content
(content: Dict[str, str])[source]¶ Update the content of this secret.
This will create a new secret revision, and notify all units tracking the secret (the “observers”) that a new revision is available with a
ops.charm.SecretChangedEvent
.Parameters: content – A key-value mapping containing the payload of the secret, for example {"password": "foo123"}
.
-
set_info
(*, label: Optional[str] = None, description: Optional[str] = None, expire: Union[datetime.datetime, datetime.timedelta, None] = None, rotate: Optional[ops.model.SecretRotate] = None)[source]¶ Update this secret’s information (metadata).
This will not create a new secret revision (that applies only to
set_content()
). Once attributes are set, they cannot be unset.Parameters: - label – New label to apply.
- description – New description to apply.
- expire – New expiration time (or timedelta from now) to apply.
- rotate – New rotation policy to apply. The new policy will take effect only after the currently-scheduled rotation.
-
grant
(relation: ops.model.Relation, *, unit: Optional[ops.model.Unit] = None)[source]¶ Grant read access to this secret.
If the application or unit has already been granted access to this secret, do nothing.
Parameters: - relation – The relation used to scope the life of this secret.
- unit – If specified, grant access to only this unit, rather than all units in the application.
-
revoke
(relation: ops.model.Relation, *, unit: Optional[ops.model.Unit] = None)[source]¶ Revoke read access to this secret.
If the application or unit does not have access to this secret, do nothing.
Parameters: - relation – The relation used to scope the life of this secret.
- unit – If specified, revoke access to only this unit, rather than all units in the application.
-
remove_revision
(revision: int)[source]¶ Remove the given secret revision.
This is normally called when handling
ops.charm.SecretRemoveEvent
orops.charm.SecretExpiredEvent
.Parameters: revision – The secret revision to remove. If being called from a secret event, this should usually be set to SecretEvent.revision
.
-
remove_all_revisions
()[source]¶ Remove all revisions of this secret.
This is called when the secret is no longer needed, for example when handling
ops.charm.RelationBrokenEvent
.
-
-
class
ops.model.
Relation
(relation_name: str, relation_id: int, is_peer: bool, our_unit: ops.model.Unit, backend: ops.model._ModelBackend, cache: ops.model._ModelCache)[source]¶ Bases:
object
Represents an established relation between this application and another application.
This class should not be instantiated directly, instead use
Model.get_relation()
orops.charm.RelationEvent.relation
. This is principally used byops.charm.RelationMeta
to represent the relationships between charms.-
name
¶ The name of the local endpoint of the relation (eg ‘db’)
-
id
¶ The identifier for a particular relation (integer)
-
app
¶ An
Application
representing the remote application of this relation. For peer relations this will be the local application.
-
units
¶ A set of
Unit
for units that have started and joined this relation. For subordinate relations, this set will include only one unit: the principal unit.
-
data
¶ A
RelationData
holding the data buckets for each entity of a relation. Accessed via eg Relation.data[unit][‘foo’]
-
-
class
ops.model.
RelationData
(relation: ops.model.Relation, our_unit: ops.model.Unit, backend: ops.model._ModelBackend)[source]¶ Bases:
collections.abc.Mapping
,typing.Generic
Represents the various data buckets of a given relation.
Each unit and application involved in a relation has their own data bucket. Eg:
{entity: RelationDataContent}
where entity can be either aUnit
or aApplication
.Units can read and write their own data, and if they are the leader, they can read and write their application data. They are allowed to read remote unit and application data.
This class should not be created directly. It should be accessed via
Relation.data
-
class
ops.model.
RelationDataContent
(relation: Relation, entity: UnitOrApplication, backend: _ModelBackend)[source]¶ Bases:
ops.model.LazyMapping
,collections.abc.MutableMapping
,typing.Generic
Data content of a unit or application in a relation.
-
class
ops.model.
ConfigData
(backend: ops.model._ModelBackend)[source]¶ Bases:
ops.model.LazyMapping
Configuration data.
This class should not be created directly. It should be accessed via
Model.config
.
-
class
ops.model.
StatusBase
(message: str = '')[source]¶ Bases:
object
Status values specific to applications and units.
To access a status by name, see
StatusBase.from_name()
, most use cases will just directly use the child class to indicate their status.Forbid the usage of StatusBase directly.
-
name
= NotImplemented¶
-
-
class
ops.model.
UnknownStatus
[source]¶ Bases:
ops.model.StatusBase
The unit status is unknown.
A unit-agent has finished calling install, config-changed and start, but the charm has not called status-set yet.
Forbid the usage of StatusBase directly.
-
name
= 'unknown'¶
-
-
class
ops.model.
ErrorStatus
(message: str = '')[source]¶ Bases:
ops.model.StatusBase
The unit status is error.
The unit-agent has encountered an error (the application or unit requires human intervention in order to operate correctly).
Forbid the usage of StatusBase directly.
-
name
= 'error'¶
-
-
class
ops.model.
ActiveStatus
(message: str = '')[source]¶ Bases:
ops.model.StatusBase
The unit is ready.
The unit believes it is correctly offering all the services it has been asked to offer.
Forbid the usage of StatusBase directly.
-
name
= 'active'¶
-
-
class
ops.model.
BlockedStatus
(message: str = '')[source]¶ Bases:
ops.model.StatusBase
The unit requires manual intervention.
An operator has to manually intervene to unblock the unit and let it proceed.
Forbid the usage of StatusBase directly.
-
name
= 'blocked'¶
-
-
class
ops.model.
MaintenanceStatus
(message: str = '')[source]¶ Bases:
ops.model.StatusBase
The unit is performing maintenance tasks.
The unit is not yet providing services, but is actively doing work in preparation for providing those services. This is a “spinning” state, not an error state. It reflects activity on the unit itself, not on peers or related units.
Forbid the usage of StatusBase directly.
-
name
= 'maintenance'¶
-
-
class
ops.model.
WaitingStatus
(message: str = '')[source]¶ Bases:
ops.model.StatusBase
A unit is unable to progress.
The unit is unable to progress to an active state because an application to which it is related is not running.
Forbid the usage of StatusBase directly.
-
name
= 'waiting'¶
-
-
class
ops.model.
Resources
(names: Iterable[str], backend: ops.model._ModelBackend)[source]¶ Bases:
object
Object representing resources for the charm.
-
class
ops.model.
Pod
(backend: ops.model._ModelBackend)[source]¶ Bases:
object
Represents the definition of a pod spec in Kubernetes models.
Currently only supports simple access to setting the Juju pod spec via
set_spec
.-
set_spec
(spec: K8sSpec, k8s_resources: Optional[K8sSpec] = None)[source]¶ Set the specification for pods that Juju should start in kubernetes.
See juju help-tool pod-spec-set for details of what should be passed.
Parameters: - spec – The mapping defining the pod specification
- k8s_resources – Additional kubernetes specific specification.
Returns: None
-
-
class
ops.model.
StorageMapping
(storage_names: Iterable[str], backend: ops.model._ModelBackend)[source]¶ Bases:
collections.abc.Mapping
,typing.Generic
Map of storage names to lists of Storage instances.
-
class
ops.model.
Storage
(storage_name: str, storage_index: int, backend: ops.model._ModelBackend)[source]¶ Bases:
object
Represents a storage as defined in metadata.yaml.
-
name
¶ Simple string name of the storage
-
index
¶ The index number for storage
-
index
The index associated with the storage (usually 0 for singular storage).
-
id
¶ The index associated with the storage.
Type: DEPRECATED (use “.index”)
-
full_id
¶ Returns the canonical storage name and id/index based identifier.
-
location
¶ Return the location of the storage.
-
-
exception
ops.model.
MultiPushPullError
(message: str, errors: List[Tuple[str, Exception]])[source]¶ Bases:
Exception
Aggregates multiple push/pull related exceptions into one.
Create an aggregation of several push/pull errors.
Parameters: - message – error message
- errors – list of errors with each represented by a tuple (<source_path>,<exception>) where source_path is the path being pushed/pulled from.
-
class
ops.model.
Container
(name: str, backend: _ModelBackend, pebble_client: Optional[Client] = None)[source]¶ Bases:
object
Represents a named container in a unit.
This class should not be instantiated directly, instead use
Unit.get_container()
orUnit.containers
.-
name
¶ The name of the container from metadata.yaml (eg, ‘postgres’).
-
pebble
¶ The low-level
ops.pebble.Client
instance for this container.
-
can_connect
() → bool[source]¶ Report whether the Pebble API is reachable in the container.
can_connect()
returns a bool that indicates whether the Pebble API is available at the time the method is called. It does not guard against the Pebble API becoming unavailable, and should be treated as a ‘point in time’ status only.If the Pebble API later fails, serious consideration should be given as to the reason for this.
Example:
container = self.unit.get_container("example") if container.can_connect(): try: c.pull('/does/not/exist') except ProtocolError, PathError: # handle it else: event.defer()
-
add_layer
(label: str, layer: _Layer, *, combine: bool = False)[source]¶ Dynamically add a new layer onto the Pebble configuration layers.
Parameters: - label – Label for new layer (and label of layer to merge with if combining).
- layer – A YAML string, configuration layer dict, or pebble.Layer object containing the Pebble layer to add.
- combine – If combine is False (the default), append the new layer as the top layer with the given label (must be unique). If combine is True and the label already exists, the two layers are combined into a single one considering the layer override rules; if the layer doesn’t exist, it is added as usual.
-
get_plan
() → Plan[source]¶ Get the combined Pebble configuration.
This will immediately reflect changes from any previous
add_layer()
calls, regardless of whetherreplan()
orrestart()
have been called.
-
get_services
(*service_names) → _ServiceInfoMapping[source]¶ Fetch and return a mapping of status information indexed by service name.
If no service names are specified, return status information for all services, otherwise return information for only the given services.
-
get_service
(service_name: str) → ServiceInfo[source]¶ Get status information for a single named service.
Raises
ModelError
if service_name is not found.
-
get_checks
(*check_names, level: Optional[CheckLevel] = None) → CheckInfoMapping[source]¶ Fetch and return a mapping of check information indexed by check name.
Parameters: - check_names – Optional check names to query for. If no check names are specified, return checks with any name.
- level – Optional check level to query for. If not specified, fetch checks with any level.
-
get_check
(check_name: str) → CheckInfo[source]¶ Get check information for a single named check.
Raises
ModelError
if check_name is not found.
-
pull
(path: Union[str, pathlib.Path], *, encoding: Optional[str] = 'utf-8') → Union[BinaryIO, TextIO][source]¶ Read a file’s content from the remote system.
Parameters: - path – Path of the file to read from the remote system.
- encoding – Encoding to use for decoding the file’s bytes to str, or None to specify no decoding.
Returns: A readable file-like object, whose read() method will return str objects decoded according to the specified encoding, or bytes if encoding is None.
Raises: pebble.PathError
– If there was an error reading the file at path, for example, if the file doesn’t exist or is a directory.
-
push
(path: Union[str, pathlib.Path], source: Union[bytes, str, BinaryIO, TextIO], *, encoding: str = 'utf-8', make_dirs: bool = False, permissions: Optional[int] = None, user_id: Optional[int] = None, user: Optional[str] = None, group_id: Optional[int] = None, group: Optional[str] = None)[source]¶ Write content to a given file path on the remote system.
Parameters: - path – Path of the file to write to on the remote system.
- source – Source of data to write. This is either a concrete str or bytes instance, or a readable file-like object.
- encoding – Encoding to use for encoding source str to bytes, or strings read from source if it is a TextIO type. Ignored if source is bytes or BinaryIO.
- make_dirs – If True, create parent directories if they don’t exist.
- permissions – Permissions (mode) to create file with (Pebble default is 0o644).
- user_id – User ID (UID) for file.
- user – Username for file. User’s UID must match user_id if both are specified.
- group_id – Group ID (GID) for file.
- group – Group name for file. Group’s GID must match group_id if both are specified.
-
list_files
(path: Union[str, pathlib.Path], *, pattern: Optional[str] = None, itself: bool = False) → List[FileInfo][source]¶ Return list of directory entries from given path on remote system.
Despite the name, this method returns a list of files and directories, similar to
os.listdir()
oros.scandir()
.Parameters: - path – Path of the directory to list, or path of the file to return information about.
- pattern – If specified, filter the list to just the files that match,
for example
*.txt
. - itself – If path refers to a directory, return information about the directory itself, rather than its contents.
-
push_path
(source_path: Union[str, pathlib.Path, Iterable[Union[str, pathlib.Path]]], dest_dir: Union[str, pathlib.Path])[source]¶ Recursively push a local path or files to the remote system.
Only regular files and directories are copied; symbolic links, device files, etc. are skipped. Pushing is attempted to completion even if errors occur during the process. All errors are collected incrementally. After copying has completed, if any errors occurred, a single MultiPushPullError is raised containing details for each error.
Assuming the following files exist locally:
- /foo/bar/baz.txt
- /foo/foobar.txt
- /quux.txt
You could push the following ways:
# copy one file container.push_path('/foo/foobar.txt', '/dst') # Destination results: /dst/foobar.txt # copy a directory container.push_path('/foo', '/dst') # Destination results: /dst/foo/bar/baz.txt, /dst/foo/foobar.txt # copy a directory's contents container.push_path('/foo/*', '/dst') # Destination results: /dst/bar/baz.txt, /dst/foobar.txt # copy multiple files container.push_path(['/foo/bar/baz.txt', 'quux.txt'], '/dst') # Destination results: /dst/baz.txt, /dst/quux.txt # copy a file and a directory container.push_path(['/foo/bar', '/quux.txt'], '/dst') # Destination results: /dst/bar/baz.txt, /dst/quux.txt
Parameters: - source_path – A single path or list of paths to push to the remote system. The paths can be either a file or a directory. If source_path is a directory, the directory base name is attached to the destination directory - i.e. the source path directory is placed inside the destination directory. If a source path ends with a trailing “/” it will have its *contents placed inside the destination directory.
- dest_dir – Remote destination directory inside which the source dir/files will be placed. This must be an absolute path.
-
pull_path
(source_path: Union[str, pathlib.Path, Iterable[Union[str, pathlib.Path]]], dest_dir: Union[str, pathlib.Path])[source]¶ Recursively pull a remote path or files to the local system.
Only regular files and directories are copied; symbolic links, device files, etc. are skipped. Pulling is attempted to completion even if errors occur during the process. All errors are collected incrementally. After copying has completed, if any errors occurred, a single MultiPushPullError is raised containing details for each error.
Assuming the following files exist remotely:
- /foo/bar/baz.txt
- /foo/foobar.txt
- /quux.txt
You could pull the following ways:
# copy one file container.pull_path('/foo/foobar.txt', '/dst') # Destination results: /dst/foobar.txt # copy a directory container.pull_path('/foo', '/dst') # Destination results: /dst/foo/bar/baz.txt, /dst/foo/foobar.txt # copy a directory's contents container.pull_path('/foo/*', '/dst') # Destination results: /dst/bar/baz.txt, /dst/foobar.txt # copy multiple files container.pull_path(['/foo/bar/baz.txt', 'quux.txt'], '/dst') # Destination results: /dst/baz.txt, /dst/quux.txt # copy a file and a directory container.pull_path(['/foo/bar', '/quux.txt'], '/dst') # Destination results: /dst/bar/baz.txt, /dst/quux.txt
Parameters: - source_path – A single path or list of paths to pull from the remote system. The paths can be either a file or a directory but must be absolute paths. If source_path is a directory, the directory base name is attached to the destination directory - i.e. the source path directory is placed inside the destination directory. If a source path ends with a trailing “/” it will have its *contents placed inside the destination directory.
- dest_dir – Local destination directory inside which the source dir/files will be placed.
-
isdir
(path: str) → bool[source]¶ Return true if a directory exists at the given path on the container filesystem.
-
make_dir
(path: str, *, make_parents: bool = False, permissions: Optional[int] = None, user_id: Optional[int] = None, user: Optional[str] = None, group_id: Optional[int] = None, group: Optional[str] = None)[source]¶ Create a directory on the remote system with the given attributes.
Parameters: - path – Path of the directory to create on the remote system.
- make_parents – If True, create parent directories if they don’t exist.
- permissions – Permissions (mode) to create directory with (Pebble default is 0o755).
- user_id – User ID (UID) for directory.
- user – Username for directory. User’s UID must match user_id if both are specified.
- group_id – Group ID (GID) for directory.
- group – Group name for directory. Group’s GID must match group_id if both are specified.
-
remove_path
(path: str, *, recursive: bool = False)[source]¶ Remove a file or directory on the remote system.
Parameters: - path – Path of the file or directory to delete from the remote system.
- recursive – If True, recursively delete path and everything under it.
-
exec
(command: List[str], *, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, user_id: Optional[int] = None, user: Optional[str] = None, group_id: Optional[int] = None, group: Optional[str] = None, stdin: Union[str, bytes, TextIO, BinaryIO, None] = None, stdout: Union[TextIO, BinaryIO, None] = None, stderr: Union[TextIO, BinaryIO, None] = None, encoding: str = 'utf-8', combine_stderr: bool = False) → ExecProcess[source]¶ Execute the given command on the remote system.
See
ops.pebble.Client.exec()
for documentation of the parameters and return value, as well as examples.
-
send_signal
(sig: Union[int, str], *service_names)[source]¶ Send the given signal to one or more services.
Parameters: - sig – Name or number of signal to send, e.g., “SIGHUP”, 1, or signal.SIGHUP.
- service_names – Name(s) of the service(s) to send the signal to.
Raises: pebble.APIError
– If any of the services are not in the plan or are not currently running.
-
-
class
ops.model.
ContainerMapping
(names: Iterable[str], backend: ops.model._ModelBackend)[source]¶ Bases:
collections.abc.Mapping
,typing.Generic
Map of container names to Container objects.
This is done as a mapping object rather than a plain dictionary so that we can extend it later, and so it’s not mutable.
-
class
ops.model.
ServiceInfoMapping
(services: Iterable[ServiceInfo])[source]¶ Bases:
collections.abc.Mapping
,typing.Generic
Map of service names to
ops.pebble.ServiceInfo
objects.This is done as a mapping object rather than a plain dictionary so that we can extend it later, and so it’s not mutable.
-
class
ops.model.
CheckInfoMapping
(checks: Iterable[CheckInfo])[source]¶ Bases:
collections.abc.Mapping
,typing.Generic
Map of check names to
ops.pebble.CheckInfo
objects.This is done as a mapping object rather than a plain dictionary so that we can extend it later, and so it’s not mutable.
-
exception
ops.model.
ModelError
[source]¶ Bases:
Exception
Base class for exceptions raised when interacting with the Model.
-
exception
ops.model.
TooManyRelatedAppsError
(relation_name: str, num_related: int, max_supported: int)[source]¶ Bases:
ops.model.ModelError
Raised by
Model.get_relation()
if there is more than one related application.
-
exception
ops.model.
RelationDataError
[source]¶ Bases:
ops.model.ModelError
Raised when a relation data read/write is invalid.
This is raised if you’re either trying to set a value to something that isn’t a string, or if you are trying to set a value in a bucket that you don’t have access to. (eg, another application/unit or setting your application data but you aren’t the leader.) Also raised when you attempt to read a databag you don’t have access to (i.e. a local app databag if you’re not the leader).
-
exception
ops.model.
RelationDataTypeError
[source]¶ Bases:
ops.model.RelationDataError
Raised by
Relation.data[entity][key] = value
if key or value are not strings.
-
exception
ops.model.
RelationDataAccessError
[source]¶ Bases:
ops.model.RelationDataError
Raised by
Relation.data[entity][key] = value
if you don’t have access.This typically means that you don’t have permission to write read/write the databag, but in some cases it is raised when attempting to read/write from a deceased remote entity.
-
exception
ops.model.
RelationNotFoundError
[source]¶ Bases:
ops.model.ModelError
Backend error when querying juju for a given relation and that relation doesn’t exist.
-
exception
ops.model.
InvalidStatusError
[source]¶ Bases:
ops.model.ModelError
Raised if trying to set an Application or Unit status to something invalid.
-
exception
ops.model.
SecretNotFoundError
[source]¶ Bases:
ops.model.ModelError
Raised when the specified secret does not exist.
ops.pebble module¶
Client for the Pebble API (HTTP over Unix socket).
For a command-line interface for local testing, see test/pebble_cli.py.
-
exception
ops.pebble.
Error
[source]¶ Bases:
Exception
Base class of most errors raised by the Pebble client.
-
exception
ops.pebble.
TimeoutError
[source]¶ Bases:
TimeoutError
,ops.pebble.Error
Raised when a polling timeout occurs.
-
exception
ops.pebble.
ConnectionError
[source]¶ Bases:
ops.pebble.Error
Raised when the Pebble client can’t connect to the socket.
-
exception
ops.pebble.
ProtocolError
[source]¶ Bases:
ops.pebble.Error
Raised when there’s a higher-level protocol error talking to Pebble.
-
exception
ops.pebble.
PathError
(kind: str, message: str)[source]¶ Bases:
ops.pebble.Error
Raised when there’s an error with a specific path.
-
kind
¶ A short string representing the kind of error. Possible values are “not-found”, “permission-denied”, and “generic-file-error”.
-
message
¶ A human-readable error message.
This shouldn’t be instantiated directly.
-
-
exception
ops.pebble.
APIError
(body: Dict[str, Any], code: int, status: str, message: str)[source]¶ Bases:
ops.pebble.Error
Raised when an HTTP API error occurs talking to the Pebble server.
This shouldn’t be instantiated directly.
-
exception
ops.pebble.
ChangeError
(err: str, change: ops.pebble.Change)[source]¶ Bases:
ops.pebble.Error
Raised by actions when a change is ready but has an error.
This shouldn’t be instantiated directly.
-
exception
ops.pebble.
ExecError
(command: List[str], exit_code: int, stdout: Optional[_StrOrBytes], stderr: Optional[_StrOrBytes])[source]¶ Bases:
ops.pebble.Error
Raised when a
Client.exec()
command returns a non-zero exit code.-
command
¶ Command line of command being executed.
-
exit_code
¶ The process’s exit code. This will always be non-zero.
-
stdout
¶ If
ExecProcess.wait_output()
was being called, this is the captured stdout as a str (or bytes if encoding was None). IfExecProcess.wait()
was being called, this is None.
-
stderr
¶ If
ExecProcess.wait_output()
was being called and combine_stderr was False, this is the captured stderr as a str (or bytes if encoding was None). IfExecProcess.wait()
was being called or combine_stderr was True, this is None.
-
STR_MAX_OUTPUT
= 1024¶
-
-
class
ops.pebble.
WarningState
[source]¶ Bases:
enum.Enum
Enum of states for get_warnings() select parameter.
-
ALL
= 'all'¶
-
PENDING
= 'pending'¶
-
-
class
ops.pebble.
ChangeState
[source]¶ Bases:
enum.Enum
Enum of states for get_changes() select parameter.
-
ALL
= 'all'¶
-
IN_PROGRESS
= 'in-progress'¶
-
READY
= 'ready'¶
-
-
class
ops.pebble.
Warning
(message: str, first_added: datetime.datetime, last_added: datetime.datetime, last_shown: Optional[datetime.datetime], expire_after: str, repeat_after: str)[source]¶ Bases:
object
Warning object.
-
class
ops.pebble.
TaskProgress
(label: str, done: int, total: int)[source]¶ Bases:
object
Task progress object.
-
class
ops.pebble.
Task
(id: ops.pebble.TaskID, kind: str, summary: str, status: str, log: List[str], progress: ops.pebble.TaskProgress, spawn_time: datetime.datetime, ready_time: Optional[datetime.datetime], data: Optional[_TaskData] = None)[source]¶ Bases:
object
Task object.
-
class
ops.pebble.
Change
(id: ops.pebble.ChangeID, kind: str, summary: str, status: str, tasks: List[ops.pebble.Task], ready: bool, err: Optional[str], spawn_time: datetime.datetime, ready_time: Optional[datetime.datetime], data: Optional[_ChangeData] = None)[source]¶ Bases:
object
Change object.
-
class
ops.pebble.
Plan
(raw: str)[source]¶ Bases:
object
Represents the effective Pebble configuration.
A plan is the combined layer configuration. The layer configuration is documented at https://github.com/canonical/pebble/#layer-specification.
-
services
¶ This plan’s services mapping (maps service name to Service).
This property is currently read-only.
-
-
class
ops.pebble.
Layer
(raw: Union[str, LayerDict, None] = None)[source]¶ Bases:
object
Represents a Pebble configuration layer.
The format of this is documented at https://github.com/canonical/pebble/#layer-specification.
-
summary
= None¶ Summary of the purpose of this layer.
-
description
= None¶ Long-form description of this layer.
-
-
class
ops.pebble.
Service
(name: str, raw: Optional[_ServiceDict] = None)[source]¶ Bases:
object
Represents a service description in a Pebble configuration layer.
-
class
ops.pebble.
ServiceStartup
[source]¶ Bases:
enum.Enum
Enum of service startup options.
-
ENABLED
= 'enabled'¶
-
DISABLED
= 'disabled'¶
-
-
class
ops.pebble.
ServiceStatus
[source]¶ Bases:
enum.Enum
Enum of service statuses.
-
ACTIVE
= 'active'¶
-
INACTIVE
= 'inactive'¶
-
ERROR
= 'error'¶
-
-
class
ops.pebble.
ServiceInfo
(name: str, startup: Union[ops.pebble.ServiceStartup, str], current: Union[ops.pebble.ServiceStatus, str])[source]¶ Bases:
object
Service status information.
-
class
ops.pebble.
Check
(name: str, raw: Optional[_CheckDict] = None)[source]¶ Bases:
object
Represents a check in a Pebble configuration layer.
-
class
ops.pebble.
CheckLevel
[source]¶ Bases:
enum.Enum
Enum of check levels.
-
UNSET
= ''¶
-
ALIVE
= 'alive'¶
-
READY
= 'ready'¶
-
-
class
ops.pebble.
CheckStatus
[source]¶ Bases:
enum.Enum
Enum of check statuses.
-
UP
= 'up'¶
-
DOWN
= 'down'¶
-
-
class
ops.pebble.
FileType
[source]¶ Bases:
enum.Enum
Enum of file types.
-
FILE
= 'file'¶
-
DIRECTORY
= 'directory'¶
-
SYMLINK
= 'symlink'¶
-
SOCKET
= 'socket'¶
-
NAMED_PIPE
= 'named-pipe'¶
-
DEVICE
= 'device'¶
-
UNKNOWN
= 'unknown'¶
-
-
class
ops.pebble.
FileInfo
(path: str, name: str, type: Union[FileType, str], size: Optional[int], permissions: int, last_modified: datetime.datetime, user_id: Optional[int], user: Optional[str], group_id: Optional[int], group: Optional[str])[source]¶ Bases:
object
Stat-like information about a single file or directory.
-
class
ops.pebble.
CheckInfo
(name: str, level: Union[ops.pebble.CheckLevel, str, None], status: Union[ops.pebble.CheckStatus, str], failures: int = 0, threshold: int = 0)[source]¶ Bases:
object
Check status information.
A list of these objects is returned from
Client.get_checks()
.-
name
¶ The name of the check.
-
level
¶ The check level:
CheckLevel.ALIVE
,CheckLevel.READY
, or None (level not set).
-
status
¶ The status of the check:
CheckStatus.UP
means the check is healthy (the number of failures is less than the threshold),CheckStatus.DOWN
means the check is unhealthy (the number of failures has reached the threshold).
-
failures
¶ The number of failures since the check last succeeded (reset to zero if the check succeeds).
-
threshold
¶ The failure threshold, that is, how many consecutive failures for the check to be considered “down”.
-
-
class
ops.pebble.
ExecProcess
(stdin: Optional[_Readable], stdout: Optional[_Writeable], stderr: Optional[_Writeable], client: Client, timeout: Optional[float], control_ws: _WebSocket, stdio_ws: _WebSocket, stderr_ws: Optional[_WebSocket], command: List[str], encoding: Optional[str], change_id: ops.pebble.ChangeID, cancel_stdin: Optional[Callable[[], None]], cancel_reader: Optional[int], threads: List[threading.Thread])[source]¶ Bases:
object
Represents a process started by
Client.exec()
.To avoid deadlocks, most users should use
wait_output()
instead of reading and writing thestdin
,stdout
, andstderr
attributes directly. Alternatively, users can pass stdin/stdout/stderr toClient.exec()
.This class should not be instantiated directly, only via
Client.exec()
.-
stdin
¶ If the stdin argument was not passed to
Client.exec()
, this is a writable file-like object the caller can use to stream input to the process. It is None if stdin was passed toClient.exec()
.
-
stdout
¶ If the stdout argument was not passed to
Client.exec()
, this is a readable file-like object the caller can use to stream output from the process. It is None if stdout was passed toClient.exec()
.
-
stderr
¶ If the stderr argument was not passed to
Client.exec()
and combine_stderr was False, this is a readable file-like object the caller can use to stream error output from the process. It is None if stderr was passed toClient.exec()
or combine_stderr was True.
-
wait
()[source]¶ Wait for the process to finish.
If a timeout was specified to the
Client.exec()
call, this waits at most that duration.Raises: ChangeError
– if there was an error starting or running the process.ExecError
– if the process exits with a non-zero exit code.
-
wait_output
() → Tuple[_StrOrBytes, Optional[_StrOrBytes]][source]¶ Wait for the process to finish and return tuple of (stdout, stderr).
If a timeout was specified to the
Client.exec()
call, this waits at most that duration. If combine_stderr was True, stdout will include the process’s standard error, and stderr will be None.Raises: ChangeError
– if there was an error starting or running the process.ExecError
– if the process exits with a non-zero exit code.
-
-
class
ops.pebble.
Client
(socket_path: str, opener: Optional[urllib.request.OpenerDirector] = None, base_url: str = 'http://localhost', timeout: float = 5.0)[source]¶ Bases:
object
Pebble API client.
Initialize a client instance.
Defaults to using a Unix socket at socket_path (which must be specified unless a custom opener is provided).
-
get_warnings
(select: ops.pebble.WarningState = <WarningState.PENDING: 'pending'>) → List[ops.pebble.Warning][source]¶ Get list of warnings in given state (pending or all).
-
ack_warnings
(timestamp: datetime.datetime) → int[source]¶ Acknowledge warnings up to given timestamp, return number acknowledged.
-
get_changes
(select: ops.pebble.ChangeState = <ChangeState.IN_PROGRESS: 'in-progress'>, service: Optional[str] = None) → List[ops.pebble.Change][source]¶ Get list of changes in given state, filter by service name if given.
-
abort_change
(change_id: ops.pebble.ChangeID) → ops.pebble.Change[source]¶ Abort change with given ID.
-
autostart_services
(timeout: float = 30.0, delay: float = 0.1) → ops.pebble.ChangeID[source]¶ Start the startup-enabled services and wait (poll) for them to be started.
Parameters: - timeout – Seconds before autostart change is considered timed out (float).
- delay – Seconds before executing the autostart change (float).
Returns: ChangeID of the autostart change.
Raises: ChangeError
– if one or more of the services didn’t start. If timeout is 0, submit the action but don’t wait; just return the change ID immediately.
-
replan_services
(timeout: float = 30.0, delay: float = 0.1) → ops.pebble.ChangeID[source]¶ Replan by (re)starting changed and startup-enabled services and wait for them to start.
Parameters: - timeout – Seconds before replan change is considered timed out (float).
- delay – Seconds before executing the replan change (float).
Returns: ChangeID of the replan change.
Raises: ChangeError
– if one or more of the services didn’t stop/start. If timeout is 0, submit the action but don’t wait; just return the change ID immediately.
-
start_services
(services: Iterable[str], timeout: float = 30.0, delay: float = 0.1) → ops.pebble.ChangeID[source]¶ Start services by name and wait (poll) for them to be started.
Parameters: - services – Non-empty list of services to start.
- timeout – Seconds before start change is considered timed out (float).
- delay – Seconds before executing the start change (float).
Returns: ChangeID of the start change.
Raises: ChangeError
– if one or more of the services didn’t stop/start. If timeout is 0, submit the action but don’t wait; just return the change ID immediately.
-
stop_services
(services: Iterable[str], timeout: float = 30.0, delay: float = 0.1) → ops.pebble.ChangeID[source]¶ Stop services by name and wait (poll) for them to be started.
Parameters: - services – Non-empty list of services to stop.
- timeout – Seconds before stop change is considered timed out (float).
- delay – Seconds before executing the stop change (float).
Returns: ChangeID of the stop change.
Raises: ChangeError
– if one or more of the services didn’t stop/start. If timeout is 0, submit the action but don’t wait; just return the change ID immediately.
-
restart_services
(services: Iterable[str], timeout: float = 30.0, delay: float = 0.1) → ops.pebble.ChangeID[source]¶ Restart services by name and wait (poll) for them to be started.
Parameters: - services – Non-empty list of services to restart.
- timeout – Seconds before restart change is considered timed out (float).
- delay – Seconds before executing the restart change (float).
Returns: ChangeID of the restart change.
Raises: ChangeError
– if one or more of the services didn’t stop/start. If timeout is 0, submit the action but don’t wait; just return the change ID immediately.
-
wait_change
(change_id: ops.pebble.ChangeID, timeout: Optional[float] = 30.0, delay: float = 0.1) → ops.pebble.Change[source]¶ Wait for the given change to be ready.
If the Pebble server supports the /v1/changes/{id}/wait API endpoint, use that to avoid polling, otherwise poll /v1/changes/{id} every delay seconds.
Parameters: - change_id – Change ID of change to wait for.
- timeout – Maximum time in seconds to wait for the change to be ready. It may be None, in which case wait_change never times out.
- delay – If polling, this is the delay in seconds between attempts.
Returns: The Change object being waited on.
Raises: TimeoutError
– If the maximum timeout is reached.
-
add_layer
(label: str, layer: Union[str, LayerDict, ops.pebble.Layer], *, combine: bool = False)[source]¶ Dynamically add a new layer onto the Pebble configuration layers.
If combine is False (the default), append the new layer as the top layer with the given label. If combine is True and the label already exists, the two layers are combined into a single one considering the layer override rules; if the layer doesn’t exist, it is added as usual.
-
get_services
(names: Optional[Iterable[str]] = None) → List[ops.pebble.ServiceInfo][source]¶ Get the service status for the configured services.
If names is specified, only fetch the service status for the services named.
-
pull
(path: str, *, encoding: Optional[str] = 'utf-8') → Union[BinaryIO, TextIO][source]¶ Read a file’s content from the remote system.
Parameters: - path – Path of the file to read from the remote system.
- encoding – Encoding to use for decoding the file’s bytes to str, or None to specify no decoding.
Returns: A readable file-like object, whose read() method will return str objects decoded according to the specified encoding, or bytes if encoding is None.
Raises: PathError
– If there was an error reading the file at path, for example, if the file doesn’t exist or is a directory.
-
push
(path: str, source: _IOSource, *, encoding: str = 'utf-8', make_dirs: bool = False, permissions: Optional[int] = None, user_id: Optional[int] = None, user: Optional[str] = None, group_id: Optional[int] = None, group: Optional[str] = None)[source]¶ Write content to a given file path on the remote system.
Parameters: - path – Path of the file to write to on the remote system.
- source – Source of data to write. This is either a concrete str or bytes instance, or a readable file-like object.
- encoding – Encoding to use for encoding source str to bytes, or strings read from source if it is a TextIO type. Ignored if source is bytes or BinaryIO.
- make_dirs – If True, create parent directories if they don’t exist.
- permissions – Permissions (mode) to create file with (Pebble default is 0o644).
- user_id – User ID (UID) for file.
- user – Username for file. User’s UID must match user_id if both are specified.
- group_id – Group ID (GID) for file.
- group – Group name for file. Group’s GID must match group_id if both are specified.
-
list_files
(path: str, *, pattern: Optional[str] = None, itself: bool = False) → List[ops.pebble.FileInfo][source]¶ Return list of directory entries from given path on remote system.
Despite the name, this method returns a list of files and directories, similar to
os.listdir()
oros.scandir()
.Parameters: - path – Path of the directory to list, or path of the file to return information about.
- pattern – If specified, filter the list to just the files that match,
for example
*.txt
. - itself – If path refers to a directory, return information about the directory itself, rather than its contents.
-
make_dir
(path: str, *, make_parents: bool = False, permissions: Optional[int] = None, user_id: Optional[int] = None, user: Optional[str] = None, group_id: Optional[int] = None, group: Optional[str] = None)[source]¶ Create a directory on the remote system with the given attributes.
Parameters: - path – Path of the directory to create on the remote system.
- make_parents – If True, create parent directories if they don’t exist.
- permissions – Permissions (mode) to create directory with (Pebble default is 0o755).
- user_id – User ID (UID) for directory.
- user – Username for directory. User’s UID must match user_id if both are specified.
- group_id – Group ID (GID) for directory.
- group – Group name for directory. Group’s GID must match group_id if both are specified.
-
remove_path
(path: str, *, recursive: bool = False)[source]¶ Remove a file or directory on the remote system.
Parameters: - path – Path of the file or directory to delete from the remote system.
- recursive – If True, and path is a directory recursively deletes it and everything under it. If the path is a file, delete the file and do nothing if the file is non-existent. Behaviourally similar to rm -rf <file|dir>
-
exec
(command: List[str], *, environment: Optional[Dict[str, str]] = None, working_dir: Optional[str] = None, timeout: Optional[float] = None, user_id: Optional[int] = None, user: Optional[str] = None, group_id: Optional[int] = None, group: Optional[str] = None, stdin: Optional[_IOSource] = None, stdout: Optional[_TextOrBinaryIO] = None, stderr: Optional[_TextOrBinaryIO] = None, encoding: Optional[str] = 'utf-8', combine_stderr: bool = False) → ops.pebble.ExecProcess[source]¶ Execute the given command on the remote system.
Most of the parameters are explained in the “Parameters” section below, however, input/output handling is a bit more complex. Some examples are shown below:
# Simple command with no output; just check exit code >>> process = client.exec(['send-emails']) >>> process.wait() # Fetch output as string >>> process = client.exec(['python3', '--version']) >>> version, _ = process.wait_output() >>> print(version) Python 3.8.10 # Fetch both stdout and stderr as strings >>> process = client.exec(['pg_dump', '-s', ...]) >>> schema, logs = process.wait_output() # Stream input from a string and write output to files >>> stdin = 'foo\nbar\n' >>> with open('out.txt', 'w') as out, open('err.txt', 'w') as err: ... process = client.exec(['awk', '{ print toupper($0) }'], ... stdin=stdin, stdout=out, stderr=err) ... process.wait() >>> open('out.txt').read() 'FOO\nBAR\n' >>> open('err.txt').read() '' # Real-time streaming using ExecProcess.stdin and ExecProcess.stdout >>> process = client.exec(['cat']) >>> def stdin_thread(): ... for line in ['one\n', '2\n', 'THREE\n']: ... process.stdin.write(line) ... process.stdin.flush() ... time.sleep(1) ... process.stdin.close() ... >>> threading.Thread(target=stdin_thread).start() >>> for line in process.stdout: ... print(datetime.datetime.now().strftime('%H:%M:%S'), repr(line)) ... 16:20:26 'one\n' 16:20:27 '2\n' 16:20:28 'THREE\n' >>> process.wait() # will return immediately as stdin was closed # Show exception raised for non-zero return code >>> process = client.exec(['ls', 'notexist']) >>> out, err = process.wait_output() Traceback (most recent call last): ... ExecError: "ls" returned exit code 2 >>> exc = sys.last_value >>> exc.exit_code 2 >>> exc.stdout '' >>> exc.stderr "ls: cannot access 'notfound': No such file or directory\n"
Parameters: - command – Command to execute: the first item is the name (or path) of the executable, the rest of the items are the arguments.
- environment – Environment variables to pass to the process.
- working_dir – Working directory to run the command in. If not set, Pebble uses the target user’s $HOME directory (and if the user argument is not set, $HOME of the user Pebble is running as).
- timeout – Timeout in seconds for the command execution, after which the process will be terminated. If not specified, the execution never times out.
- user_id – User ID (UID) to run the process as.
- user – Username to run the process as. User’s UID must match user_id if both are specified.
- group_id – Group ID (GID) to run the process as.
- group – Group name to run the process as. Group’s GID must match group_id if both are specified.
- stdin – A string or readable file-like object that is sent to the
process’s standard input. If not set, the caller can write
input to
ExecProcess.stdin
to stream input to the process. - stdout – A writable file-like object that the process’s standard
output is written to. If not set, the caller can use
ExecProcess.wait_output()
to capture output as a string, or read fromExecProcess.stdout()
to stream output from the process. - stderr – A writable file-like object that the process’s standard
error is written to. If not set, the caller can use
ExecProcess.wait_output()
to capture error output as a string, or read fromExecProcess.stderr()
to stream error output from the process. Must be None if combine_stderr is True. - encoding – If encoding is set (the default is UTF-8), the types read or written to stdin/stdout/stderr are str, and encoding is used to encode them to bytes. If encoding is None, the types read or written are raw bytes.
- combine_stderr – If True, process’s stderr output is combined into its stdout (the stderr argument must be None). If False, separate streams are used for stdout and stderr.
Returns: A Process object representing the state of the running process. To wait for the command to finish, the caller will typically call
ExecProcess.wait()
if stdout/stderr were provided as arguments toexec()
, orExecProcess.wait_output()
if not.
-
send_signal
(sig: Union[int, str], services: Iterable[str])[source]¶ Send the given signal to the list of services named.
Parameters: - sig – Name or number of signal to send, e.g., “SIGHUP”, 1, or signal.SIGHUP.
- services – Non-empty list of service names to send the signal to.
Raises: APIError
– If any of the services are not in the plan or are not currently running.
-
get_checks
(level: Optional[ops.pebble.CheckLevel] = None, names: Optional[Iterable[str]] = None) → List[ops.pebble.CheckInfo][source]¶ Get the check status for the configured checks.
Parameters: - level – Optional check level to query for (default is to fetch checks with any level).
- names – Optional list of check names to query for (default is to fetch checks with any name).
Returns: List of
CheckInfo
objects.
-
ops.testing module¶
Infrastructure to build unit tests for charms using the Operator Framework.
-
class
ops.testing.
Harness
(charm_cls: Type[CharmType], *, meta: Union[str, TextIO, None] = None, actions: Union[str, TextIO, None] = None, config: Union[str, TextIO, None] = None)[source]¶ Bases:
typing.Generic
This class represents a way to build up the model that will drive a test suite.
The model that is created is from the viewpoint of the charm that you are testing.
Example:
harness = Harness(MyCharm) # Do initial setup here relation_id = harness.add_relation('db', 'postgresql') # Now instantiate the charm to see events as the model changes harness.begin() harness.add_relation_unit(relation_id, 'postgresql/0') harness.update_relation_data(relation_id, 'postgresql/0', {'key': 'val'}) # Check that charm has properly handled the relation_joined event for postgresql/0 self.assertEqual(harness.charm. ...)
Parameters: - charm_cls – The Charm class that you’ll be testing.
- meta – A string or file-like object containing the contents of metadata.yaml. If not supplied, we will look for a ‘metadata.yaml’ file in the parent directory of the Charm, and if not found fall back to a trivial ‘name: test-charm’ metadata.
- actions – A string or file-like object containing the contents of actions.yaml. If not supplied, we will look for a ‘actions.yaml’ file in the parent directory of the Charm.
- config – A string or file-like object containing the contents of config.yaml. If not supplied, we will look for a ‘config.yaml’ file in the parent directory of the Charm.
-
set_can_connect
(container: Union[str, ops.model.Container], val: bool)[source]¶ Change the simulated connection status of a container’s underlying Pebble client.
After calling this,
ops.model.Container.can_connect()
will return val.
-
charm
¶ Return the instance of the charm class that was passed to __init__.
Note that the Charm is not instantiated until you have called
begin()
. Until then, attempting to access this property will raise an exception.
-
framework
¶ Return the Framework that is being driven by this Harness.
-
begin
() → None[source]¶ Instantiate the Charm and start handling events.
Before calling
begin()
, there is no Charm instance, so changes to the Model won’t emit events. You must callbegin()
beforecharm
is valid.
-
begin_with_initial_hooks
() → None[source]¶ Called when you want the Harness to fire the same hooks that Juju would fire at startup.
This triggers install, relation-created, config-changed, start, pebble-ready (for any containers), and any relation-joined hooks based on what relations have been added before you called begin. Note that all of these are fired before returning control to the test suite, so if you want to introspect what happens at each step, you need to fire them directly (e.g. Charm.on.install.emit()).
To use this with all the normal hooks, you should instantiate the harness, setup any relations that you want active when the charm starts, and then call this method. This method will automatically create and add peer relations that are specified in metadata.yaml.
If the charm metadata specifies containers, this sets can_connect to True for all containers (in addition to triggering pebble-ready for each).
Example:
harness = Harness(MyCharm) # Do initial setup here # Add storage if needed before begin_with_initial_hooks() is called storage_ids = harness.add_storage('data', count=1)[0] storage_id = storage_id[0] # we only added one storage instance relation_id = harness.add_relation('db', 'postgresql') harness.add_relation_unit(relation_id, 'postgresql/0') harness.update_relation_data(relation_id, 'postgresql/0', {'key': 'val'}) harness.set_leader(True) harness.update_config({'initial': 'config'}) harness.begin_with_initial_hooks() # This will cause # install, db-relation-created('postgresql'), leader-elected, config-changed, start # db-relation-joined('postrgesql/0'), db-relation-changed('postgresql/0') # To be fired.
-
cleanup
() → None[source]¶ Called by your test infrastructure to cleanup any temporary directories/files/etc.
Currently this only needs to be called if you test with resources. But it is reasonable to always include a testcase.addCleanup(harness.cleanup) just in case.
-
add_oci_resource
(resource_name: str, contents: Optional[Mapping[str, str]] = None) → None[source]¶ Add oci resources to the backend.
This will register an oci resource and create a temporary file for processing metadata about the resource. A default set of values will be used for all the file contents unless a specific contents dict is provided.
Parameters: - resource_name – Name of the resource to add custom contents to.
- contents – Optional custom dict to write for the named resource.
-
add_resource
(resource_name: str, content: AnyStr) → None[source]¶ Add content for a resource to the backend.
This will register the content, so that a call to Model.resources.fetch(resource_name) will return a path to a file containing that content.
Parameters: - resource_name – The name of the resource being added
- content – Either string or bytes content, which will be the content of the filename returned by resource-get. If contents is a string, it will be encoded in utf-8
-
disable_hooks
() → None[source]¶ Stop emitting hook events when the model changes.
This can be used by developers to stop changes to the model from emitting events that the charm will react to. Call
enable_hooks()
to re-enable them.
-
enable_hooks
() → None[source]¶ Re-enable hook events from charm.on when the model is changed.
By default hook events are enabled once you call
begin()
, but if you have useddisable_hooks()
, this can be used to enable them again.
-
hooks_disabled
()[source]¶ A context manager to run code with hooks disabled.
Example:
with harness.hooks_disabled(): # things in here don't fire events harness.set_leader(True) harness.update_config(unset=['foo', 'bar']) # things here will again fire events
-
add_storage
(storage_name: str, count: int = 1, *, attach: bool = False) → List[str][source]¶ Create a new storage device and attach it to this unit.
To have repeatable tests, each device will be initialized with location set to /[tmpdir]/<storage_name>N, where N is the counter and will be a number from [0,total_num_disks-1].
Parameters: - storage_name – The storage backend name on the Charm
- count – Number of disks being added
- attach – True to also attach the storage mount and emit storage-attached if harness.begin() has been called.
Returns: A list of storage IDs, e.g. [“my-storage/1”, “my-storage/2”].
-
detach_storage
(storage_id: str) → None[source]¶ Detach a storage device.
The intent of this function is to simulate a “juju detach-storage” call. It will trigger a storage-detaching hook if the storage unit in question exists and is presently marked as attached.
Parameters: storage_id – The full storage ID of the storage unit being detached, including the storage key, e.g. my-storage/0.
-
attach_storage
(storage_id: str) → None[source]¶ Attach a storage device.
The intent of this function is to simulate a “juju attach-storage” call. It will trigger a storage-attached hook if the storage unit in question exists and is presently marked as detached.
Parameters: storage_id – The full storage ID of the storage unit being attached, including the storage key, e.g. my-storage/0.
-
remove_storage
(storage_id: str) → None[source]¶ Detach a storage device.
The intent of this function is to simulate a “juju remove-storage” call. It will trigger a storage-detaching hook if the storage unit in question exists and is presently marked as attached. Then it will remove the storage unit from the testing backend.
Parameters: storage_id – The full storage ID of the storage unit being removed, including the storage key, e.g. my-storage/0.
-
add_relation
(relation_name: str, remote_app: str) → int[source]¶ Declare that there is a new relation between this app and remote_app.
In the case of adding peer relations, remote_app is this app. This function creates a relation with an application and will trigger a relation-created hook. To relate units (and trigger relation-joined and relation-changed hooks), you should also call
add_relation_unit()
.Parameters: - relation_name – The relation on Charm that is being related to
- remote_app – The name of the application that is being related to
Returns: The relation_id created by this add_relation.
-
remove_relation
(relation_id: int) → None[source]¶ Remove a relation.
Parameters: relation_id – The relation ID for the relation to be removed. Raises: RelationNotFoundError
– if relation id is not valid
-
add_relation_unit
(relation_id: int, remote_unit_name: str) → None[source]¶ Add a new unit to a relation.
Example:
rel_id = harness.add_relation('db', 'postgresql') harness.add_relation_unit(rel_id, 'postgresql/0')
This will trigger a relation_joined event. This would naturally be followed by a relation_changed event, which you can trigger with
update_relation_data()
. This separation is artificial in the sense that Juju will always fire the two, but is intended to make testing relations and their data bags slightly more natural.Parameters: - relation_id – The integer relation identifier (as returned by add_relation).
- remote_unit_name – A string representing the remote unit that is being added.
Returns: None
-
remove_relation_unit
(relation_id: int, remote_unit_name: str) → None[source]¶ Remove a unit from a relation.
Example:
rel_id = harness.add_relation('db', 'postgresql') harness.add_relation_unit(rel_id, 'postgresql/0') ... harness.remove_relation_unit(rel_id, 'postgresql/0')
This will trigger a relation_departed event. This would normally be followed by a relation_changed event triggered by Juju. However, when using the test harness, a relation_changed event must be triggered using
update_relation_data()
. This deviation from normal Juju behaviour facilitates testing by making each step in the charm life cycle explicit.Parameters: - relation_id – The integer relation identifier (as returned by add_relation).
- remote_unit_name – A string representing the remote unit that is being removed.
Raises: KeyError
– if relation_id or remote_unit_name is not validValueError
– if remote_unit_name is not valid
-
get_relation_data
(relation_id: int, app_or_unit: Union[str, ops.model.Application, ops.model.Unit]) → Mapping[str, str][source]¶ Get the relation data bucket for a single app or unit in a given relation.
This ignores all of the safety checks of who can and can’t see data in relations (eg, non-leaders can’t read their own application’s relation data because there are no events that keep that data up-to-date for the unit).
Parameters: - relation_id – The relation whose content we want to look at.
- app_or_unit – An Application or Unit instance, or its name, whose data we want to read
Returns: A dict containing the relation data for app_or_unit or None.
Raises: KeyError
– if relation_id doesn’t exist
-
get_pod_spec
() → Tuple[Mapping[Any, Any], Mapping[Any, Any]][source]¶ Return the content of the pod spec as last set by the charm.
This returns both the pod spec and any k8s_resources that were supplied. See the signature of Model.pod.set_spec
-
get_container_pebble_plan
(container_name: str) → ops.pebble.Plan[source]¶ Return the current Plan that pebble is executing for the given container.
Parameters: container_name – The simple name of the associated container Returns: The pebble.Plan for this container. You can use ops.pebble.Plan.to_yaml()
to get a string form for the content. Will raise KeyError if no pebble client exists for that container name. (should only happen if container is not present in metadata.yaml)
-
container_pebble_ready
(container_name: str)[source]¶ Fire the pebble_ready hook for the associated container.
This will switch the given container’s can_connect state to True before the hook function is called.
It will do nothing if begin() has not been called.
-
set_model_info
(name: Optional[str] = None, uuid: Optional[str] = None) → None[source]¶ Set the name and uuid of the Model that this is representing.
This cannot be called once begin() has been called. But it lets you set the value that will be returned by Model.name and Model.uuid.
This is a convenience method to invoke both Harness.set_model_name and Harness.set_model_uuid at once.
-
set_model_name
(name: str) → None[source]¶ Set the name of the Model that this is representing.
This cannot be called once begin() has been called. But it lets you set the value that will be returned by Model.name.
-
set_model_uuid
(uuid: str) → None[source]¶ Set the uuid of the Model that this is representing.
This cannot be called once begin() has been called. But it lets you set the value that will be returned by Model.uuid.
-
update_relation_data
(relation_id: int, app_or_unit: str, key_values: Mapping[str, str]) → None[source]¶ Update the relation data for a given unit or application in a given relation.
This also triggers the relation_changed event for this relation_id.
Parameters: - relation_id – The integer relation_id representing this relation.
- app_or_unit – The unit or application name that is being updated. This can be the local or remote application.
- key_values – Each key/value will be updated in the relation data.
-
update_config
(key_values: Optional[Mapping[str, _ConfigValue]] = None, unset: Iterable[str] = ()) → None[source]¶ Update the config as seen by the charm.
This will trigger a config_changed event.
Note that the key_values mapping will only add or update configuration items. To remove existing ones, see the unset parameter.
Parameters: - key_values – A Mapping of key:value pairs to update in config.
- unset – An iterable of keys to remove from Config. This sets the value to the default if defined, otherwise removes the key altogether.
-
set_leader
(is_leader: bool = True) → None[source]¶ Set whether this unit is the leader or not.
If this charm becomes a leader then leader_elected will be triggered. If Harness.begin() has already been called, then the charm’s peer relation should usually be added prior to calling this method (i.e. with Harness.add_relation) to properly initialize and make available relation data that leader elected hooks may want to access.
Parameters: is_leader – True/False as to whether this unit is the leader.
-
set_planned_units
(num_units: int) → None[source]¶ Set the number of “planned” units that “Application.planned_units” should return.
In real world circumstances, this number will be the number of units in the application. E.g., this number will be the number of peers this unit has, plus one, as we count our own unit in the total.
A change to the return from planned_units will not generate an event. Typically, a charm author would check planned units during a config or install hook, or after receiving a peer relation joined event.
-
reset_planned_units
()[source]¶ Reset the planned units override.
This allows the harness to fall through to the built in methods that will try to guess at a value for planned units, based on the number of peer relations that have been setup in the testing harness.
-
add_network
(address: str, *, endpoint: Optional[str] = None, relation_id: Optional[int] = None, cidr: Optional[str] = None, interface: str = 'eth0', ingress_addresses: Optional[Iterable[str]] = None, egress_subnets: Optional[Iterable[str]] = None)[source]¶ Add simulated network data for the given relation endpoint (binding).
Calling this multiple times with the same (binding, relation_id) combination will replace the associated network data.
Example:
# Set network info for default binding harness.add_network('10.0.0.10') # Or set network info for specific endpoint harness.add_network('10.0.0.10', endpoint='db')
After either of those calls, the following will be true (in the first case, the simulated network-get will fall back to the default binding):
binding = harness.model.get_binding('db') assert binding.network.bind_address == ipaddress.IPv4Address('10.0.0.10'))
Parameters: - address – Binding’s IPv4 or IPv6 address.
- endpoint – Name of relation endpoint (binding) to add network data for. If not provided, add info for the default binding.
- relation_id – Relation ID for the binding. If provided, the endpoint argument must be provided and correspond. If not provided, add network data for the endpoint’s default binding.
- cidr – Binding’s CIDR. Defaults to “<address>/24” if address is an IPv4 address, or “<address>/64” if address is IPv6 (the host bits are cleared).
- interface – Name of network interface.
- ingress_addresses – List of ingress addresses. Defaults to [address].
- egress_subnets – List of egress subnets. Defaults to [cidr].
Raises: ModelError
– If the endpoint is not a known relation name, or the relation_id is incorrect or doesn’t match the endpoint.ValueError
– If address is not an IPv4 or IPv6 address.
-
add_model_secret
(owner: Union[str, ops.model.Application, ops.model.Unit], content: Dict[str, str]) → str[source]¶ Add a secret owned by the remote application or unit specified.
This is named
add_model_secret
instead ofadd_secret
to avoid confusion with theops.model.Application.add_secret()
andops.model.Unit.add_secret()
methods used by secret owner charms.Parameters: - owner – The name of the remote application (or specific remote unit) that will own the secret.
- content – A key-value mapping containing the payload of the secret,
for example
{"password": "foo123"}
.
Returns: The ID of the newly-secret added.
-
set_secret_content
(secret_id: str, content: Dict[str, str])[source]¶ Update a secret’s content, add a new revision, and fire secret-changed.
Parameters: - secret_id – The ID of the secret to update. This should normally be
the return value of
add_model_secret()
. - content – A key-value mapping containing the new payload.
- secret_id – The ID of the secret to update. This should normally be
the return value of
-
grant_secret
(secret_id: str, observer: Union[str, ops.model.Application, ops.model.Unit])[source]¶ Grant read access to this secret for the given observer application or unit.
If the given application or unit has already been granted access to this secret, do nothing.
Parameters: - secret_id – The ID of the secret to grant access to. This should
normally be the return value of
add_model_secret()
. - observer – The name of the application (or specific unit) to grant access to. You must already have created a relation between this application and the charm under test.
- secret_id – The ID of the secret to grant access to. This should
normally be the return value of
-
revoke_secret
(secret_id: str, observer: Union[str, ops.model.Application, ops.model.Unit])[source]¶ Revoke read access to this secret for the given observer application or unit.
If the given application or unit does not have access to this secret, do nothing.
Parameters: - secret_id – The ID of the secret to revoke access for. This should
normally be the return value of
add_model_secret()
. - observer – The name of the application (or specific unit) to revoke access to. You must already have created a relation between this application and the charm under test.
- secret_id – The ID of the secret to revoke access for. This should
normally be the return value of
-
get_secret_grants
(secret_id: str, relation_id: int) → Set[str][source]¶ Return the set of app and unit names granted to secret for this relation.
Parameters: - secret_id – The ID of the secret to get grants for.
- relation_id – The ID of the relation granted access.
-
get_secret_revisions
(secret_id: str) → List[int][source]¶ Return the list of revision IDs for the given secret, oldest first.
Parameters: secret_id – The ID of the secret to get revisions for.
-
trigger_secret_rotation
(secret_id: str, *, label: Optional[str] = None)[source]¶ Trigger a secret-rotate event for the given secret.
This event is fired by Juju when a secret’s rotation time elapses, however, time-based events cannot be simulated appropriately in the harness, so this fires it manually.
Parameters: - secret_id – The ID of the secret associated with the event.
- label – Label value to send to the event. If None, the secret’s label is used.
-
trigger_secret_removal
(secret_id: str, revision: int, *, label: Optional[str] = None)[source]¶ Trigger a secret-remove event for the given secret and revision.
This event is fired by Juju for a specific revision when all the secret’s observers have refreshed to a later revision, however, in the harness you call this method to fire the event manually.
Parameters: - secret_id – The ID of the secret associated with the event.
- revision – Revision number to provide to the event. This should be
an item from the list returned by
get_secret_revisions()
. - label – Label value to send to the event. If None, the secret’s label is used.
-
trigger_secret_expiration
(secret_id: str, revision: int, *, label: Optional[str] = None)[source]¶ Trigger a secret-expired event for the given secret.
This event is fired by Juju when a secret’s expiration time elapses, however, time-based events cannot be simulated appropriately in the harness, so this fires it manually.
Parameters: - secret_id – The ID of the secret associated with the event.
- revision – Revision number to provide to the event. This should be
an item from the list returned by
get_secret_revisions()
. - label – Label value to send to the event. If None, the secret’s label is used.