diff --git a/.gitignore b/.gitignore index 6f577d7..0cbfdf2 100644 --- a/.gitignore +++ b/.gitignore @@ -106,3 +106,6 @@ pip-wheel-metadata docs/themes/ docs/public/ resources/_gen/ + +# Misc +.dockertidy* diff --git a/dockertidy/Cli.py b/dockertidy/Cli.py index 7c41439..e7a9f56 100644 --- a/dockertidy/Cli.py +++ b/dockertidy/Cli.py @@ -31,6 +31,7 @@ class DockerTidy: parser.add_argument( "--dry-run", action="store_true", + default=None, dest="dry_run", help="Only log actions, don't stop anything." ) @@ -52,7 +53,7 @@ class DockerTidy: "--version", action="version", version="%(prog)s {}".format(__version__) ) - subparsers = parser.add_subparsers(help="sub-command help") + subparsers = parser.add_subparsers(dest="command", help="sub-command help") parser_gc = subparsers.add_parser("gc", help="Run docker garbage collector.") parser_gc.add_argument( diff --git a/dockertidy/Config.py b/dockertidy/Config.py index ee6c7e7..ec82397 100644 --- a/dockertidy/Config.py +++ b/dockertidy/Config.py @@ -14,6 +14,7 @@ import dockertidy.Exception import dockertidy.Parser from dockertidy.Parser import env from dockertidy.Utils import Singleton +from dockertidy.Utils import dict_intersect config_dir = AppDirs("docker-tidy").user_config_dir default_config_file = os.path.join(config_dir, "config.yml") @@ -29,6 +30,9 @@ class Config(): """ SETTINGS = { + "command": { + "default": "", + }, "config_file": { "default": "", "env": "CONFIG_FILE", @@ -134,9 +138,12 @@ class Config(): return normalized - def _get_defaults(self): + def _get_defaults(self, files=False): normalized = {} + for key, item in self.SETTINGS.items(): + if files and not item.get("file"): + continue normalized = self._add_dict_branch(normalized, key.split("."), item["default"]) self.schema = anyconfig.gen_schema(normalized) @@ -165,6 +172,7 @@ class Config(): args = self._get_args(self._args) envs = self._get_envs() defaults = self._get_defaults() + files_raw = self._get_defaults(files=True) # preset config file path if envs.get("config_file"): @@ -179,23 +187,25 @@ class Config(): source_files.append(os.path.join(os.getcwd(), ".dockertidy.yml")) source_files.append(os.path.join(os.getcwd(), ".dockertidy.yaml")) - for config in source_files: - if config and os.path.exists(config): - with open(config, "r", encoding="utf8") as stream: - s = stream.read() - try: - file_dict = ruamel.yaml.safe_load(s) - except ( - ruamel.yaml.composer.ComposerError, ruamel.yaml.scanner.ScannerError - ) as e: - message = "{} {}".format(e.context, e.problem) - raise dockertidy.Exception.ConfigError( - "Unable to read config file {}".format(config), message - ) + for config in [i for i in source_files if os.path.exists(i)]: + with open(config, "r", encoding="utf8") as stream: + s = stream.read() + try: + normalized = ruamel.yaml.safe_load(s) + except (ruamel.yaml.composer.ComposerError, ruamel.yaml.scanner.ScannerError) as e: + message = "{} {}".format(e.context, e.problem) + raise dockertidy.Exception.ConfigError( + "Unable to read config file {}".format(config), message + ) - if self._validate(file_dict): - anyconfig.merge(defaults, file_dict, ac_merge=anyconfig.MS_DICTS) - defaults["logging"]["level"] = defaults["logging"]["level"].upper() + if self._validate(normalized): + anyconfig.merge(files_raw, normalized, ac_merge=anyconfig.MS_DICTS) + files_raw["logging"]["level"] = files_raw["logging"]["level"].upper() + + files = dict_intersect(files_raw, self._get_defaults(files=True)) + + if self._validate(files): + anyconfig.merge(defaults, files, ac_merge=anyconfig.MS_DICTS) if self._validate(envs): anyconfig.merge(defaults, envs, ac_merge=anyconfig.MS_DICTS) diff --git a/dockertidy/Utils.py b/dockertidy/Utils.py index f6aabd6..3b89e48 100644 --- a/dockertidy/Utils.py +++ b/dockertidy/Utils.py @@ -8,6 +8,13 @@ def to_bool(string): return bool(strtobool(str(string))) +def dict_intersect(d1, d2): + return { + k: dict_intersect(d1[k], d2[k]) if isinstance(d1[k], dict) else d1[k] + for k in d1.keys() & d2.keys() + } + + class Singleton(type): """Singleton metaclass."""