#!/usr/bin/env python3 """Real Git/config adoption regression; container mutation is not used here.""" import json import os from pathlib import Path import shutil import subprocess import tempfile import time source = Path(__file__).resolve().parent.parent started = time.monotonic() def run(args, root, env=None): return subprocess.run(args, cwd=root, env=env, capture_output=True, text=True, timeout=30) with tempfile.TemporaryDirectory(prefix='easyai-legacy-update-') as directory: base = Path(directory) root = base / 'easyai' root.mkdir() (root / 'docker-compose.yml').write_text('services:\n easyai-server:\n image: server:old\n environment:\n - EXISTING=keep\n watchtower:\n image: watcher:old\n environment:\n - WATCHTOWER_HTTP_API_TOKEN=${LEGACY_TOKEN}\n') (root / '.gitignore').write_text('.env\ndata/\n') (root / '.env').write_text('VERSION=old\nCONFIG_JWT_SECRET=keep-this-private-key\n') for args in [['git','init','-q'], ['git','config','user.name','Update test'], ['git','config','user.email','test@example.invalid'], ['git','add','.'], ['git','commit','-qm','test: 旧部署基准']]: assert run(args, root).returncode == 0 remote = base / 'remote.git' seed = base / 'seed' assert run(['git','clone','-q','--bare',str(root),str(remote)], base).returncode == 0 assert run(['git','remote','add','origin',str(remote)], root).returncode == 0 assert run(['git','fetch','-q'],root).returncode == 0 branch = run(['git','branch','--show-current'], root).stdout.strip() assert run(['git','branch','--set-upstream-to','origin/'+branch],root).returncode == 0 assert run(['git','clone','-q',str(remote),str(seed)],base).returncode == 0 (seed / 'docker-compose.yml').write_text('''services: easyai-server: image: server:new environment: - EXISTING=keep - MODEL_CONVERSION_WORKER_TOKEN=${MODEL_CONVERSION_WORKER_TOKEN} watchtower: image: watcher:new environment: - WATCHTOWER_HTTP_API_TOKEN=${WATCHTOWER_API_KEY} easyai-updater: image: updater:new environment: - UPDATE_MANIFEST_FILE=/etc/manifest.json volumes: - ./data/update-state:/state - ./system-update/release-manifest.json:/etc/manifest.json:ro networks: easyai: ipv4_address: 172.21.0.31 model-conversion-worker: image: worker:new volumes: - accepted-data:/fixture networks: easyai: {} volumes: accepted-data: {} ''') (seed / 'scripts').mkdir() (seed / 'scripts/init-security-env.sh').write_text('# modern security initializer\n') (seed / 'demo.example.conf').write_text('# upstream example\n') for args in [['git','config','user.name','Update test'],['git','config','user.email','test@example.invalid'],['git','add','.'],['git','commit','-qm','test: 新增转换服务与独立更新地址'],['git','push','-q']]: assert run(args,seed).returncode == 0 with (root / 'docker-compose.yml').open('a') as stream: stream.write(''' easyai-updater: image: updater:new environment: - UPDATE_MANIFEST_FILE=/state/custom.json volumes: - ./data/update-state:/state networks: - easyai ''') (root / 'scripts').mkdir() (root / 'scripts/init-security-env.sh').write_text('# local legacy initializer\n') (root / 'demo.example.conf').write_text('# custom nginx source; active copy elsewhere\n') original = (root / 'docker-compose.yml').read_bytes().replace(b' environment:\n - WATCHTOWER_HTTP_API_TOKEN=${LEGACY_TOKEN}\n', b'') (root / 'docker-compose.yml').write_bytes(original) bins = base / 'bin' bins.mkdir() docker = bins / 'docker' docker.write_text('#!/bin/sh\nexit 0\n') docker.chmod(0o755) env = {**os.environ, 'PATH': str(bins)+os.pathsep+os.environ['PATH']} legacy = run(['git','pull','--ff-only'],root) assert legacy.returncode != 0 and 'would be overwritten' in legacy.stderr assert original == (root / 'docker-compose.yml').read_bytes() (root / 'business.js').write_text('local business code\n') assert run(['git','add','business.js'],root).returncode == 0 rejected = run(['python3',str(source/'scripts/prepare-update-repository.py'),str(root)],root,env) assert rejected.returncode != 0 assert original == (root / 'docker-compose.yml').read_bytes() assert run(['git','reset','-q','HEAD','--','business.js'],root).returncode == 0 accepted = run(['python3',str(source/'scripts/prepare-update-repository.py'),str(root)],root,env) assert accepted.returncode == 0, accepted.stdout+accepted.stderr assert run(['git','pull','--ff-only'],root).returncode == 0 assert run(['git','diff','--name-only'],root).stdout == '' override=json.loads((root/'data/deployment-config/legacy-compose.json').read_text()) assert override == {'services': {'easyai-updater': {'environment': {'UPDATE_MANIFEST_FILE': '/state/custom.json'}}}}, override assert 'COMPOSE_FILE=docker-compose.yml:data/deployment-config/legacy-compose.json' in (root/'.env').read_text() assert 'CONFIG_JWT_SECRET=keep-this-private-key' in (root/'.env').read_text() backups=list((root/'data/deployment-backups').glob('legacy-update-*')) assert len(backups)==1 assert (backups[0]/'docker-compose.yml').read_bytes()==original assert (backups[0]/'demo.example.conf').read_text().startswith('# custom nginx') assert (backups[0]/'.env').stat().st_mode & 0o077 == 0 assert (backups[0]).stat().st_mode & 0o077 == 0 resolved=run(['docker','compose','--env-file','.env','config','--format','json'],root) assert resolved.returncode==0,resolved.stderr config=json.loads(resolved.stdout) assert config['services']['easyai-updater']['networks']['easyai']['ipv4_address']=='172.21.0.31' assert config['services']['easyai-updater']['environment']['UPDATE_MANIFEST_FILE']=='/state/custom.json' assert 'model-conversion-worker' in config['services'] assert 'accepted-data' in config['volumes'] assert 'WATCHTOWER_HTTP_API_TOKEN' in config['services']['watchtower']['environment'] repeat=run(['python3',str(source/'scripts/prepare-update-repository.py'),str(root)],root,env) assert repeat.returncode==0 assert len(list((root/'data/deployment-backups').glob('legacy-update-*')))==1 print('legacy Git conflict: reproduced; code guard, private backups, local override, new Worker and updater address passed') print(json.dumps({'passed':True,'durationSeconds':round(time.monotonic()-started,3)}))