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
146 await model.connect_current()
148 # add a new default machine
149 machine1 = await model.add_machine()
151 # add a machine with constraints, disks, and series specified
152 machine2 = await model.add_machine(
164 # add a lxd container to machine2
165 machine3 = await model.add_machine(
166 'lxd:{}'.format(machine2.id))
168 # deploy charm to the lxd container
169 application = await model.deploy(
171 application_name='ubuntu',
178 Reacting to Changes in a Model
179 ------------------------------
180 To watch for and respond to changes in a model, register an observer with the
181 model. The easiest way to do this is by creating a
182 :class:`juju.model.ModelObserver` subclass.
186 from juju.model import Model, ModelObserver
188 class MyModelObserver(ModelObserver):
189 async def on_change(self, delta, old, new, model):
190 # The raw change data (dict) from the websocket.
193 # The entity type (str) affected by this change.
194 # One of ('action', 'application', 'annotation', 'machine',
195 # 'unit', 'relation')
198 # The type (str) of change.
199 # One of ('add', 'change', 'remove')
202 # The 'old' and 'new' parameters are juju.model.ModelEntity
203 # instances which represent an entity in the model both before
204 # this change was applied (old) and after (new).
206 # If an entity is being added to the model, the 'old' param
208 if delta.type == 'add':
211 # If an entity is being removed from the model, the 'new' param
213 if delta.type == 'remove':
216 # The 'old' and 'new' parameters, when not None, will be instances
217 # of a juju.model.ModelEntity subclass. The type of the subclass
218 # depends on the value of 'delta.entity', for example:
222 # 'action' -> juju.action.Action
223 # 'application' -> juju.application.Application
224 # 'annotation' -> juju.annotation.Annotation
225 # 'machine' -> juju.machine.Machine
226 # 'unit' -> juju.unit.Unit
227 # 'relation' -> juju.relation.Relation
229 # Finally, the 'model' parameter is a reference to the
230 # juju.model.Model instance to which this observer is attached.
235 await model.connect_current()
237 model.add_observer(MyModelObserver())
240 Every change in the model will result in a call to the `on_change()`
241 method of your observer(s).
243 To target your code more precisely, define method names that correspond
244 to the entity and type of change that you wish to handle.
248 from juju.model import Model, ModelObserver
250 class MyModelObserver(ModelObserver):
251 async def on_application_change(self, delta, old, new, model):
252 # Both 'old' and 'new' params will be instances of
253 # juju.application.Application
256 async def on_unit_remove(self, delta, old, new, model):
257 # Since a unit is being removed, the 'new' param will always
258 # be None in this handler. The 'old' param will be an instance
259 # of juju.unit.Unit - the state of the unit before it was removed.
262 async def on_machine_add(self, delta, old, new, model):
263 # Since a machine is being added, the 'old' param will always be
264 # None in this handler. The 'new' param will be an instance of
265 # juju.machine.Machine.
268 async def on_change(self, delta, old, new, model):
269 # The catch-all handler - will be called whenever a more
270 # specific handler method is not defined.
273 Any :class:`juju.model.ModelEntity` object can be observed directly by
274 registering callbacks on the object itself.
280 async def on_app_change(delta, old, new, model):
281 logging.debug('App changed: %r', new)
283 async def on_app_remove(delta, old, new, model):
284 logging.debug('App removed: %r', old)
286 ubuntu_app = await model.deploy(
288 application_name='ubuntu',
292 ubuntu_app.on_change(on_app_change)
293 ubuntu_app.on_remove(on_app_remove)