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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
@PYTHON@
# vim: set ts=4 et:
import json
import os
import shutil
import sys
import boto3
from botocore.exceptions import ClientError
from datetime import datetime, timedelta
from pyhocon import ConfigFactory
if len(sys.argv) != 2:
sys.exit("Usage: " + os.path.basename(__file__) + " <profile>")
PROFILE = sys.argv[1]
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
# path to the profile config file
PROFILE_CONF = os.path.join(SCRIPT_DIR, '..', 'profiles', PROFILE + '.conf')
# load the profile's build configuration
BUILDS = ConfigFactory.parse_file(PROFILE_CONF)['BUILDS']
# where we store the profile's builds' config/output
PROFILE_DIR = os.path.join(SCRIPT_DIR, 'profile', PROFILE)
if not os.path.exists(PROFILE_DIR):
os.makedirs(PROFILE_DIR)
# fold these build config keys' dict to scalar
FOLD_DICTS = {
'ami_access': ',{0}',
'ami_regions': ',{0}',
'repos': "\n@{1} {0}",
'pkgs': ' {0}@{1}',
'kernel_modules': ',{0}',
'kernel_options': ' {0}'
}
NOW = datetime.utcnow()
ONE_DAY = timedelta(days=1)
# func to fold dict down to scalar
def fold(fdict, ffmt):
folded = ''
for fkey, fval in fdict.items():
fkey = fkey.strip('"') # complex keys may be in quotes
if fval is True:
folded += ffmt[0] + fkey
elif fval not in [None, False]:
folded += ffmt.format(fkey, fval)
return folded[1:]
# list of AWS regions, and whether they're enabled
all_regions = {}
AWS = boto3.session.Session()
sys.stderr.write("\n>>> Determining region availability...")
sys.stderr.flush()
for region in AWS.get_available_regions('ec2'):
ec2 = AWS.client('ec2', region_name=region)
try:
ec2.describe_regions()
except ClientError as e:
if e.response['Error']['Code'] == 'AuthFailure':
sys.stderr.write('-')
sys.stderr.flush()
all_regions[region] = False
continue
elif e.response['Error']['Code'] == 'UnauthorizedOperation':
# have access to the region, but not to ec2:DescribeRegions
pass
else:
raise
sys.stderr.write('+')
sys.stderr.flush()
all_regions[region] = True
sys.stderr.write("\n")
# parse/resolve HOCON profile's builds' config
for build, cfg in BUILDS.items():
print(f"\n>>> Resolving configuration for '{build}'")
build_dir = os.path.join(PROFILE_DIR, build)
# make a fresh profile build directory
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
os.makedirs(build_dir)
# populate profile build vars
cfg['profile'] = PROFILE
cfg['profile_build'] = build
# mostly edge-related temporal substitutions
if cfg['end_of_life'] == '@TOMORROW@':
cfg['end_of_life'] = (NOW + ONE_DAY).isoformat(timespec='seconds')
elif cfg['end_of_life'] is not None:
# to explicitly UTC-ify end_of_life
cfg['end_of_life'] = datetime.fromisoformat(
cfg['end_of_life'] + '+00:00').isoformat(timespec='seconds')
if cfg['revision'] == '@NOW@':
cfg['revision'] = NOW.strftime('%Y%m%d%H%M%S')
# 'ALL' region expansion (or retraction)
if 'ALL' in cfg['ami_regions']:
all_val = cfg['ami_regions']['ALL']
if all_val not in [None, False]:
cfg['ami_regions'] = all_regions
else:
cfg['ami_regions'] = {}
else:
# warn/remove disabled regions
for region, enabled in all_regions.items():
if enabled is not False or region not in cfg['ami_regions']:
continue
if cfg['ami_regions'][region] not in [None, False]:
sys.stderr.write(f"*** WARNING: skipping disabled region {region}\n")
cfg['ami_regions'][region] = False
# fold dict vars to scalars
for foldkey, foldfmt in FOLD_DICTS.items():
cfg[foldkey] = fold(cfg[foldkey], foldfmt)
# fold 'svcs' dict to scalar
lvls = {}
for svc, lvl in cfg['svcs'].items():
if lvl is True:
# service in default runlevel
lvls['default'].append(svc)
elif lvl not in [None, False]:
# service in specified runlevel (skip svc when false/null)
if lvl not in lvls.keys():
lvls[lvl] = []
lvls[lvl].append(svc)
cfg['svcs'] = ' '.join(
str(lvl) + '=' + ','.join(
str(svc) for svc in svcs
) for lvl, svcs in lvls.items()
)
# resolve ami_name and ami_desc
cfg['ami_name'] = cfg['ami_name'].format(var=cfg)
cfg['ami_desc'] = cfg['ami_desc'].format(var=cfg)
# write build vars file
with open(os.path.join(build_dir, 'vars.json'), 'w') as out:
json.dump(cfg, out, indent=4, separators=(',', ': '))
print()
|