X-Git-Url: http://xvm.mit.edu/gitweb/invirt/packages/invirt-base.git/blobdiff_plain/f2acfa0565a11ed251ad390461a5c428640681c2..30641d7872dd5f494c965b4d9f01e8d8910dc4f4:/python/invirt/config.py diff --git a/python/invirt/config.py b/python/invirt/config.py index 69cb899..039ca3d 100644 --- a/python/invirt/config.py +++ b/python/invirt/config.py @@ -2,17 +2,76 @@ 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 -default_src_path = '/etc/invirt/master.yaml' -default_cache_path = '/var/lib/invirt/cache.json' -lock_path = '/var/lib/invirt/cache.lock' +try: loader = yaml.CSafeLoader +except: loader = yaml.SafeLoader -def load(src_path = default_src_path, - cache_path = default_cache_path, - force_refresh = False): +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. + + d2 may be None for an empty dict-tree, because yaml.load produces that. + + 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} + """ + if d2 is None: + return d1 + 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): """ Try loading the configuration from the faster-to-load JSON cache at cache_path. If it doesn't exist or is outdated, load the configuration @@ -27,7 +86,7 @@ def load(src_path = default_src_path, 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 @@ -68,24 +127,20 @@ def load(src_path = default_src_path, # 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() -structs = dicts2struct(dicts) +structs = dicts2struct(dicts, '') +safestructs = dicts2struct(dicts, '', '') # vim:et:sw=4:ts=4