connection test
authorTim Van Steenburgh <tvansteenburgh@gmail.com>
Mon, 23 May 2016 20:36:01 +0000 (16:36 -0400)
committerTim Van Steenburgh <tvansteenburgh@gmail.com>
Mon, 23 May 2016 20:36:01 +0000 (16:36 -0400)
Makefile
juju/client/connection.py
setup.py
tox.ini

index 88d58dc..0812d53 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -3,5 +3,10 @@ PY := .tox/py35/bin/python3.5
 .tox:
        tox -r --notest
 
+.phony: client
 client:
        $(PY) -m juju.client.facade -s juju/client/schemas.json -o juju/client/client.py
+
+.phony: test
+test:
+       tox
index 4d9ee1c..9a631ec 100644 (file)
@@ -1,23 +1,38 @@
 import asyncio
+import io
 import json
 import logging
+import os
 import random
 import ssl
 import string
 import websockets
 
+import yaml
 
 log = logging.getLogger("websocket")
 
+
 class Connection:
     """
-    Usage:
-        client  = await Connection.connect(info)
+    Usage::
+
+        # Connect to an arbitrary api server
+        client = await Connection.connect(
+            api_endpoint, model_uuid, username, password, cacert)
+
+        # Connect using a controller/model name
+        client = await Connection.connect_model('local.local:default')
+
+        # Connect to the currently active model
+        client = await Connection.connect_current()
+
     """
     def __init__(self):
-        self.__requestId__ = 0
+        self.__request_id__ = 0
         self.addr = None
         self.ws = None
+        self.facades = {}
 
     def _get_ssl(self, cert):
         return ssl.create_default_context(
@@ -41,8 +56,8 @@ class Connection:
         return result
 
     async def rpc(self, msg):
-        self.__requestID += 1
-        msg['RequestId'] = self.__requestID
+        self.__request_id__ += 1
+        msg['RequestId'] = self.__request_id__
         if'Params' not in msg:
             msg['Params'] = {}
         if "Version" not in msg:
@@ -56,36 +71,102 @@ class Connection:
         return result
 
     @classmethod
-    async def connect(cls, info):
-        uuid = info.model_uuid
-        controller = info.controller
-        details = controller['details']
-        endpoint = "wss://{}/model/{}/api".format(
-            details['api-endpoints'][0],
-            entity.model_uuid)
+    async def connect(cls, endpoint, uuid, username, password, cacert=None):
+        url = "wss://{}/model/{}/api".format(endpoint, uuid)
         client = cls()
-        await client.dial(endpoint, details['ca-cert'])
-        server_info = await client.login(info)
+        await client.open(url, cacert)
+        server_info = await client.login(username, password)
         client.build_facades(server_info['facades'])
         log.info("Driver connected to juju %s", endpoint)
         return client
 
+    @classmethod
+    async def connect_current(cls):
+        """Connect to the currently active model.
+
+        """
+        jujudata = JujuData()
+        controller_name = jujudata.current_controller()
+        controller = jujudata.controllers()[controller_name]
+        endpoint = controller['api-endpoints'][0]
+        cacert = controller.get('ca-cert')
+        accounts = jujudata.accounts()[controller_name]
+        username = accounts['current-account']
+        password = accounts['accounts'][username]['password']
+        models = jujudata.models()[controller_name]['accounts'][username]
+        model_name = models['current-model']
+        model_uuid = models['models'][model_name]['uuid']
+
+        return await cls.connect(
+            endpoint, model_uuid, username, password, cacert)
+
+    @classmethod
+    async def connect_model(cls, model):
+        """Connect to a model by name.
+
+        :param str model: <controller>:<model>
+
+        """
+        controller_name, model_name = model.split(':')
+
+        jujudata = JujuData()
+        controller = jujudata.controllers()[controller_name]
+        endpoint = controller['api-endpoints'][0]
+        cacert = controller.get('ca-cert')
+        accounts = jujudata.accounts()[controller_name]
+        username = accounts['current-account']
+        password = accounts['accounts'][username]['password']
+        models = jujudata.models()[controller_name]['accounts'][username]
+        model_uuid = models['models'][model_name]['uuid']
+
+        return await cls.connect(
+            endpoint, model_uuid, username, password, cacert)
+
     def build_facades(self, info):
         self.facades.clear()
         for facade in info:
             self.facades[facade['Name']] = facade['Versions'][-1]
 
-    async def login(self, info):
-        account = info.account
-        result = await client.rpc({
+    async def login(self, username, password):
+        if not username.startswith('user-'):
+            username = 'user-{}'.format(username)
+
+        result = await self.rpc({
             "Type": "Admin",
             "Request": "Login",
             "Version": 3,
             "Params": {
-                "auth-tag": "user-{}".format(account['user']),
-                "credentials": account['password'],
+                "auth-tag": username,
+                "credentials": password,
                 "Nonce": "".join(random.sample(string.printable, 12)),
             }})
         return result['Response']
 
 
+class JujuData:
+    def __init__(self):
+        self.path = os.environ.get('JUJU_DATA') or '~/.local/share/juju'
+        self.path = os.path.abspath(os.path.expanduser(self.path))
+
+    def current_controller(self):
+        try:
+            filepath = os.path.join(self.path, 'current-controller')
+            with io.open(filepath, 'rt') as f:
+                return f.read().strip()
+        except OSError as e:
+            log.exception(e)
+            return None
+
+    def controllers(self):
+        return self._load_yaml('controllers.yaml', 'controllers')
+
+    def models(self):
+        return self._load_yaml('models.yaml', 'controllers')
+
+    def accounts(self):
+        return self._load_yaml('accounts.yaml', 'controllers')
+
+    def _load_yaml(self, filename, key):
+        filepath = os.path.join(self.path, filename)
+        with io.open(filepath, 'rt') as f:
+            return yaml.safe_load(f)[key]
index adea67f..7a7e110 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,10 @@ setup(
     version="0.0.1",
     packages=find_packages(
         exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
-    install_requires=[],
+    install_requires=[
+        'websockets',
+        'pyyaml',
+    ],
     include_package_data=True,
     maintainer='Juju Ecosystem Engineering',
     maintainer_email='juju@lists.ubunut.com',
diff --git a/tox.ini b/tox.ini
index 0f55e6a..e57cea1 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -7,6 +7,8 @@
 envlist = py35
 
 [testenv]
-commands = py.test
+passenv =
+    HOME
+commands = py.test -rsx -s
 deps =
     pytest