"""Boot the rendered image/configuration via Docker, including remote Docker sockets.""" import secrets import subprocess import sys import tempfile import time from pathlib import Path import yaml docs = list(yaml.safe_load_all(Path(sys.argv[1]).read_text())) d = next(d for d in docs if d['kind'] == 'Deployment') c = next(c for c in d['spec']['template']['spec']['containers'] if c['name'] == 'factorio') cms = {d['metadata']['name']: d['data'] for d in docs if d['kind'] == 'ConfigMap'} run = lambda *a, **kw: subprocess.run(a, check=True, **kw) volume = 'factorio-smoke-' + secrets.token_hex(6) run('docker', 'volume', 'create', volume, stdout=subprocess.DEVNULL) container = None try: with tempfile.TemporaryDirectory() as directory: root = Path(directory) for name in ['scripts', 'settings', 'credentials']: (root / name).mkdir() for folder, name in [('scripts', 'factorio-scripts'), ('settings', 'factorio-settings')]: for key, value in cms[name].items(): file = root / folder / key file.write_text(value) file.chmod(0o755 if key.endswith('.sh') else 0o644) (root / 'credentials/rcon-password').write_text(secrets.token_urlsafe(32)) args = ['docker', 'create'] for env in c['env']: args += ['--env', env['name'] + '=' + env['value']] data_mount = next(m['mountPath'] for m in c['volumeMounts'] if m['name'] == 'data') args += ['--volume', f'{volume}:{data_mount}'] args += ['--entrypoint', c['command'][0], c['image'], *c['command'][1:]] container = subprocess.check_output(args, text=True).strip() # docker cp works when the daemon is outside the CI job container; # bind-mounting the job container's /tmp would silently mount a different directory. for mount in c['volumeMounts']: if mount['name'] != 'data': run('docker', 'cp', str(root / mount['name']), container + ':' + mount['mountPath']) run('docker', 'start', container, stdout=subprocess.DEVNULL) def ready(): command = c['readinessProbe']['exec']['command'] for _ in range(90): if subprocess.run(['docker', 'exec', container, *command], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=15).returncode == 0: return time.sleep(2) raise RuntimeError('Server did not pass the production readiness probe') ready() run('docker', 'exec', container, 'rcon', '/server-save') run('docker', 'stop', '--time', str(d['spec']['template']['spec']['terminationGracePeriodSeconds']), container, stdout=subprocess.DEVNULL) run('docker', 'start', container, stdout=subprocess.DEVNULL) ready() run('docker', 'exec', container, 'sh', '-c', 'test -n "$(find "$1/saves" -name "*.zip")" && test -n "$(find "$1/upgrade-backups" -name "*.zip")"', 'sh', data_mount) print('Actual image passed RCON readiness, persistent save, restart, and upgrade-backup checks') finally: if container: run('docker', 'rm', '--force', container, stdout=subprocess.DEVNULL) run('docker', 'volume', 'rm', volume, stdout=subprocess.DEVNULL)