--- 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(