alpine-zdt-images/scripts/resolve-profile.py.in
Mike Crute 1fd42af98d Refactor resolve-profile script
This is paving the way for identity broker improvements for opt-in
regions. The output is functionally identical between the two scripts
modulo the svcs change. Hopefully this makes the transformation process
a little more clear.
2020-05-21 16:45:17 -07:00

134 lines
3.6 KiB
Python

@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)