1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
@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)
|