fix(deploy): 保留已有覆盖配置并支持重复迁移
This commit is contained in:
@@ -150,6 +150,29 @@ if token_key in old_monitor and token_key not in monitor and token_key in new_mo
|
||||
monitor[token_key] = new_monitor[token_key]
|
||||
merged = merge(base, local, remote)
|
||||
override = delta(remote, merged)
|
||||
config_dir = root / 'data/deployment-config'
|
||||
override_path = config_dir / 'legacy-compose.json'
|
||||
|
||||
def apply_override(original, additional):
|
||||
if isinstance(original, dict) and isinstance(additional, dict):
|
||||
result = copy.deepcopy(original)
|
||||
for key, value in additional.items():
|
||||
result[key] = apply_override(result.get(key, MISSING), value)
|
||||
return result
|
||||
return copy.deepcopy(additional)
|
||||
|
||||
# Repeated adoption must preserve already migrated settings. A newly reviewed
|
||||
# main-file difference has the same precedence as the old main file; only
|
||||
# explicitly restored target images are released back to the repository.
|
||||
if override_path.exists():
|
||||
if override_path.is_symlink() or not override_path.is_file():
|
||||
sys.exit('❌ 旧配置覆盖文件类型不受支持;尚未修改配置或容器')
|
||||
previous_override = canonical(json.loads(override_path.read_text()))
|
||||
for service in restore_images:
|
||||
previous_override.get('services', {}).get(service, {}).pop('image', None)
|
||||
override = apply_override(previous_override, {} if override is MISSING else override)
|
||||
merged = apply_override(remote, override)
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', dir=root, prefix='.update-compose-', suffix='.json', delete=False) as candidate:
|
||||
json.dump(serialize(merged), candidate)
|
||||
candidate_path = Path(candidate.name)
|
||||
@@ -165,7 +188,9 @@ backup_root = root / 'data/deployment-backups'
|
||||
backup_root.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
backup = Path(tempfile.mkdtemp(prefix='legacy-update-' + time.strftime('%Y%m%d-%H%M%S') + '-', dir=backup_root))
|
||||
backup.chmod(0o700)
|
||||
for name in changed | collisions | {'.env'}:
|
||||
backup_files = changed | collisions | {'.env'}
|
||||
if override_path.exists(): backup_files.add(str(override_path.relative_to(root)))
|
||||
for name in backup_files:
|
||||
destination = backup / name
|
||||
destination.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
shutil.copy2(root / name, destination)
|
||||
@@ -174,8 +199,6 @@ config_dir = root / 'data/deployment-config'
|
||||
config_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
override_path = config_dir / 'legacy-compose.json'
|
||||
if override is not MISSING and override:
|
||||
if override_path.exists():
|
||||
sys.exit('❌ 已存在旧配置迁移结果;请检查备份后再重试,尚未修改仓库')
|
||||
override_path.write_text(json.dumps(serialize(override), indent=2) + '\n')
|
||||
override_path.chmod(0o600)
|
||||
env_path = root / '.env'
|
||||
@@ -192,7 +215,7 @@ for name in collisions:
|
||||
(root / name).unlink()
|
||||
if changed:
|
||||
subprocess.run(['git', 'restore', '--source=HEAD', '--staged', '--worktree', '--', 'docker-compose.yml'], check=True)
|
||||
(backup / 'migration.json').write_text(json.dumps({'from': git('rev-parse', 'HEAD').strip(), 'to': git('rev-parse', upstream).strip(), 'preservedFields': preserved, 'restoredRepositoryImages': sorted(restore_images), 'files': sorted(changed | collisions)}, indent=2))
|
||||
(backup / 'migration.json').write_text(json.dumps({'from': git('rev-parse', 'HEAD').strip(), 'to': git('rev-parse', upstream).strip(), 'preservedFields': preserved, 'restoredRepositoryImages': sorted(restore_images), 'files': sorted(backup_files)}, indent=2))
|
||||
(backup / 'migration.json').chmod(0o600)
|
||||
print('✅ 已备份旧配置并迁移本地差异;新版部署字段由仓库继续管理')
|
||||
print(' 备份目录: ' + str(backup))
|
||||
|
||||
@@ -86,6 +86,22 @@ for restore in ('', 'easyai-web', 'redis', 'ws-gateway'):
|
||||
# A second adoption preserves the result and creates no extra backup.
|
||||
checked(['python3', str(prepare), str(root)], root, env)
|
||||
assert len(list((root / 'data/deployment-backups').glob('legacy-update-*'))) == 1
|
||||
# A later main-file customization is adopted without dropping the
|
||||
# existing override or changing an unrelated dependency setting.
|
||||
main_path = root / 'docker-compose.yml'
|
||||
main_path.write_text(main_path.read_text().replace('LOCAL_SETTING=original', 'LOCAL_SETTING=second-custom'))
|
||||
override_file = root / 'data/deployment-config/legacy-compose.json'
|
||||
previous_override = override_file.read_bytes()
|
||||
checked(['python3', str(prepare), str(root)], root, env)
|
||||
config = json.loads(checked(['docker', 'compose', '--env-file', '.env', 'config', '--format', 'json'], root))
|
||||
assert config['services']['easyai-server']['environment']['LOCAL_SETTING'] == 'second-custom'
|
||||
assert config['services']['redis']['image'] == 'redis:6.2'
|
||||
assert config['services']['easyai-web']['image'] == ('repository-web:latest' if restore else pin)
|
||||
backups = list((root / 'data/deployment-backups').glob('legacy-update-*'))
|
||||
assert len(backups) == 2
|
||||
newest = max(backups, key=lambda p: p.stat().st_mtime)
|
||||
assert (newest / 'data/deployment-config/legacy-compose.json').read_bytes() == previous_override
|
||||
assert (newest / 'data/deployment-config/legacy-compose.json').stat().st_mode & 0o077 == 0
|
||||
|
||||
for mode, adopt in (('image', '1'), ('full', '0')):
|
||||
env = {**os.environ, 'UPDATE_MODE': mode, 'UPDATE_ADOPT_LEGACY_CONFIG': adopt, 'UPDATE_RESTORE_REPOSITORY_IMAGES': 'easyai-web'}
|
||||
|
||||
Reference in New Issue
Block a user