pvl.hosts.config: support direct directory hosts, using the directory name as the parent
authorTero Marttila <tero.marttila@aalto.fi>
Thu, 26 Feb 2015 18:07:03 +0200
changeset 510 368a568412ed
parent 509 ae53114766fc
child 511 99043eab9140
pvl.hosts.config: support direct directory hosts, using the directory name as the parent
pvl/hosts/config.py
pvl/hosts/tests.py
--- a/pvl/hosts/config.py	Thu Feb 26 17:40:12 2015 +0200
+++ b/pvl/hosts/config.py	Thu Feb 26 18:07:03 2015 +0200
@@ -172,19 +172,21 @@
         # single host
         yield apply_host(name, domain, config)
 
-def apply_hosts_includes (options, config_path, include):
+def parse_config_includes (options, config_path, includes, **opts):
     """
-        Yield files from a given config's include=... value
+        Yield file paths from a given config's include=... value
     """
-        
+    
+    # relative
     include_paths = [os.path.dirname(config_path)]
     
     if options.hosts_include:
         include_paths.append[options.hosts_include]
 
-    for include in include.split():
+    for include in includes:
         for include_path in include_paths:
             path = os.path.join(include_path, include)
+
             if os.path.exists(path):
                 break
         else:
@@ -193,21 +195,9 @@
                     include_path=' '.join(include_paths),
             ))
 
-        if include.endswith('/'):
-            for name in os.listdir(path):
-                file_path = os.path.join(path, name)
 
-                if name.startswith('.'):
-                    pass
-                elif not os.path.exists(file_path):
-                    log.warn("%s: skip nonexistant include: %s", config_path, file_path)
-                else:
-                    log.info("%s: include: %s", config_path, file_path)
-                    yield open(file_path)
-
-        else:
-            log.info("%s: include: %s", config_path, path)
-            yield open(path)
+        log.info("%s: include: %s", config_path, path)
+        yield path
 
 def apply_hosts_configs (options, path, name, config, parent=None, defaults={}):
     """
@@ -227,12 +217,16 @@
         section[scalar] = config[scalar]
 
     # process includes?
-    includes = section.pop('include', '')
+    if 'include' in section:
+        includes = section.pop('include').split()
 
-    for file in apply_hosts_includes(options, path, includes):
+        includes = list(parse_config_includes(options, path, includes))
+
         # within our domain context
-        for host in apply_hosts_file(options, file, parent=name):
+        for host in apply_hosts_files(options, includes, parent=name):
             yield host
+    else:
+        includes = None
 
     if config.sections:
         # this is a top-level section that includes hosts
@@ -264,15 +258,17 @@
         # includes-only zone
         pass
 
+    elif section:
+        raise HostConfigError(path, "Top-level hosts are only allowed in included confs")
     else:
-        raise HostConfigError(path, "No sections in config")
+        # empty file
+        log.info("%s: skip empty conf", path)
 
-def apply_hosts_file (options, file, parent=None):
+def apply_hosts_config (options, file, **opts):
     """
         Load Hosts from a file path.
-
-            path:str        filesystem path
-            parent          domain from included file, or --hosts-domain
+            
+            file            - opened file object, with .name attribute
 
         Raises
             HostConfigError
@@ -292,23 +288,55 @@
     except configobj.ConfigObjError as ex:
         raise HostConfigObjError(path, ex)
     
-    return apply_hosts_configs(options, path, name, config, parent=parent)
+    return apply_hosts_configs(options, path, name, config, **opts)
 
-def apply_hosts_files (options, files):
+def apply_hosts_file (options, path, **opts):
+    """
+        Load Hosts from a file path.
+    """
+
+    try:
+        file = open(path)
+    except IOError as ex:
+        raise HostConfigError(path, ex.strerror)
+
+    for host in apply_hosts_config(options, file, **opts):
+        yield host
+
+def apply_hosts_directory (options, path, parent=None, **opts):
+    """
+        Load Hosts from a directory, loading each file within the directory.
+
+        Skips .dotfiles.
+    """
+
+    if parent is None:
+        # use directory name
+        parent = os.path.basename(path.rstrip('/'))
+
+    for name in os.listdir(path):
+        file_path = os.path.join(path, name)
+
+        if name.startswith('.'):
+            continue
+
+        for host in apply_hosts_file(options, file_path, parent=parent, **opts):
+            yield host
+
+def apply_hosts_files (options, files, **opts):
     """
         Load Hosts from files.
 
-            files:[str]     list of filesystem paths
+            files:[str]     list of filesystem paths, which may be directories or files
     """
 
     for path in files:
-        try:
-            file = open(path)
-        except IOError as ex:
-            raise HostConfigError(path, ex.strerror)
-
-        for host in apply_hosts_file(options, file, parent=options.hosts_domain):
-            yield host
+        if os.path.isdir(path):
+            for host in apply_hosts_directory(options, path, **opts):
+                yield host
+        else:
+            for host in apply_hosts_file(options, path, **opts):
+                yield host
 
 def apply (options, args):
     """
@@ -331,3 +359,26 @@
 
     # stable ordering
     return sorted(hosts, key=Host.sort_key)
+
+
+def apply (options, args):
+    """
+        Load Hosts from arguments.
+
+        Exits with status=2 if loading the confs fails.
+    """
+    
+    try:
+        # load hosts from configs
+        hosts = list(apply_hosts_files(options, args))
+    except HostConfigObjError as error:
+        log.error("%s", error)
+        log.error("\t%s", error.line_contents)
+        sys.exit(2)
+
+    except HostConfigError as error:
+        log.error("%s", error)
+        sys.exit(2)
+
+    # stable ordering
+    return sorted(hosts, key=Host.sort_key)
--- a/pvl/hosts/tests.py	Thu Feb 26 17:40:12 2015 +0200
+++ b/pvl/hosts/tests.py	Thu Feb 26 18:07:03 2015 +0200
@@ -112,7 +112,7 @@
                 ('bar@test', dict(ip=ipaddr.IPAddress('127.0.0.2'))),
         ]
 
-        self.assertHostsEqual(config.apply_hosts_file(self.options, conf_file), expected)
+        self.assertHostsEqual(config.apply_hosts_config(self.options, conf_file), expected)
 
     def testApplyIncludes(self):
         self.assertHostsEqual(config.apply_hosts_files(self.options, ['etc/hosts/includes.test']), [
@@ -127,6 +127,16 @@
                 )),
         ])
 
+    def testApplyDirectory(self):
+        self.assertHostsEqual(config.apply_hosts_files(self.options, ['etc/hosts/included.test/']), [
+                ('foo@included.test', dict(
+                    ip          = ipaddr.IPAddress('192.0.2.1'),
+                )),
+                ('bar@included.test', dict(
+                    ip          = ipaddr.IPAddress('192.0.2.2'),
+                )),
+        ])
+
     def testApply(self):
         self.assertHostsEqual(config.apply(self.options, ['etc/hosts/test']), [
                 ('foo@test', dict(