3 A Juju controller provides websocket endpoints for each of its
4 models. In order to do anything useful with a model, the juju lib must
5 connect to one of these endpoints. There are several ways to do this.
7 For api docs, see :class:`juju.model.Model`.
10 Connecting to the Current Model
11 -------------------------------
12 Connect to the currently active Juju model (the one returned by
13 `juju switch`). This only works if you have the Juju CLI client installed.
17 from juju.model import Model
20 await model.connect_current()
23 Connecting to a Named Model
24 ---------------------------
25 Connect to a model by name, using the same format as that returned from the
26 `juju switch` command. The accepted format is '[controller:][user/]model'.
27 This only works if you have the Juju CLI client installed.
32 # juju-2.0.1:admin/libjuju
34 from juju.model import Model
37 await model.connect_model('juju-2.0.1:admin/libjuju')
40 Connecting with Username/Password Authentication
41 ------------------------------------------------
42 The most flexible, but also most verbose, way to connect is using the API
43 endpoint url and credentials directly. This method does NOT require the Juju
44 CLI client to be installed.
48 from juju.model import Model
52 controller_endpoint = '10.0.4.171:17070'
53 model_uuid = 'e8399ac7-078c-4817-8e5e-32316d55b083'
55 password = 'f53f08cfc32a2e257fe5393271d89d62'
57 # Left out for brevity, but if you have a cert string you should pass it in.
58 # You can copy the cert from the output of The `juju show-controller`
71 Connecting with Macaroon Authentication
72 ---------------------------------------
73 To connect to a shared model, or a model an a shared controller, you'll need
74 to use macaroon authentication. The simplest example is shown below, and uses
75 already-discharged macaroons from the local filesystem. This will work if you
76 have the Juju CLI installed.
80 The library does not yet contain support for fetching and discharging
81 macaroons. Until it does, if you want to use macaroon auth, you'll need
82 to supply already-discharged macaroons yourself.
86 from juju.client.connection import get_macaroons()
87 from juju.model import Model
91 controller_endpoint = '10.0.4.171:17070'
92 model_uuid = 'e8399ac7-078c-4817-8e5e-32316d55b083'
96 macaroons = get_macaroons()
108 Creating and Destroying a Model
109 -------------------------------
110 Example of creating a new model and then destroying it. See
111 :meth:`juju.controller.Controller.add_model` and
112 :meth:`juju.controller.Controller.destroy_model` for more info.
116 from juju.controller import Controller
118 controller = Controller()
119 await controller.connect_current()
121 # Create our new model
122 model = await controller.add_model(
123 'mymodel', # name of your new model
126 # Do stuff with our model...
129 await model.disconnect()
130 await controller.destroy_model(model.info.uuid)
134 Adding Machines and Containers
135 ------------------------------
136 To add a machine or container, connect to a model and then call its
137 :meth:`~juju.model.Model.add_machine` method. A
138 :class:`~juju.machine.Machine` instance is returned. The machine id
139 can be used to deploy a charm to a specific machine or container.
143 from juju.model import Model
150 await model.connect_current()
152 # add a new default machine
153 machine1 = await model.add_machine()
155 # add a machine with constraints, disks, and series specified
156 machine2 = await model.add_machine(
168 # add a lxd container to machine2
169 machine3 = await model.add_machine(
170 'lxd:{}'.format(machine2.id))
172 # deploy charm to the lxd container
173 application = await model.deploy(
175 application_name='ubuntu',
182 await application.remove()
184 # destroy machines - note that machine3 must be destroyed before machine2
185 # since it's a container on machine2
186 await machine3.destroy(force=True)
187 await machine2.destroy(force=True)
188 await machine1.destroy(force=True)
191 Reacting to Changes in a Model
192 ------------------------------
193 To watch for and respond to changes in a model, register an observer with the
194 model. The easiest way to do this is by creating a
195 :class:`juju.model.ModelObserver` subclass.
199 from juju.model import Model, ModelObserver
201 class MyModelObserver(ModelObserver):
202 async def on_change(self, delta, old, new, model):
203 # The raw change data (dict) from the websocket.
206 # The entity type (str) affected by this change.
207 # One of ('action', 'application', 'annotation', 'machine',
208 # 'unit', 'relation')
211 # The type (str) of change.
212 # One of ('add', 'change', 'remove')
215 # The 'old' and 'new' parameters are juju.model.ModelEntity
216 # instances which represent an entity in the model both before
217 # this change was applied (old) and after (new).
219 # If an entity is being added to the model, the 'old' param
221 if delta.type == 'add':
224 # If an entity is being removed from the model, the 'new' param
226 if delta.type == 'remove':
229 # The 'old' and 'new' parameters, when not None, will be instances
230 # of a juju.model.ModelEntity subclass. The type of the subclass
231 # depends on the value of 'delta.entity', for example:
235 # 'action' -> juju.action.Action
236 # 'application' -> juju.application.Application
237 # 'annotation' -> juju.annotation.Annotation
238 # 'machine' -> juju.machine.Machine
239 # 'unit' -> juju.unit.Unit
240 # 'relation' -> juju.relation.Relation
242 # Finally, the 'model' parameter is a reference to the
243 # juju.model.Model instance to which this observer is attached.
248 await model.connect_current()
250 model.add_observer(MyModelObserver())
253 Every change in the model will result in a call to the `on_change()`
254 method of your observer(s).
256 To target your code more precisely, define method names that correspond
257 to the entity and type of change that you wish to handle.
261 from juju.model import Model, ModelObserver
263 class MyModelObserver(ModelObserver):
264 async def on_application_change(self, delta, old, new, model):
265 # Both 'old' and 'new' params will be instances of
266 # juju.application.Application
269 async def on_unit_remove(self, delta, old, new, model):
270 # Since a unit is being removed, the 'new' param will always
271 # be None in this handler. The 'old' param will be an instance
272 # of juju.unit.Unit - the state of the unit before it was removed.
275 async def on_machine_add(self, delta, old, new, model):
276 # Since a machine is being added, the 'old' param will always be
277 # None in this handler. The 'new' param will be an instance of
278 # juju.machine.Machine.
281 async def on_change(self, delta, old, new, model):
282 # The catch-all handler - will be called whenever a more
283 # specific handler method is not defined.
286 Any :class:`juju.model.ModelEntity` object can be observed directly by
287 registering callbacks on the object itself.
293 async def on_app_change(delta, old, new, model):
294 logging.debug('App changed: %r', new)
296 async def on_app_remove(delta, old, new, model):
297 logging.debug('App removed: %r', old)
299 ubuntu_app = await model.deploy(
301 application_name='ubuntu',
305 ubuntu_app.on_change(on_app_change)
306 ubuntu_app.on_remove(on_app_remove)