From d69976caa5070403f81799c79be974241cff7f70 Mon Sep 17 00:00:00 2001
From: kali <kali@leap.se>
Date: Thu, 30 Aug 2012 03:43:05 +0900
Subject: fetcher moved to baseconfig + eipchecker using eipservice config.

---
 src/leap/base/config.py               | 42 +++++++++++++++----
 src/leap/base/providers.py            |  2 +-
 src/leap/base/tests/test_providers.py |  2 +-
 src/leap/eip/checks.py                | 76 +++++++++++------------------------
 src/leap/eip/config.py                | 14 +++++++
 src/leap/eip/specs.py                 | 29 +++++++++++++
 src/leap/eip/tests/data.py            |  8 ----
 src/leap/eip/tests/test_checks.py     |  9 +++--
 8 files changed, 109 insertions(+), 73 deletions(-)

(limited to 'src')

diff --git a/src/leap/base/config.py b/src/leap/base/config.py
index 45a5f08a..7a65474a 100644
--- a/src/leap/base/config.py
+++ b/src/leap/base/config.py
@@ -6,12 +6,14 @@ import json
 import logging
 import requests
 import socket
+import tempfile
 import os
 
 logger = logging.getLogger(name=__name__)
 logger.setLevel('DEBUG')
 
 import configuration
+import requests
 
 from leap.base import exceptions
 from leap.base import constants
@@ -55,6 +57,11 @@ class MetaConfigWithSpec(type):
     # XXX in the near future, this is the
     # place where we want to enforce
     # singletons, read-only and stuff.
+
+    # TODO:
+    # - add a error handler for missing options that
+    #   we can act easily upon (sys.exit is ugly, for $deity's sake)
+
     def __new__(meta, classname, bases, classDict):
         spec_options = classDict.get('spec', None)
         # XXX if not spec_options, raise BadConfiguration or something
@@ -102,6 +109,7 @@ class JSONLeapConfig(BaseLeapConfig):
 
         self._config = self.spec()
         self._config.parse_args(list(args))
+        self.fetcher = kwargs.pop('fetcher', requests)
 
     # mandatory baseconfig interface
 
@@ -111,7 +119,7 @@ class JSONLeapConfig(BaseLeapConfig):
         folder, filename = os.path.split(to)
         if folder and not os.path.isdir(folder):
             mkdir_p(folder)
-        # lazy evaluation until first level nest
+        # lazy evaluation until first level of nesting
         # to allow lambdas with context-dependant info
         # like os.path.expanduser
         config = self.get_config()
@@ -120,14 +128,27 @@ class JSONLeapConfig(BaseLeapConfig):
                 config[k] = v()
         self._config.serialize(to)
 
-    def load(self, fromfile=None):
-        # load should get a much more generic
-        # argument. it could be, f.i., from_uri,
-        # and call to Fetcher
-
+    def load(self, fromfile=None, from_uri=None, fetcher=None):
+        if from_uri is not None:
+            fetched = self.fetch(from_uri, fetcher=fetcher)
+            if fetched:
+                return
         if fromfile is None:
             fromfile = self.filename
-        self._config.config = self._config.deserialize(fromfile)
+        newconfig = self._config.deserialize(fromfile)
+        # XXX check for no errors, etc
+        self._config.config = newconfig
+
+    def fetch(self, uri, fetcher=None):
+        if not fetcher:
+            fetcher = self.fetcher
+        request = fetcher.get(uri)
+        request.raise_for_status()
+        fd, fname = tempfile.mkstemp(suffix=".json")
+        with open(fname, 'w') as tmp:
+            tmp.write(json.dumps(request.json))
+        self._loadtemp(fname)
+        return True
 
     def get_config(self):
         return self._config.config
@@ -141,6 +162,12 @@ class JSONLeapConfig(BaseLeapConfig):
     def filename(self):
         return self.get_filename()
 
+    # private
+
+    def _loadtemp(self, filename):
+        self.load(fromfile=filename)
+        os.remove(filename)
+
     def _slug_to_filename(self):
         # is this going to work in winland if slug is "foo/bar" ?
         folder, filename = os.path.split(self.slug)
@@ -157,6 +184,7 @@ class JSONLeapConfig(BaseLeapConfig):
 #
 # (might be moved to some class as we see fit, but
 # let's remain functional for a while)
+# maybe base.config.util ??
 #
 
 
diff --git a/src/leap/base/providers.py b/src/leap/base/providers.py
index 677dd6ec..ce30d4a4 100644
--- a/src/leap/base/providers.py
+++ b/src/leap/base/providers.py
@@ -9,7 +9,7 @@ class LeapProviderDefinition(baseconfig.JSONLeapConfig):
     def _get_slug(self):
         provider_path = baseconfig.get_default_provider_path()
         return baseconfig.get_config_file(
-            'definition.json',
+            'provider-definition.json',
             folder=provider_path)
 
     def _set_slug(self, *args, **kwargs):
