X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=juju%2Fclient%2Fconnection.py;h=3011a8adfb2ae307e23ea2dd45cedcbb00990eda;hb=896db9ff2ab6afeb9756948c771bddae942e2723;hp=452754e3f960f0ce32f7151cc9423bd99ef32e76;hpb=6ba2856fecf224ae3fd589331e889a6587e8153b;p=osm%2FN2VC.git diff --git a/juju/client/connection.py b/juju/client/connection.py index 452754e..3011a8a 100644 --- a/juju/client/connection.py +++ b/juju/client/connection.py @@ -9,10 +9,13 @@ import ssl import string import subprocess import websockets +from http.client import HTTPSConnection +import asyncio import yaml -from juju.errors import JujuAPIError +from juju import tag +from juju.errors import JujuAPIError, JujuConnectionError log = logging.getLogger("websocket") @@ -34,13 +37,14 @@ class Connection: """ def __init__( self, endpoint, uuid, username, password, cacert=None, - macaroons=None): + macaroons=None, loop=None): self.endpoint = endpoint self.uuid = uuid self.username = username self.password = password self.macaroons = macaroons self.cacert = cacert + self.loop = loop or asyncio.get_event_loop() self.__request_id__ = 0 self.addr = None @@ -65,6 +69,7 @@ class Connection: kw = dict() kw['ssl'] = self._get_ssl(self.cacert) + kw['loop'] = self.loop self.addr = url self.ws = await websockets.connect(url, **kw) log.info("Driver connected to juju %s", url) @@ -93,6 +98,52 @@ class Connection: raise JujuAPIError(result) return result + def http_headers(self): + """Return dictionary of http headers necessary for making an http + connection to the endpoint of this Connection. + + :return: Dictionary of headers + + """ + if not self.username: + return {} + + creds = u'{}:{}'.format( + tag.user(self.username), + self.password or '' + ) + token = base64.b64encode(creds.encode()) + return { + 'Authorization': 'Basic {}'.format(token.decode()) + } + + def https_connection(self): + """Return an https connection to this Connection's endpoint. + + Returns a 3-tuple containing:: + + 1. The :class:`HTTPSConnection` instance + 2. Dictionary of auth headers to be used with the connection + 3. The root url path (str) to be used for requests. + + """ + endpoint = self.endpoint + host, remainder = endpoint.split(':', 1) + port = remainder + if '/' in remainder: + port, _ = remainder.split('/', 1) + + conn = HTTPSConnection( + host, int(port), + context=self._get_ssl(self.cacert), + ) + + path = ( + "/model/{}".format(self.uuid) + if self.uuid else "" + ) + return conn, self.http_headers(), path + async def clone(self): """Return a new Connection, connected to the same websocket endpoint as this one. @@ -105,6 +156,7 @@ class Connection: self.password, self.cacert, self.macaroons, + self.loop, ) async def controller(self): @@ -118,19 +170,21 @@ class Connection: self.password, self.cacert, self.macaroons, + self.loop, ) @classmethod async def connect( cls, endpoint, uuid, username, password, cacert=None, - macaroons=None): + macaroons=None, loop=None): """Connect to the websocket. If uuid is None, the connection will be to the controller. Otherwise it will be to the model. """ - client = cls(endpoint, uuid, username, password, cacert, macaroons) + client = cls(endpoint, uuid, username, password, cacert, macaroons, + loop) await client.open() redirect_info = await client.redirect_info() @@ -161,7 +215,7 @@ class Connection: "Couldn't authenticate to %s", endpoint) @classmethod - async def connect_current(cls): + async def connect_current(cls, loop=None): """Connect to the currently active model. """ @@ -171,20 +225,22 @@ class Connection: model_name = models['current-model'] return await cls.connect_model( - '{}:{}'.format(controller_name, model_name)) + '{}:{}'.format(controller_name, model_name), loop) @classmethod - async def connect_current_controller(cls): + async def connect_current_controller(cls, loop=None): """Connect to the currently active controller. """ jujudata = JujuData() controller_name = jujudata.current_controller() + if not controller_name: + raise JujuConnectionError('No current controller') - return await cls.connect_controller(controller_name) + return await cls.connect_controller(controller_name, loop) @classmethod - async def connect_controller(cls, controller_name): + async def connect_controller(cls, controller_name, loop=None): """Connect to a controller by name. """ @@ -198,10 +254,10 @@ class Connection: macaroons = get_macaroons() if not password else None return await cls.connect( - endpoint, None, username, password, cacert, macaroons) + endpoint, None, username, password, cacert, macaroons, loop) @classmethod - async def connect_model(cls, model): + async def connect_model(cls, model, loop=None): """Connect to a model by name. :param str model: : @@ -217,11 +273,12 @@ class Connection: username = accounts['user'] password = accounts.get('password') models = jujudata.models()[controller_name] + model_name = '{}/{}'.format(username, model_name) model_uuid = models['models'][model_name]['uuid'] macaroons = get_macaroons() if not password else None return await cls.connect( - endpoint, model_uuid, username, password, cacert, macaroons) + endpoint, model_uuid, username, password, cacert, macaroons, loop) def build_facades(self, info): self.facades.clear() @@ -271,10 +328,10 @@ class JujuData: self.path = os.path.abspath(os.path.expanduser(self.path)) def current_controller(self): - cmd = shlex.split('juju show-controller --format yaml') + cmd = shlex.split('juju list-controllers --format yaml') output = subprocess.check_output(cmd) output = yaml.safe_load(output) - return list(output.keys())[0] + return output.get('current-controller', '') def controllers(self): return self._load_yaml('controllers.yaml', 'controllers') @@ -299,7 +356,7 @@ def get_macaroons(): cookie_file = os.path.expanduser('~/.go-cookies') with open(cookie_file, 'r') as f: cookies = json.load(f) - except (OSError, ValueError) as e: + except (OSError, ValueError): log.warn("Couldn't load macaroons from %s", cookie_file) return []