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]
|
monitor[token_key] = new_monitor[token_key]
|
||||||
merged = merge(base, local, remote)
|
merged = merge(base, local, remote)
|
||||||
override = delta(remote, merged)
|
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:
|
with tempfile.NamedTemporaryFile(mode='w', dir=root, prefix='.update-compose-', suffix='.json', delete=False) as candidate:
|
||||||
json.dump(serialize(merged), candidate)
|
json.dump(serialize(merged), candidate)
|
||||||
candidate_path = Path(candidate.name)
|
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_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 = Path(tempfile.mkdtemp(prefix='legacy-update-' + time.strftime('%Y%m%d-%H%M%S') + '-', dir=backup_root))
|
||||||
backup.chmod(0o700)
|
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 = backup / name
|
||||||
destination.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
destination.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||||
shutil.copy2(root / name, destination)
|
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)
|
config_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||||
override_path = config_dir / 'legacy-compose.json'
|
override_path = config_dir / 'legacy-compose.json'
|
||||||
if override is not MISSING and override:
|
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.write_text(json.dumps(serialize(override), indent=2) + '\n')
|
||||||
override_path.chmod(0o600)
|
override_path.chmod(0o600)
|
||||||
env_path = root / '.env'
|
env_path = root / '.env'
|
||||||
@@ -192,7 +215,7 @@ for name in collisions:
|
|||||||
(root / name).unlink()
|
(root / name).unlink()
|
||||||
if changed:
|
if changed:
|
||||||
subprocess.run(['git', 'restore', '--source=HEAD', '--staged', '--worktree', '--', 'docker-compose.yml'], check=True)
|
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)
|
(backup / 'migration.json').chmod(0o600)
|
||||||
print('✅ 已备份旧配置并迁移本地差异;新版部署字段由仓库继续管理')
|
print('✅ 已备份旧配置并迁移本地差异;新版部署字段由仓库继续管理')
|
||||||
print(' 备份目录: ' + str(backup))
|
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.
|
# A second adoption preserves the result and creates no extra backup.
|
||||||
checked(['python3', str(prepare), str(root)], root, env)
|
checked(['python3', str(prepare), str(root)], root, env)
|
||||||
assert len(list((root / 'data/deployment-backups').glob('legacy-update-*'))) == 1
|
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')):
|
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'}
|
env = {**os.environ, 'UPDATE_MODE': mode, 'UPDATE_ADOPT_LEGACY_CONFIG': adopt, 'UPDATE_RESTORE_REPOSITORY_IMAGES': 'easyai-web'}
|
||||||
|
|||||||
Reference in New Issue
Block a user