2ef5df0927
* switch build name from 'current-x86_64' to 'v#_#-x86_64' to avoid any confusion when new versions roll out * resolvie-alpine.py.in - only warn about disabled regions once, instead of for each profile build * make-amis - tweak script output * new set of AMIs for edge, 3.10.0, and 3.9.4
157 lines
4.7 KiB
Python
157 lines
4.7 KiB
Python
@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")
|
|
|
|
for region, available in all_regions.items():
|
|
if available is False:
|
|
sys.stderr.write(f"*** WARNING: skipping disabled region {region}\n")
|
|
|
|
print()
|
|
|
|
# parse/resolve HOCON profile's builds' config
|
|
for build, cfg in BUILDS.items():
|
|
print(f">>> 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]:
|
|
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()
|