X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-base.git/blobdiff_plain/c4e6b2806febb24d73d79ec91431e0deaa8cee24..310c5346861980c50ac8cd5113198ff0eb04b390:/python/invirt/config.py diff --git a/python/invirt/config.py b/python/invirt/config.py index 0e906d5..e4aad28 100644 --- a/python/invirt/config.py +++ b/python/invirt/config.py @@ -2,13 +2,70 @@ from __future__ import with_statement import json from invirt.common import * +import os from os import rename from os.path import getmtime from contextlib import closing +import yaml +import re -src_path = '/etc/invirt/master.yaml' -cache_path = '/var/lib/invirt/cache.json' -lock_path = '/var/lib/invirt/cache.lock' +try: loader = yaml.CSafeLoader +except: loader = yaml.SafeLoader + +src_path = '/etc/invirt/master.yaml' +src_dirpath = '/etc/invirt/conf.d' +cache_path = '/var/lib/invirt/cache.json' +lock_path = '/var/lib/invirt/cache.lock' + +def augment(d1, d2): + """Splice dict-tree d2 into d1. Return d1. + + Example: + >>> d = {'a': {'b': 1}, 'c': 2} + >>> augment(d, {'a': {'d': 3}}) + {'a': {'b', 1, 'd': 3}, 'c': 2} + >>> d + {'a': {'b', 1, 'd': 3}, 'c': 2} + """ + for k in d2: + if k in d1 and isinstance(d1[k], dict): + augment(d1[k], d2[k]) + else: + d1[k] = d2[k] + return d1 + +def run_parts_list(dirname): + """Reimplements Debian's run-parts --list. + + One difference from run-parts's behavior: run-parts --list /foo/ + will give output like /foo//bar, but run_parts_list('/foo/') gives + /foo/bar in deference to Python conventions. + + Matches documented behavior of run-parts in debianutils v2.28.2, dated 2007. + """ + # From run-parts(8). + lanana_re = re.compile('^[a-z0-9]+$') + lsb_re = re.compile('^_?([a-z0-9_.]+-)+[a-z0-9]+$') + deb_cron_re = re.compile('^[a-z0-9][a-z0-9-]*$') + for name in os.listdir(dirname): + if lanana_re.match(name) or lsb_re.match(name) or deb_cron_re.match(name): + yield os.path.join(dirname, name) + +def list_files(): + yield src_path + for name in run_parts_list(src_dirpath): + yield name + +def load_master(): + config = dict() + for filename in list_files(): + with closing(file(filename)) as f: + augment(config, yaml.load(f, loader)) + return config + +def get_src_mtime(): + return max(max(getmtime(filename) for filename in list_files()), + getmtime(src_dirpath)) def load(force_refresh = False): """ @@ -25,7 +82,7 @@ def load(force_refresh = False): if force_refresh: do_refresh = True else: - src_mtime = getmtime(src_path) + src_mtime = get_src_mtime() try: cache_mtime = getmtime(cache_path) except OSError: do_refresh = True else: do_refresh = src_mtime + 1 >= cache_mtime @@ -66,21 +123,16 @@ def load(force_refresh = False): # is interleaved). The final atomic rename is to keep this # transactionally isolated from the above cache read. If we fail to # acquire the lock, just try to load the master configuration. - import yaml - try: loader = yaml.CSafeLoader - except: loader = yaml.SafeLoader try: with lock_file(lock_path): - with closing(file(src_path)) as f: - ns.cfg = yaml.load(f, loader) + ns.cfg = load_master() try: with closing(file(cache_path + '.tmp', 'w')) as f: f.write(json.write(ns.cfg)) except: pass # silent failure else: rename(cache_path + '.tmp', cache_path) except IOError: - with closing(file(src_path)) as f: - ns.cfg = yaml.load(f, loader) + ns.cfg = load_master() return ns.cfg dicts = load()