#!/usr/bin/env python3 """Приводит папку приложения к присланной копии: app_sync.py . Файлы переписываются по месту (тот же inode): смонтированный в контейнер файл остается тем же файлом. Лишние файлы удаляются, пустые папки за ними тоже. Печатает JSON: {"changed": [...], "removed": [...], "created": true, если папки приложения не было}. """ import json import os import shutil import stat import sys def files(root): result = set() for directory, _, names in os.walk(root): for name in names: result.add(os.path.relpath(os.path.join(directory, name), root)) return result def same(src, dst): if not os.path.isfile(dst) or os.path.islink(dst): return False if stat.S_IMODE(os.stat(src).st_mode) != stat.S_IMODE(os.stat(dst).st_mode): return False with open(src, 'rb') as a, open(dst, 'rb') as b: return a.read() == b.read() def clear(path): """Убирает то, что мешает положить файл: папку или ссылку на его месте.""" if os.path.islink(path) or (os.path.exists(path) and not os.path.isdir(path)): os.remove(path) elif os.path.isdir(path): shutil.rmtree(path) def ensure_dir(app_dir, relative): """Создает папки до файла; файл или ссылка на месте папки удаляется.""" current = app_dir for part in relative.split(os.sep)[:-1]: current = os.path.join(current, part) if os.path.islink(current) or (os.path.exists(current) and not os.path.isdir(current)): os.remove(current) os.makedirs(current, exist_ok=True) def main(stage, app_dir): created = not os.path.isdir(app_dir) os.makedirs(app_dir, mode=0o700, exist_ok=True) new, old = files(stage), files(app_dir) changed, removed = [], [] for relative in sorted(old - new): os.remove(os.path.join(app_dir, relative)) removed.append(relative) for relative in sorted(new): src, dst = os.path.join(stage, relative), os.path.join(app_dir, relative) if same(src, dst): continue ensure_dir(app_dir, relative) if os.path.isdir(dst) or os.path.islink(dst): clear(dst) with open(src, 'rb') as source, open(dst, 'wb') as target: shutil.copyfileobj(source, target) os.chmod(dst, stat.S_IMODE(os.stat(src).st_mode)) changed.append(relative) for directory, _, _ in os.walk(app_dir, topdown=False): if directory != app_dir and not os.listdir(directory): os.rmdir(directory) print(json.dumps({'changed': changed, 'removed': removed, 'created': created})) if __name__ == '__main__': main(sys.argv[1], sys.argv[2])