M bem/bem_cli.py => bem/bem_cli.py +9 -12
@@ 54,7 54,6 @@ def bem_cli():
bem_util.setup_logging("ERROR")
elif args.debug:
bem_util.setup_logging("DEBUG")
- else:
bem_util.setup_logging("INFO")
logging.debug("Starting BEM version {}".format(bem_constants.version))
@@ 75,28 74,26 @@ def bem_cli():
logging.debug("using bemfile: '{}'".format(bemfile))
- config = configparser.ConfigParser()
- config.read(bemfile)
+ configreader = configparser.ConfigParser()
+ configreader.read(bemfile)
+
+ config = bem_config.load_config(configreader, args, bemfile.parent)
- min_version = bem_config.get_key(config, args, bemfile.parent,
- "minimum_version")
- if not bem_util.compare_versions(bem_constants.version, min_version):
+ if not bem_util.compare_versions(bem_constants.version, config["minimum_version"]):
logging.error("BEM version '{}' too old, at least version '{}' is required"
.format(bem_constants.version, min_version))
sys.exit(1)
- logging.debug("project dir: {}".format(
- bem_config.get_key(config, args, bemfile.parent, "projectdir")))
- logging.debug("dockerfile: {}".format(
- bem_config.get_key(config, args, bemfile.parent, "dockerfile")))
+ logging.debug("project dir: {}".format(config["projectdir"]))
+ logging.debug("dockerfile: {}".format(config["dockerfile"]))
try:
- bem_engine.validate_config(config, args, bemfile.parent)
+ bem_engine.validate_config(config)
except Exception as e:
logging.error("Configuration error: {}".format(e))
sys.exit(1)
- bem_engine.execute_command(config, args, bemfile.parent, args.command)
+ bem_engine.execute_command(config, args.command)
if __name__ == "__main__":
bem_cli()
M bem/bem_config.py => bem/bem_config.py +44 -52
@@ 1,24 1,15 @@
import logging
import configparser
+import copy
from . import bem_constants
+from . import bem_util
-def apply_indirection(inifile, args, ini_dir, val):
+def load_config(inifile, args, ini_dir):
+ """load_config
- # there isn't a good way to enumerate what keys are available, so we
- # make a dummy that portends to be a dict, but really just calls get_key()
- class DummyDict:
- def __getitem__(this, key):
- return get_key(inifile, args, ini_dir, key, True)
-
- def __missing__(this, key):
- logging.error("unknown indirect key '{}'".format(key))
-
- dummy = DummyDict()
- return str(val).format_map(dummy)
-
-def get_key(inifile, args, ini_dir, key, indirection=True):
- """get_key
+ Load a configuration dictionary from the INI file, CLI arguments, and so
+ on, resolving any indirect values.
:param inifile: INI file as loaded by configparser.ConfigParser.read
:param args: args file from argparse
@@ 26,65 17,66 @@ def get_key(inifile, args, ini_dir, key, indirection=True):
:param indirection: set to True to allow indirection
:param ini_dir: parent directory of the loaded INI file
- Retrieves the specified config key. Order of precedence is:
+ Order of precedence is:
* runtime keys
* arguments
* INI file
* bem_constants.config_defaults
- Returned value will always be a string. Throws a KeyError if there is no
- such config value.
+ Returns a dict.
"""
- logging.debug("retrieving config key '{}'".format(key))
-
-
- runtime = {
- "bem_file_dir": ini_dir,
- "version": bem_constants.version
- }
-
- result = None
+ # grab all defaults
+ config = copy.deepcopy(bem_constants.config_defaults)
- # check values provided by arguments
- arguments = {}
+ # load from INI file
+ for key in inifile["BEM"]:
+ config[key] = inifile["BEM"][key]
+ # handle arguments
if args.dockerfile is not None:
- arguments["dockerfile"] = args.dockerfile
+ config["dockerfile"] = args.dockerfile
if args.projectdir is not None:
- arguments["projectdir"] = args.projectdir
+ config["projectdir"] = args.projectdir
if args.logfile is not None:
- arguments["logfile"] = args.logfile
+ config["logfile"] = args.logfile
if args.noninteractive is not None:
- arguments["noninteractive"] = args.noninteractive
+ config["noninteractive"] = args.noninteractive
- if key in runtime:
- logging.debug("\trequested key found in runtime: '{}'".format(runtime[key]))
- result = runtime[key]
+ # runtime
+ config["bem_file_dir"] = ini_dir
+ config["version"] = bem_constants.version
- elif key in arguments:
- result = arguments[key]
- logging.debug("\tkey found in arguments: '{}'".format(result))
+ logging.debug("unresolved config... ")
+ bem_util.log_pretty(logging.debug, config)
- elif key in inifile["BEM"]:
- logging.debug("\trequested key found in INI file: '{}'".format(inifile["BEM"][key]))
- result = inifile["BEM"][key]
+ iterations = 0
+ while True:
- elif key in bem_constants.config_defaults:
- logging.debug("\trequested key found in defaults: '{}'".format(bem_constants.config_defaults[key]))
- result = bem_constants.config_defaults[key]
+ # check if we're done resolving indirect values
+ count = 0
+ for key in config:
+ if '{' in str(config[key]):
+ count += 1
- if result is not None:
- if indirection:
- return apply_indirection(inifile, args, ini_dir, result)
- else:
- return result
+ if count == 0:
+ return config
- logging.error("No such config key: '{}'".format(key))
- raise KeyError("No such config key: '{}'".format(key))
+ if iterations > 1000:
+ raise Exception(
+ "Too many levels of indirection, cannot load config. Offending keys: {}"
+ .format([k for k in config if '{' in config[k]]))
+ for key in config:
+ # perform the next iteration of indirection
+ if '{' in str(config[key]):
+ logging.debug("resolving indirect value for key '{}': "
+ .format(key, config[key]))
+ config[key] = config[key].format_map(config)
+ logging.debug("\tresolved to: {}".format(config[key]))
+ iterations += 1
M bem/bem_engine.py => bem/bem_engine.py +14 -16
@@ 9,31 9,29 @@ import os
from . import bem_config
from . import bem_util
-def validate_config(config, args, ini_dir):
+def validate_config(config):
"""validate_config
Sanity check a given runtime configuration. Throw an exception if there is
a problem.
:param config:
- :param args:
- :param ini_dir:
"""
logging.debug("validating config... ")
try:
- dockerfile = pathlib.Path(bem_config.get_key(config, args, ini_dir, "dockerfile"))
+ dockerfile = pathlib.Path(config["dockerfile"])
except Exception as e:
raise Exception("unable to get key 'dockerfile', this may indicate a problem with your BEMfile; reason: {}".format(e))
try:
- projectdir = pathlib.Path(bem_config.get_key(config, args, ini_dir, "projectdir"))
+ projectdir = pathlib.Path(config["projectdir"])
except Exception as e:
raise Exception("unable to get key 'projectdir', this may indicate a problem with your BEMfile; reason: {}".format(e))
try:
- cachedir = pathlib.Path(bem_config.get_key(config, args, ini_dir, "cachedir"))
+ cachedir = pathlib.Path(config["cachedir"])
except Exception as e:
raise Exception("unable to get key 'cachedir', this may indicate a problem with your BEMfile; reason: {}".format(e))
@@ 50,14 48,14 @@ def validate_config(config, args, ini_dir):
.format(cachedir))
try:
- bool(bem_config.get_key(config, args, ini_dir, "noninteractive"))
+ bem_util.parse_bool(config["noninteractive"])
except Exception as e:
raise Exception("Invalid value for 'noninteractive'; reason: {}".format(e))
logging.debug("config looks ok")
-def ensure_image_built(config, args, ini_dir):
+def ensure_image_built(config):
"""ensure_image_built
Ensure the docker image for the particular project is built.
@@ 71,8 69,8 @@ def ensure_image_built(config, args, ini_dir):
logging.info("building image... ")
- dockerfile = pathlib.Path(bem_config.get_key(config, args, ini_dir, "dockerfile"))
- projectdir = pathlib.Path(bem_config.get_key(config, args, ini_dir, "projectdir"))
+ dockerfile = pathlib.Path(config["dockerfile"])
+ projectdir = pathlib.Path(config["projectdir"])
client = docker.from_env()
image = client.images.build(path=str(projectdir), dockerfile=str(dockerfile))
@@ 82,12 80,12 @@ def ensure_image_built(config, args, ini_dir):
return image[0].id
-def execute_command(config, args, ini_dir, command):
- image = ensure_image_built(config, args, ini_dir)
- noninteractive = bem_util.parse_bool(bem_config.get_key(config, args, ini_dir, "noninteractive"))
- logfile = str(bem_config.get_key(config, args, ini_dir, "logfile"))
- flush_interval = int(bem_config.get_key(config, args, ini_dir, "flush_interval"))
- projectdir = pathlib.Path(bem_config.get_key(config, args, ini_dir, "projectdir"))
+def execute_command(config, command):
+ image = ensure_image_built(config)
+ noninteractive = bem_util.parse_bool(config["noninteractive"])
+ logfile = str(config["logfile"])
+ flush_interval = int(config["flush_interval"])
+ projectdir = pathlib.Path(config["projectdir"])
client = docker.from_env()
M bem/bem_util.py => bem/bem_util.py +1 -0
@@ 1,4 1,5 @@
import logging
+import pprint
import pathlib
from . import bem_config