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 py: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.
21 Connecting to a Named Model
22 ---------------------------
23 Connect to a model by name, using the same format as that returned from the
24 `juju switch` command. The accepted format is '[controller:][user/]model'.
25 This only works if you have the Juju CLI client installed.
30 await model.connect('juju-2.0.1:admin/libjuju')
33 Connecting with Authentication
34 ------------------------------
35 You can control what user you are connecting with by specifying either a
36 username/password pair, or a macaroon or bakery client that can provide
43 await model.connect(username='admin',
44 password='f53f08cfc32a2e257fe5393271d89d62')
47 await model.connect(macaroons=[
49 "Name": "macaroon-218d87053ad19626bcd5a0eef0bc9ba8bd4fbd80a968f52a5fd430b2aa8660df",
50 "Value": "W3siY2F2ZWF0cyI6 ... jBkZiJ9XQ==",
51 "Domain": "10.130.48.27",
55 "Expires": "2018-03-07T22:07:23Z",
59 # or with a bakery client
60 from macaroonbakery.httpbakery import Client
61 from http.cookiejar import FileCookieJar
63 bakery_client=Client()
64 bakery_client.cookies = FileCookieJar('cookies.txt')
66 await model.connect(bakery_client=bakery_client)
70 Connecting with an Explicit Endpoint
71 ------------------------------------
72 The most flexible, but also most verbose, way to connect is using the API
73 endpoint url, model UUID, and credentials directly. This method does NOT
74 require the Juju CLI client to be installed.
78 from juju.model import Model
82 endpoint='10.0.4.171:17070',
83 uuid='e8399ac7-078c-4817-8e5e-32316d55b083',
85 password='f53f08cfc32a2e257fe5393271d89d62',
86 cacert=None, # Left out for brevity, but if you have a cert string you
87 # should pass it in. You can get the cert from the output
88 # of The `juju show-controller` command.
92 Creating and Destroying a Model
93 -------------------------------
94 Example of creating a new model and then destroying it. See
95 py:method:`juju.controller.Controller.add_model` and
96 py:method:`juju.controller.Controller.destroy_model` for more info.
100 from juju.controller import Controller
102 controller = Controller()
103 await controller.connect_current()
105 # Create our new model
106 model = await controller.add_model(
107 'mymodel', # name of your new model
110 # Do stuff with our model...
113 await model.disconnect()
114 await controller.destroy_model(model.info.uuid)
118 Adding Machines and Containers
119 ------------------------------
120 To add a machine or container, connect to a model and then call its
121 py:method:`~juju.model.Model.add_machine` method. A
122 py:class:`~juju.machine.Machine` instance is returned. The machine id
123 can be used to deploy a charm to a specific machine or container.
127 from juju.model import Model
134 await model.connect_current()
136 # add a new default machine
137 machine1 = await model.add_machine()
139 # add a machine with constraints, disks, and series specified
140 machine2 = await model.add_machine(
152 # add a lxd container to machine2
153 machine3 = await model.add_machine(
154 'lxd:{}'.format(machine2.id))
156 # deploy charm to the lxd container
157 application = await model.deploy(
159 application_name='ubuntu',
166 await application.remove()
168 # destroy machines - note that machine3 must be destroyed before machine2
169 # since it's a container on machine2
170 await machine3.destroy(force=True)
171 await machine2.destroy(force=True)
172 await machine1.destroy(force=True)
175 Reacting to Changes in a Model
176 ------------------------------
177 To watch for and respond to changes in a model, register an observer with the
178 model. The easiest way to do this is by creating a
179 py:class:`juju.model.ModelObserver` subclass.
183 from juju.model import Model, ModelObserver
185 class MyModelObserver(ModelObserver):
186 async def on_change(self, delta, old, new, model):
187 # The raw change data (dict) from the websocket.
190 # The entity type (str) affected by this change.
191 # One of ('action', 'application', 'annotation', 'machine',
192 # 'unit', 'relation')
195 # The type (str) of change.
196 # One of ('add', 'change', 'remove')
199 # The 'old' and 'new' parameters are juju.model.ModelEntity
200 # instances which represent an entity in the model both before
201 # this change was applied (old) and after (new).
203 # If an entity is being added to the model, the 'old' param
205 if delta.type == 'add':
208 # If an entity is being removed from the model, the 'new' param
210 if delta.type == 'remove':
213 # The 'old' and 'new' parameters, when not None, will be instances
214 # of a juju.model.ModelEntity subclass. The type of the subclass
215 # depends on the value of 'delta.entity', for example:
219 # 'action' -> juju.action.Action
220 # 'application' -> juju.application.Application
221 # 'annotation' -> juju.annotation.Annotation
222 # 'machine' -> juju.machine.Machine
223 # 'unit' -> juju.unit.Unit
224 # 'relation' -> juju.relation.Relation
226 # Finally, the 'model' parameter is a reference to the
227 # juju.model.Model instance to which this observer is attached.
232 await model.connect_current()
234 model.add_observer(MyModelObserver())
237 Every change in the model will result in a call to the `on_change()`
238 method of your observer(s).
240 To target your code more precisely, define method names that correspond
241 to the entity and type of change that you wish to handle.
245 from juju.model import Model, ModelObserver
247 class MyModelObserver(ModelObserver):
248 async def on_application_change(self, delta, old, new, model):
249 # Both 'old' and 'new' params will be instances of
250 # juju.application.Application
253 async def on_unit_remove(self, delta, old, new, model):
254 # Since a unit is being removed, the 'new' param will always
255 # be None in this handler. The 'old' param will be an instance
256 # of juju.unit.Unit - the state of the unit before it was removed.
259 async def on_machine_add(self, delta, old, new, model):
260 # Since a machine is being added, the 'old' param will always be
261 # None in this handler. The 'new' param will be an instance of
262 # juju.machine.Machine.
265 async def on_change(self, delta, old, new, model):
266 # The catch-all handler - will be called whenever a more
267 # specific handler method is not defined.
270 Any py:class:`juju.model.ModelEntity` object can be observed directly by
271 registering callbacks on the object itself.
277 async def on_app_change(delta, old, new, model):
278 logging.debug('App changed: %r', new)
280 async def on_app_remove(delta, old, new, model):
281 logging.debug('App removed: %r', old)
283 ubuntu_app = await model.deploy(
285 application_name='ubuntu',
289 ubuntu_app.on_change(on_app_change)
290 ubuntu_app.on_remove(on_app_remove)