docker/backup.py

111 lines
3.0 KiB
Python
Executable File

#!/usr/bin/env python
# file backup.py
# author Florent Guiotte <florent.guiotte@irisa.fr>
# version 0.0
# date 10 août 2024
"""Abstract
doc.
"""
import yaml
from pathlib import Path
import subprocess
from datetime import datetime
COMPOSE_PATH = Path('docker-compose.yml')
BACKUP_PATH = Path('./data/bkp')
VOLUME_PREFIX = 'docker_'
class UnionFind:
def __init__(self):
self.parent = {}
def make(self, service):
self.parent.setdefault(service, service)
def find(self, service):
"""return root"""
if self.parent[service] != service:
self.parent[service] = self.find(self.parent[service])
return self.parent[service]
def union(self, service1, service2):
root1 = self.find(service1)
root2 = self.find(service2)
if root1 != root2:
self.parent[root2] = root1 # compress!
def build_services_graph(services):
uf = UnionFind()
for service in services:
uf.make(service)
for dependency in services[service].get('depends_on', []):
uf.make(dependency)
uf.union(service, dependency)
return uf
def group_services(services, graph):
grouped_services = {}
for service in services:
root = graph.find(service)
if root not in grouped_services:
grouped_services[root] = {'services': []}
grouped_services[root]['services'].append(service)
return grouped_services
def group_volumes(services, volumes, services_group):
for group_name, group in services_group.items():
group_volumes = group.setdefault('volumes', [])
for service in group['services']:
for volume in [v.split(':')[0] for v in services[service]['volumes']]:
if volume in volumes: group_volumes += [volume]
return services_group
def backup(volume):
current_date = datetime.now()
date_string = current_date.strftime("%Y-%m-%d")
archive_name = f'{date_string}_{volume}.tar'
print(f'backup volume {volume} to {BACKUP_PATH}/{archive_name}')
subprocess.run(f'docker run --rm --volume {VOLUME_PREFIX}{volume}:/data --volume {BACKUP_PATH.resolve()}:/bkp ubuntu tar -cf /bkp/{archive_name} -C /data .'.split())
def run_docker_compose(cmd):
subprocess.run(f'docker compose {cmd}'.split())
if __name__ == '__main__':
with COMPOSE_PATH.open() as cf:
compose = yaml.safe_load(cf)
services = compose['services']
volumes = compose['volumes']
services_graph = build_services_graph(services)
services_group = group_services(services, services_graph)
services_group = group_volumes(services, volumes, services_group)
for group_name, group in services_group.items():
print(f'Service group {group_name} ', end='')
if not group['volumes']:
print('no volumes')
continue
print('run backup...')
run_docker_compose(f'stop {" ".join(group["services"])}')
for volume in group['volumes']:
backup(volume)
run_docker_compose(f'start {" ".join(group["services"])}')