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")
"""
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
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)
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.
self.password,
self.cacert,
self.macaroons,
+ self.loop,
)
async def controller(self):
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()
"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.
"""
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.
"""
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: <controller>:<model>
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()
"macaroons": macaroons or []
}})
response = result['response']
- self.build_facades(response['facades'])
+ self.build_facades(response.get('facades', {}))
self.info = response.copy()
return response
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')
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 []