import string
import subprocess
import websockets
+from http.client import HTTPSConnection
import yaml
-from juju.errors import JujuAPIError
+from juju import tag
+from juju.errors import JujuAPIError, JujuConnectionError
log = logging.getLogger("websocket")
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.
redirect_info = await client.redirect_info()
if not redirect_info:
- server_info = await client.login(username, password, macaroons)
- client.build_facades(server_info['facades'])
+ await client.login(username, password, macaroons)
return client
await client.close()
result = await client.login(username, password, macaroons)
if 'discharge-required-error' in result:
continue
- client.build_facades(result['facades'])
return client
except Exception as e:
await client.close()
return await cls.connect_model(
'{}:{}'.format(controller_name, model_name))
+ @classmethod
+ async def connect_current_controller(cls):
+ """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)
+
+ @classmethod
+ async def connect_controller(cls, controller_name):
+ """Connect to a controller by name.
+
+ """
+ jujudata = JujuData()
+ controller = jujudata.controllers()[controller_name]
+ endpoint = controller['api-endpoints'][0]
+ cacert = controller.get('ca-cert')
+ accounts = jujudata.accounts()[controller_name]
+ username = accounts['user']
+ password = accounts.get('password')
+ macaroons = get_macaroons() if not password else None
+
+ return await cls.connect(
+ endpoint, None, username, password, cacert, macaroons)
+
@classmethod
async def connect_model(cls, model):
"""Connect to a model by name.
"nonce": "".join(random.sample(string.printable, 12)),
"macaroons": macaroons or []
}})
- return result['response']
+ response = result['response']
+ self.build_facades(response.get('facades', {}))
+ self.info = response.copy()
+ return response
async def redirect_info(self):
try:
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')