@PYTHON@ # vim: set ts=4 et: import os import sys import json import shutil import argparse from datetime import datetime, timedelta from pyhocon import ConfigFactory # Just group together our transforms class Transforms: NOW = datetime.utcnow() TOMORROW = NOW + timedelta(days=1) unquote = lambda x: x.strip('"') @staticmethod def force_iso_date(input): return datetime.fromisoformat(input).isoformat(timespec="seconds") @classmethod def resolve_tomorrow(cls, input): return cls.TOMORROW.isoformat(timespec="seconds") @classmethod def resolve_now(cls, input): return cls.NOW.strftime("%Y%m%d%H%M%S") @classmethod def fold_comma(cls, input): return ",".join([cls.unquote(k) for k in input.keys()]) @classmethod def fold_space(cls, input): return " ".join([cls.unquote(k) for k in input.keys()]) @classmethod def fold_repos(cls, input): return "\n".join( f"@{v} {cls.unquote(k)}" if isinstance(v, str) else cls.unquote(k) for k, v in input.items()) @staticmethod def fold_packages(input): return " ".join( f"{k}@{v}" if isinstance(v, str) else k for k, v in input.items()) @staticmethod def fold_services(input): return " ".join( "{}={}".format(k, ",".join(v.keys())) for k, v in input.items()) class ConfigBuilder: _CFG_TRANSFORMS = { "ami_access" : Transforms.fold_comma, "ami_regions" : Transforms.fold_comma, "kernel_modules" : Transforms.fold_comma, "kernel_options" : Transforms.fold_space, "repos" : Transforms.fold_repos, "pkgs" : Transforms.fold_packages, "svcs" : Transforms.fold_services, "revision" : Transforms.resolve_now, "end_of_life" : lambda x: \ Transforms.force_iso_date(Transforms.resolve_tomorrow(x)), } def __init__(self, config_path, out_dir): self.config_path = config_path self.out_dir = out_dir def build(self, profile): build_config = ConfigFactory.parse_file(self.config_path) for build, cfg in build_config["BUILDS"].items(): build_dir = os.path.join(self.out_dir, build) # Always start fresh shutil.rmtree(build_dir, ignore_errors=True) os.makedirs(build_dir) cfg["profile"] = profile cfg["profile_build"] = build # Order of operations is important here for k, v in cfg.items(): transform = self._CFG_TRANSFORMS.get(k) if transform: cfg[k] = transform(v) if isinstance(v, str) and "{var." in v: cfg[k] = v.format(var=cfg) with open(os.path.join(build_dir, "vars.json"), "w") as out: json.dump(cfg, out, indent=4, separators=(",", ": ")) def find_repo_root(): path = os.getcwd() while ".git" not in set(os.listdir(path)) and path != "/": path = os.path.dirname(path) if path == "/": raise Exception("No repo found, stopping at /") return path def main(args): parser = argparse.ArgumentParser(description="Build Packer JSON variable " "files from HOCON build profiles") parser.add_argument("profile", help="name of profile to build") args = parser.parse_args() root = find_repo_root() ConfigBuilder( os.path.join(root, "profiles", f"{args.profile}.conf"), os.path.join(root, "build", "profile", args.profile) ).build(args.profile) if __name__ == "__main__": main(sys.argv)