diff --git a/src/leap/base/tests/test_providers.py b/src/leap/base/tests/test_providers.py
index 4920be93..23f63a95 100644
--- a/src/leap/base/tests/test_providers.py
+++ b/src/leap/base/tests/test_providers.py
@@ -46,7 +46,7 @@ class TestLeapProviderDefinition(BaseLeapTest):
                 self.home,
                 '.config', 'leap', 'providers',
                 'testprovider.example.org',
-                'definition.json'))
+                'provider-definition.json'))
         with self.assertRaises(AttributeError):
             self.definition.slug = 23
 
diff --git a/src/leap/eip/checks.py b/src/leap/eip/checks.py
index 4b2326a5..b57977f0 100644
--- a/src/leap/eip/checks.py
+++ b/src/leap/eip/checks.py
@@ -8,12 +8,11 @@ logger.setLevel(logging.DEBUG)
 
 import requests
 
-from leap.base import config as baseconfig
 from leap.base import constants as baseconstants
+from leap.base import providers
 from leap.eip import config as eipconfig
 from leap.eip import constants as eipconstants
 from leap.eip import exceptions as eipexceptions
-from leap.util.fileutil import mkdir_p
 
 """
 EIPConfigChecker
@@ -49,10 +48,11 @@ class EIPConfigChecker(object):
         # argument on init.
         # we want tests
         # to be explicitely run.
-        self.config = None
         self.fetcher = fetcher
 
         self.eipconfig = eipconfig.EIPConfig()
+        self.defaultprovider = providers.LeapProviderDefinition()
+        self.eipserviceconfig = eipconfig.EIPServiceConfig()
 
     def run_all(self, checker=None, skip_download=False):
         """
@@ -74,7 +74,7 @@ class EIPConfigChecker(object):
 
         checker.check_is_there_default_provider()
         checker.fetch_definition(skip_download=skip_download)
-        checker.fetch_eip_config(skip_download=skip_download)
+        checker.fetch_eip_service_config(skip_download=skip_download)
         checker.check_complete_eip_config()
         #checker.ping_gateway()
 
@@ -109,8 +109,7 @@ class EIPConfigChecker(object):
         provider = config.get('provider', None)
         if provider is None:
             raise eipexceptions.EIPMissingDefaultProvider
-        if config:
-            self.config = config
+        # XXX raise also if malformed ProviderDefinition?
         return True
 
     def fetch_definition(self, skip_download=False,
@@ -120,65 +119,38 @@ class EIPConfigChecker(object):
         """
         # TODO:
         # - Implement diff
-        # - overwrite if different.
+        # - overwrite only if different.
+        #   (attend to serial field different, for instance)
+
         logger.debug('fetching definition')
 
         if skip_download:
             logger.debug('(fetching def skipped)')
             return True
         if config is None:
-            config = self.config
+            config = self.defaultprovider.get_config()
         if uri is None:
-            if config:
-                domain = config.get('provider', None)
-            else:
-                domain = None
-            uri = self._get_provider_definition_uri(
-                domain=domain)
-
-        # XXX move to JSONConfig Fetcher
-        request = self.fetcher.get(uri)
-        request.raise_for_status()
-
-        definition_file = os.path.join(
-            baseconfig.get_default_provider_path(),
-            baseconstants.DEFINITION_EXPECTED_PATH)
-
-        folder, filename = os.path.split(definition_file)
-        if not os.path.isdir(folder):
-            mkdir_p(folder)
-        with open(definition_file, 'wb') as f:
-            f.write(json.dumps(request.json, indent=4))
-
-    def fetch_eip_config(self, skip_download=False,
-                         config=None, uri=None):
+            domain = config.get('provider', None)
+            uri = self._get_provider_definition_uri(domain=domain)
+
+        self.defaultprovider.load(from_uri=uri, fetcher=self.fetcher)
+        self.defaultprovider.save()
+
+    def fetch_eip_service_config(self, skip_download=False,
+                                 config=None, uri=None):
         if skip_download:
             return True
         if config is None:
-            config = self.config
+            config = self.eipserviceconfig.get_config()
         if uri is None:
-            if config:
-                domain = config.get('provider', None)
-            else:
-                domain = None
-            uri = self._get_eip_service_uri(
-                domain=domain)
-
-        # XXX move to JSONConfig Fetcher
-        request = self.fetcher.get(uri)
-        request.raise_for_status()
-
-        definition_file = os.path.join(
-            baseconfig.get_default_provider_path(),
-            eipconstants.EIP_SERVICE_EXPECTED_PATH)
-
-        folder, filename = os.path.split(definition_file)
-        if not os.path.isdir(folder):
-            mkdir_p(folder)
-        with open(definition_file, 'wb') as f:
-            f.write(json.dumps(request.json, indent=4))
+            domain = config.get('provider', None)
+            uri = self._get_eip_service_uri(domain=domain)
+
+        self.eipserviceconfig.load(from_uri=uri, fetcher=self.fetcher)
+        self.eipserviceconfig.save()
 
     def check_complete_eip_config(self, config=None):
+        # TODO check for gateway
         if config is None:
             config = self.config
         try:
diff --git a/src/leap/eip/config.py b/src/leap/eip/config.py
index a7b24f9b..b6c38a77 100644
--- a/src/leap/eip/config.py
+++ b/src/leap/eip/config.py
@@ -49,6 +49,20 @@ class EIPConfig(baseconfig.JSONLeapConfig):
     slug = property(_get_slug, _set_slug)
 
 
+class EIPServiceConfig(baseconfig.JSONLeapConfig):
+    spec = eipspecs.eipservice_config_spec
+
+    def _get_slug(self):
+        return baseconfig.get_config_file(
+            'eip-service.json',
+            folder=baseconfig.get_default_provider_path())
+
+    def _set_slug(self):
+        raise AttributeError("you cannot set slug")
+
+    slug = property(_get_slug, _set_slug)
+
+
 def check_or_create_default_vpnconf(config):
     """
     checks that a vpn config file
diff --git a/src/leap/eip/specs.py b/src/leap/eip/specs.py
index a39e5979..e617574c 100644
--- a/src/leap/eip/specs.py
+++ b/src/leap/eip/specs.py
@@ -64,3 +64,32 @@ eipconfig_spec = {
         'type': unicode
     }
 }
