#!/usr/bin/env python3 """Verify the WAR, record exact source changes, and compare shared UAT containers.""" import collections import hashlib import json import os from pathlib import Path import shutil import struct import subprocess import sys import zipfile os.umask(0o077) run = Path(sys.argv[1]).resolve() phase = sys.argv[2] assert (run / 'logs' / (phase + '.exit-code')).read_text().strip() == '0' log = (run / 'logs' / (phase + '.log')).read_text(errors='replace') assert 'BUILD SUCCESS' in log source = run / 'source' war = source / 'target/syly.war' required = [ 'WEB-INF/web.xml', 'WEB-INF/classes/com/zzc/web/system/controller/core/LoginController.class', 'WEB-INF/classes/com/zzc/core/interceptors/AuthInterceptor.class', 'WEB-INF/classes/com/zzc/web/htoal/controller/HotelMonthlyController.class', ] with zipfile.ZipFile(war) as archive: bad = archive.testzip() assert bad is None, bad names = archive.namelist() assert all(name in names for name in required) classes = [name for name in names if name.startswith('WEB-INF/classes/') and name.endswith('.class')] major_versions = collections.Counter(struct.unpack('>H', archive.read(name)[6:8])[0] for name in classes) assert set(major_versions) == {52}, major_versions libs = [name for name in names if name.startswith('WEB-INF/lib/') and name.endswith('.jar')] manifest = json.loads((run / 'source-manifest.json').read_text()) changes = [] for relative, expected in manifest.items(): actual = hashlib.sha256((source / relative).read_bytes()).hexdigest() if expected != actual: changes.append({'path': relative, 'original_sha256': expected, 'build_sha256': actual}) expected_changes = { 'pom.xml', 'src/main/java/com/zzc/web/htoal/controller/HotelQuarterStatisticController.java', 'src/main/java/com/zzc/web/travel/controller/InlandStatisticPageController.java', } assert {change['path'] for change in changes} == expected_changes, changes ids = subprocess.check_output(['docker', 'ps', '-aq'], text=True).split() fmt = '{"id":{{json .Id}},"name":{{json .Name}},"startedAt":{{json .State.StartedAt}},"running":{{json .State.Running}},"restartCount":{{json .RestartCount}}}' after = [json.loads(subprocess.check_output(['docker', 'inspect', '--format', fmt, cid], text=True)) for cid in ids] before = json.loads((run / 'containers-before.json').read_text()) key = lambda values: {value['id']: value for value in values} unchanged = key(before) == key(after) (run / 'containers-after.json').write_text(json.dumps(after, indent=2) + '\n') artifact = run / 'artifacts/syly.war' shutil.copyfile(war, artifact) artifact.chmod(0o600) evidence = { 'host': '192.168.68.223', 'run': str(run), 'phase': phase, 'build_success': True, 'artifact': str(artifact), 'artifact_bytes': artifact.stat().st_size, 'artifact_sha256': hashlib.sha256(artifact.read_bytes()).hexdigest(), 'zip_crc_check': 'passed', 'required_entries_present': required, 'compiled_class_files': len(classes), 'class_major_versions': dict(major_versions), 'bundled_jars': len(libs), 'main_java_sources': len(list((source / 'src/main/java').rglob('*.java'))), 'extra_java_sources_outside_maven_root': len(list((source / 'src/main/com').rglob('*.java'))), 'source_manifest_files': len(manifest), 'changed_source_files': changes, 'tests_skipped': 'Tests are skipped' in log, 'containers_before': len(before), 'containers_after': len(after), 'existing_containers_unchanged': unchanged, 'built_at': next((line.split('Finished at:', 1)[1].strip() for line in log.splitlines() if 'Finished at:' in line), None), 'elapsed': next((line.split('Total time:', 1)[1].strip() for line in log.splitlines() if 'Total time:' in line), None), 'java_version': subprocess.check_output([str(Path.home() / '.local/opt/syly2-build/corretto-8.512.09.1/bin/java'), '-version'], stderr=subprocess.STDOUT, text=True).strip(), } (run / 'artifacts/verification.json').write_text(json.dumps(evidence, indent=2) + '\n') print(json.dumps(evidence, indent=2)) if not unchanged: print('NOTICE: Container inventory changed during the build; inspect before/after records.')