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