+
+eipservice_config_spec = {
+    'serial': {
+        'type': int,
+        'required': True,
+        'default': 1
+    },
+    'version': {
+        'type': unicode,
+        'required': True,
+        'default': "0.1.0"
+    },
+    'capabilities': {
+        'type': dict,
+        'default': {
+            "transport": ["openvpn"],
+            "ports": ["80", "53"],
+            "protocols": ["udp", "tcp"],
+            "static_ips": True,
+            "adblock": True}
+    },
+    'gateways': {
+        'type': list,
+        'default': [{"country_code": "us",
+                    "label": {"en":"west"},
+                    "capabilities": {},
+                    "hosts": ["1.2.3.4", "1.2.3.5"]}]
+    }
+}
diff --git a/src/leap/eip/tests/data.py b/src/leap/eip/tests/data.py
index 9067c270..284b398f 100644
--- a/src/leap/eip/tests/data.py
+++ b/src/leap/eip/tests/data.py
@@ -38,13 +38,5 @@ EIP_SAMPLE_SERVICE = {
      "label": {"en":"west"},
      "capabilities": {},
      "hosts": ["1.2.3.4", "1.2.3.5"]},
-    {"country_code": "us",
-     "label": {"en":"east"},
-     "capabilities": {},
-     "hosts": ["1.2.3.4", "1.2.3.5"]},
-    {"country_code": "fr",
-     "label": {},
-     "capabilities": {},
-     "hosts": ["1.2.3.4", "1.2.3.5"]}
     ]
 }
diff --git a/src/leap/eip/tests/test_checks.py b/src/leap/eip/tests/test_checks.py
index 5697ad10..1e629203 100644
--- a/src/leap/eip/tests/test_checks.py
+++ b/src/leap/eip/tests/test_checks.py
@@ -40,7 +40,8 @@ class EIPCheckTest(BaseLeapTest):
         self.assertTrue(hasattr(checker, "check_is_there_default_provider"),
                         "missing meth")
         self.assertTrue(hasattr(checker, "fetch_definition"), "missing meth")
-        self.assertTrue(hasattr(checker, "fetch_eip_config"), "missing meth")
+        self.assertTrue(hasattr(checker, "fetch_eip_service_config"),
+                        "missing meth")
         self.assertTrue(hasattr(checker, "check_complete_eip_config"),
                         "missing meth")
         self.assertTrue(hasattr(checker, "ping_gateway"), "missing meth")
@@ -55,7 +56,7 @@ class EIPCheckTest(BaseLeapTest):
                         "not called")
         self.assertTrue(mc.fetch_definition.called,
                         "not called")
-        self.assertTrue(mc.fetch_eip_config.called,
+        self.assertTrue(mc.fetch_eip_service_config.called,
                         "not called")
         self.assertTrue(mc.check_complete_eip_config.called,
                         "not called")
@@ -133,13 +134,13 @@ class EIPCheckTest(BaseLeapTest):
         # (and proper EIPExceptions are raised).
         # Look at base.test_config.
 
-    def test_fetch_eip_config(self):
+    def test_fetch_eip_service_config(self):
         with patch.object(requests, "get") as mocked_get:
             mocked_get.return_value.status_code = 200
             mocked_get.return_value.json = testdata.EIP_SAMPLE_SERVICE
             checker = eipchecks.EIPConfigChecker(fetcher=requests)
             sampleconfig = testdata.EIP_SAMPLE_JSON
-            checker.fetch_definition(config=sampleconfig)
+            checker.fetch_eip_service_config(config=sampleconfig)
 
     def test_check_complete_eip_config(self):
         checker = eipchecks.EIPConfigChecker()
-- 
cgit v1.2.3