|
@@ -1,5 +1,7 @@
|
|
|
import os
|
|
import os
|
|
|
import re
|
|
import re
|
|
|
|
|
+import shlex
|
|
|
|
|
+import subprocess
|
|
|
from datetime import datetime, timedelta
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
|
|
|
|
@@ -65,6 +67,62 @@ def _list_archives_for_job(job, backup_dir):
|
|
|
return archives
|
|
return archives
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def apply_ssh_retention(job, destination, data_dir):
|
|
|
|
|
+ """Applique la rétention sur une destination SSH après un transfert réussi."""
|
|
|
|
|
+ import json as _json
|
|
|
|
|
+ from flask import current_app
|
|
|
|
|
+
|
|
|
|
|
+ instance = current_app.config["INSTANCE_NAME"]
|
|
|
|
|
+ prefix = _job_archive_prefix(job, instance)
|
|
|
|
|
+ key_path = os.path.join(data_dir, "keys", destination.key_name)
|
|
|
|
|
+
|
|
|
|
|
+ ssh_base = [
|
|
|
|
|
+ "ssh",
|
|
|
|
|
+ "-i", key_path,
|
|
|
|
|
+ "-p", str(destination.port),
|
|
|
|
|
+ "-o", "StrictHostKeyChecking=accept-new",
|
|
|
|
|
+ "-o", "BatchMode=yes",
|
|
|
|
|
+ "-o", "ConnectTimeout=30",
|
|
|
|
|
+ f"{destination.user}@{destination.host}",
|
|
|
|
|
+ ]
|
|
|
|
|
+
|
|
|
|
|
+ remote_dir = shlex.quote(destination.remote_path)
|
|
|
|
|
+ result = subprocess.run(
|
|
|
|
|
+ ssh_base + [f"find {remote_dir} -maxdepth 1 -name '*.tar' -printf '%f\\n' 2>/dev/null || true"],
|
|
|
|
|
+ capture_output=True, text=True, timeout=30,
|
|
|
|
|
+ )
|
|
|
|
|
+ if result.returncode != 0:
|
|
|
|
|
+ raise RuntimeError(f"Listage distant échoué : {result.stderr.strip()}")
|
|
|
|
|
+
|
|
|
|
|
+ remote_archives = sorted(
|
|
|
|
|
+ [f for f in result.stdout.splitlines() if f.startswith(prefix) and f.endswith(".tar")],
|
|
|
|
|
+ key=_extract_date,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if job.retention_mode == "count":
|
|
|
|
|
+ to_delete = _retention_count(remote_archives, job.retention_value)
|
|
|
|
|
+ elif job.retention_mode == "daily":
|
|
|
|
|
+ to_delete = _retention_daily(remote_archives, job.retention_value)
|
|
|
|
|
+ elif job.retention_mode == "gfs":
|
|
|
|
|
+ cfg = _json.loads(job.retention_gfs_config or "{}") if job.retention_gfs_config else {}
|
|
|
|
|
+ to_delete = _retention_gfs(remote_archives, cfg)
|
|
|
|
|
+ else:
|
|
|
|
|
+ return []
|
|
|
|
|
+
|
|
|
|
|
+ deleted = []
|
|
|
|
|
+ for archive_filename in to_delete:
|
|
|
|
|
+ base = os.path.splitext(archive_filename)[0]
|
|
|
|
|
+ tar_q = shlex.quote(f"{destination.remote_path}/{base}.tar")
|
|
|
|
|
+ info_q = shlex.quote(f"{destination.remote_path}/{base}.info.json")
|
|
|
|
|
+ r = subprocess.run(
|
|
|
|
|
+ ssh_base + [f"rm -f {tar_q} {info_q}"],
|
|
|
|
|
+ capture_output=True, timeout=30,
|
|
|
|
|
+ )
|
|
|
|
|
+ if r.returncode == 0:
|
|
|
|
|
+ deleted.append(archive_filename)
|
|
|
|
|
+ return deleted
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def apply_remote_retention(job, client):
|
|
def apply_remote_retention(job, client):
|
|
|
"""Applique la rétention sur l'instance distante après un push.
|
|
"""Applique la rétention sur l'instance distante après un push.
|
|
|
|
|
|