| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- import os
- import re
- from datetime import datetime, timedelta
- def apply_retention(job, new_archive_name, backup_dir):
- """Applique la politique de rétention après une sauvegarde réussie."""
- archives = _list_archives_for_job(job, backup_dir)
- if job.retention_mode == "count":
- to_delete = _retention_count(archives, job.retention_value)
- elif job.retention_mode == "daily":
- to_delete = _retention_daily(archives, job.retention_value)
- else:
- return []
- from jobs.utils import sudo_rm
- deleted = []
- for archive_filename in to_delete:
- base = os.path.splitext(archive_filename)[0]
- for ext in (".tar", ".info.json"):
- full = os.path.join(backup_dir, base + ext)
- sudo_rm(full)
- deleted.append(base + ext)
- return deleted
- def _job_archive_prefix(job, instance_name):
- """Retourne le préfixe des archives pour ce job (ex: jerry_nextcloud_)."""
- if job.type == "ynh_app":
- import json
- cfg = json.loads(job.config_json or "{}")
- return f"{instance_name}_{cfg.get('app_id', '')}_"
- elif job.type == "ynh_system":
- return f"{instance_name}_system_"
- elif job.type in ("mysql", "postgresql"):
- import json
- cfg = json.loads(job.config_json or "{}")
- return f"{instance_name}_{job.type}_{cfg.get('database', '')}_"
- elif job.type == "custom_dir":
- label = re.sub(r'[^a-z0-9]+', '-', job.name.lower().strip()).strip('-')
- return f"{instance_name}_{label}_"
- else:
- return f"{instance_name}_{job.name.lower().replace(' ', '-')}_"
- def _list_archives_for_job(job, backup_dir):
- """Liste les archives correspondant à ce job, triées par date (plus ancienne en premier)."""
- from flask import current_app
- instance = current_app.config["INSTANCE_NAME"]
- prefix = _job_archive_prefix(job, instance)
- from jobs.utils import sudo_listdir
- archives = [
- fname for fname in sudo_listdir(backup_dir)
- if fname.startswith(prefix) and fname.endswith(".tar")
- ]
- archives.sort(key=_extract_date)
- return archives
- def apply_remote_retention(job, client):
- """Applique la rétention sur l'instance distante après un push.
- Filtre les archives par le même préfixe que le job local et applique
- la même politique (count/daily). Ne touche pas aux archives des autres jobs.
- """
- from flask import current_app
- instance = current_app.config["INSTANCE_NAME"]
- prefix = _job_archive_prefix(job, instance)
- try:
- remote_archives = client.get_archives()
- except Exception:
- return []
- matching = sorted(
- [a["name"] + ".tar" for a in remote_archives if a["name"].startswith(prefix)],
- key=_extract_date,
- )
- if job.retention_mode == "count":
- to_delete = _retention_count(matching, job.retention_value)
- elif job.retention_mode == "daily":
- to_delete = _retention_daily(matching, job.retention_value)
- else:
- return []
- deleted = []
- for archive_filename in to_delete:
- base = os.path.splitext(archive_filename)[0]
- try:
- client.delete_archive(base)
- deleted.append(base)
- except Exception:
- pass
- return deleted
- def _extract_date(filename):
- match = re.search(r'(\d{8})', filename)
- if match:
- try:
- return datetime.strptime(match.group(1), "%Y%m%d")
- except ValueError:
- pass
- return datetime.min
- def _retention_count(archives, keep_n):
- if len(archives) <= keep_n:
- return []
- return archives[: len(archives) - keep_n]
- def _retention_daily(archives, days):
- cutoff = datetime.utcnow() - timedelta(days=days)
- to_delete = []
- seen_dates = set()
- for archive in reversed(archives):
- date = _extract_date(archive)
- if date < cutoff:
- to_delete.append(archive)
- continue
- date_key = date.date()
- if date_key in seen_dates:
- to_delete.append(archive)
- else:
- seen_dates.add(date_key)
- return to_delete
|