Files
IKAR/tool/deploy_messaging_fix.py

66 lines
2.9 KiB
Python

"""Update only the IKAR API container, with backup and rollback."""
from pathlib import Path
import json
import os
import shutil
import subprocess
import tarfile
import time
import urllib.request
root = Path('/mnt/data/ikar_server')
release = root / 'releases/20261006-1.0.1'
current = root / 'current'
previous = current.resolve(strict=True)
if not os.path.ismount('/mnt/data') or not previous.is_relative_to(root / 'releases') or release.exists():
raise RuntimeError('Unexpected release or storage state')
def snapshot():
containers = subprocess.check_output(['docker', 'ps', '-q'], text=True).split()
return {item['Name']: {'id': item['Id'], 'startedAt': item['State']['StartedAt']}
for item in json.loads(subprocess.check_output(['docker', 'inspect', *containers], text=True))
if item['Name'] != '/ikar_server'}
protected_before = snapshot()
(root / 'deployment-1.0.1-before.json').write_text(json.dumps(protected_before, indent=2))
backup = subprocess.check_output(['python3', str(previous / 'backup.py')], text=True).strip()
shutil.copytree(previous, release, ignore=shutil.ignore_patterns('publish', 'release.tar.gz'))
with tarfile.open(root / 'messaging-fix.tar.gz') as archive:
archive.extractall(release, filter='data')
compose = release / 'compose.yaml'
text = compose.read_text()
if 'image: ikar_server:20261006' not in text:
raise RuntimeError('Unexpected API image in compose')
compose.write_text(text.replace('image: ikar_server:20261006', 'image: ikar_server:20261006-1.0.1', 1))
with (root / 'build-1.0.1.log').open('w') as log:
subprocess.run(['docker', 'build', '-f', 'Dockerfile.release', '-t', 'ikar_server:20261006-1.0.1', '.'],
cwd=release, stdout=log, stderr=subprocess.STDOUT, check=True)
def update(path):
subprocess.run(['docker', 'compose', '--project-directory', str(path), '--env-file', str(root / 'shared/turn.env'),
'up', '-d', '--no-deps', 'ikar_server'], check=True)
try:
update(release)
health = None
for attempt in range(30):
try:
with urllib.request.urlopen('http://127.0.0.1:5099/health', timeout=3) as response:
health = json.load(response)
if health.get('status') == 'ok':
break
except Exception:
pass
time.sleep(1)
if health is None or health.get('status') != 'ok':
raise RuntimeError('New API failed health verification')
pending = root / 'current.next'
pending.symlink_to(release, target_is_directory=True)
os.replace(pending, current)
except Exception:
update(previous)
raise
report = {
'release': str(release), 'previousRelease': str(previous), 'backup': backup, 'health': health,
'protectedServicesUnchanged': snapshot() == protected_before,
}
(root / 'deployment-1.0.1.json').write_text(json.dumps(report, indent=2))
print(json.dumps(report))