retention.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import os
  2. import re
  3. from datetime import datetime, timedelta
  4. def apply_retention(job, new_archive_name, backup_dir):
  5. """Applique la politique de rétention après une sauvegarde réussie."""
  6. archives = _list_archives_for_job(job, backup_dir)
  7. if job.retention_mode == "count":
  8. to_delete = _retention_count(archives, job.retention_value)
  9. elif job.retention_mode == "daily":
  10. to_delete = _retention_daily(archives, job.retention_value)
  11. else:
  12. return []
  13. deleted = []
  14. for archive_filename in to_delete:
  15. base = os.path.splitext(archive_filename)[0]
  16. for ext in (".tar", ".info.json"):
  17. full = os.path.join(backup_dir, base + ext)
  18. if os.path.exists(full):
  19. os.remove(full)
  20. deleted.append(base + ext)
  21. return deleted
  22. def _list_archives_for_job(job, backup_dir):
  23. """Liste les archives correspondant à ce job, triées par date (plus ancienne en premier)."""
  24. from flask import current_app
  25. instance = current_app.config["INSTANCE_NAME"]
  26. if job.type == "ynh_app":
  27. import json
  28. cfg = json.loads(job.config_json or "{}")
  29. app_id = cfg.get("app_id", "")
  30. prefix = f"{instance}_{app_id}_"
  31. elif job.type == "ynh_system":
  32. prefix = f"{instance}_system_"
  33. elif job.type in ("mysql", "postgresql"):
  34. import json
  35. cfg = json.loads(job.config_json or "{}")
  36. dbname = cfg.get("database", "")
  37. prefix = f"{instance}_{job.type}_{dbname}_"
  38. else:
  39. prefix = f"{instance}_{job.name.lower().replace(' ', '-')}_"
  40. archives = []
  41. for fname in os.listdir(backup_dir):
  42. if fname.startswith(prefix) and fname.endswith(".tar"):
  43. archives.append(fname)
  44. archives.sort(key=_extract_date)
  45. return archives
  46. def _extract_date(filename):
  47. match = re.search(r'(\d{8})', filename)
  48. if match:
  49. try:
  50. return datetime.strptime(match.group(1), "%Y%m%d")
  51. except ValueError:
  52. pass
  53. return datetime.min
  54. def _retention_count(archives, keep_n):
  55. if len(archives) <= keep_n:
  56. return []
  57. return archives[: len(archives) - keep_n]
  58. def _retention_daily(archives, days):
  59. cutoff = datetime.utcnow() - timedelta(days=days)
  60. to_delete = []
  61. seen_dates = set()
  62. for archive in reversed(archives):
  63. date = _extract_date(archive)
  64. if date < cutoff:
  65. to_delete.append(archive)
  66. continue
  67. date_key = date.date()
  68. if date_key in seen_dates:
  69. to_delete.append(archive)
  70. else:
  71. seen_dates.add(date_key)
  72. return to_delete