6 from .client
import client
7 from .client
import connection
8 from .model
import Model
10 log
= logging
.getLogger(__name__
)
13 class Controller(object):
14 def __init__(self
, loop
=None,
15 max_frame_size
=connection
.Connection
.DEFAULT_FRAME_SIZE
):
16 """Instantiate a new Controller.
18 One of the connect_* methods will need to be called before this
19 object can be used for anything interesting.
21 :param loop: an asyncio event loop
24 self
.loop
= loop
or asyncio
.get_event_loop()
25 self
.max_frame_size
= None
26 self
.connection
= None
27 self
.controller_name
= None
30 self
, endpoint
, username
, password
, cacert
=None, macaroons
=None):
31 """Connect to an arbitrary Juju controller.
34 self
.connection
= await connection
.Connection
.connect(
35 endpoint
, None, username
, password
, cacert
, macaroons
,
36 max_frame_size
=self
.max_frame_size
)
38 async def connect_current(self
):
39 """Connect to the current Juju controller.
43 await connection
.Connection
.connect_current_controller(
44 max_frame_size
=self
.max_frame_size
))
46 async def connect_controller(self
, controller_name
):
47 """Connect to a Juju controller by name.
51 await connection
.Connection
.connect_controller(
52 controller_name
, max_frame_size
=self
.max_frame_size
))
53 self
.controller_name
= controller_name
55 async def disconnect(self
):
56 """Shut down the watcher task and close websockets.
59 if self
.connection
and self
.connection
.is_open
:
60 log
.debug('Closing controller connection')
61 await self
.connection
.close()
62 self
.connection
= None
65 self
, model_name
, cloud_name
=None, credential_name
=None,
66 owner
=None, config
=None, region
=None):
67 """Add a model to this controller.
69 :param str model_name: Name to give the new model.
70 :param str cloud_name: Name of the cloud in which to create the
71 model, e.g. 'aws'. Defaults to same cloud as controller.
72 :param str credential_name: Name of the credential to use when
73 creating the model. Defaults to current credential. If you
74 pass a credential_name, you must also pass a cloud_name,
75 even if it's the default cloud.
76 :param str owner: Username that will own the model. Defaults to
78 :param dict config: Model configuration.
79 :param str region: Region in which to create the model.
82 model_facade
= client
.ModelManagerFacade
.from_connection(
85 owner
= owner
or self
.connection
.info
['user-info']['identity']
86 cloud_name
= cloud_name
or await self
.get_cloud()
89 credential
= tag
.credential(
91 tag
.untag('user-', owner
),
97 log
.debug('Creating model %s', model_name
)
99 model_info
= await model_facade
.CreateModel(
100 tag
.cloud(cloud_name
),
108 # Add our ssh key to the model, to work around
109 # https://bugs.launchpad.net/juju/+bug/1643076
111 ssh_key
= await utils
.read_ssh_key(loop
=self
.loop
)
113 if self
.controller_name
:
114 model_name
= "{}:{}".format(self
.controller_name
, model_name
)
116 cmd
= ['juju', 'add-ssh-key', '-m', model_name
, ssh_key
]
118 await utils
.execute_process(*cmd
, log
=log
, loop
=self
.loop
)
121 "Could not add ssh key to model. You will not be able "
122 "to ssh into machines in this model. "
123 "Manually running `juju add-ssh-key <key>` in the cli "
124 "may fix this problem.")
128 self
.connection
.endpoint
,
130 self
.connection
.username
,
131 self
.connection
.password
,
132 self
.connection
.cacert
,
133 self
.connection
.macaroons
,
139 async def destroy_models(self
, *uuids
):
140 """Destroy one or more models.
142 :param str \*uuids: UUIDs of models to destroy
145 model_facade
= client
.ModelManagerFacade
.from_connection(
149 'Destroying model%s %s',
150 '' if len(uuids
) == 1 else 's',
154 await model_facade
.DestroyModels([
155 client
.Entity(tag
.model(uuid
))
158 destroy_model
= destroy_models
160 async def add_user(self
, username
, password
=None, display_name
=None):
161 """Add a user to this controller.
163 :param str username: Username
164 :param str display_name: Display name
165 :param str acl: Access control, e.g. 'read'
166 :param list models: Models to which the user is granted access
170 display_name
= username
171 user_facade
= client
.UserManagerFacade
.from_connection(self
.connection
)
172 users
= [{'display_name': display_name
,
173 'password': password
,
174 'username': username
}]
175 return await user_facade
.AddUser(users
)
177 async def change_user_password(self
, username
, password
):
178 """Change the password for a user in this controller.
180 :param str username: Username
181 :param str password: New password
184 user_facade
= client
.UserManagerFacade
.from_connection(self
.connection
)
185 entity
= client
.EntityPassword(password
, tag
.user(username
))
186 return await user_facade
.SetPassword([entity
])
188 async def destroy(self
, destroy_all_models
=False):
189 """Destroy this controller.
191 :param bool destroy_all_models: Destroy all hosted models in the
195 controller_facade
= client
.ControllerFacade
.from_connection(
197 return await controller_facade
.DestroyController(destroy_all_models
)
199 async def disable_user(self
, username
):
202 :param str username: Username
205 user_facade
= client
.UserManagerFacade
.from_connection(self
.connection
)
206 entity
= client
.Entity(tag
.user(username
))
207 return await user_facade
.DisableUser([entity
])
209 async def enable_user(self
, username
):
210 """Re-enable a previously disabled user.
213 user_facade
= client
.UserManagerFacade
.from_connection(self
.connection
)
214 entity
= client
.Entity(tag
.user(username
))
215 return await user_facade
.EnableUser([entity
])
218 """Forcibly terminate all machines and other associated resources for
222 raise NotImplementedError()
224 async def get_cloud(self
):
226 Get the name of the cloud that this controller lives on.
228 cloud_facade
= client
.CloudFacade
.from_connection(self
.connection
)
230 result
= await cloud_facade
.Clouds()
231 cloud
= list(result
.clouds
.keys())[0] # only lives on one cloud
232 return tag
.untag('cloud-', cloud
)
234 async def get_models(self
, all_
=False, username
=None):
235 """Return list of available models on this controller.
237 :param bool all_: List all models, regardless of user accessibilty
239 :param str username: User for which to list models (admin use only)
242 controller_facade
= client
.ControllerFacade
.from_connection(
244 return await controller_facade
.AllModels()
246 def get_payloads(self
, *patterns
):
247 """Return list of known payloads.
249 :param str \*patterns: Patterns to match against
251 Each pattern will be checked against the following info in Juju::
262 raise NotImplementedError()
264 def get_users(self
, all_
=False):
265 """Return list of users that can connect to this controller.
267 :param bool all_: Include disabled users
270 raise NotImplementedError()
273 """Log in to this controller.
276 raise NotImplementedError()
278 def logout(self
, force
=False):
279 """Log out of this controller.
281 :param bool force: Don't fail even if user not previously logged in
285 raise NotImplementedError()
287 def get_model(self
, name
):
288 """Get a model by name.
290 :param str name: Model name
293 raise NotImplementedError()
295 async def get_user(self
, username
, include_disabled
=False):
296 """Get a user by name.
298 :param str username: Username
301 client_facade
= client
.UserManagerFacade
.from_connection(
303 user
= tag
.user(username
)
304 return await client_facade
.UserInfo([client
.Entity(user
)],
307 async def grant(self
, username
, acl
='login'):
308 """Set access level of the given user on the controller
310 :param str username: Username
311 :param str acl: Access control ('login', 'add-model' or 'superuser')
314 controller_facade
= client
.ControllerFacade
.from_connection(
316 user
= tag
.user(username
)
317 await self
.revoke(username
)
318 changes
= client
.ModifyControllerAccess(acl
, 'grant', user
)
319 return await controller_facade
.ModifyControllerAccess([changes
])
321 async def revoke(self
, username
):
322 """Removes all access from a controller
324 :param str username: username
327 controller_facade
= client
.ControllerFacade
.from_connection(
329 user
= tag
.user(username
)
330 changes
= client
.ModifyControllerAccess('login', 'revoke', user
)
331 return await controller_facade
.ModifyControllerAccess([changes
])