velero_restore_new.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import datetime
  2. import time
  3. import os
  4. import json
  5. import subprocess
  6. import sys
  7. namespaces = ["vaultwarden", "postgres"]
  8. k3s_env = {"KUBECONFIG": "/etc/rancher/k3s/k3s.yaml"}
  9. ntfy_topic = "https://ntfy.jibby.org/velero-restore"
  10. ntfy_auth = os.environ["NTFY_AUTH"]
  11. restart_deployments_in = ["vaultwarden"]
  12. restart_statefulsets_in = ["postgres"]
  13. def main():
  14. if sys.version_info.major < 3 or sys.version_info.minor < 11:
  15. raise RuntimeError("Python 3.11 or greater required")
  16. velero_str = subprocess.run(
  17. ["/usr/local/bin/velero", "backup", "get", "-o", "json"],
  18. env=k3s_env,
  19. check=True,
  20. capture_output=True,
  21. ).stdout
  22. velero = json.loads(velero_str)
  23. backups_by_timestamp = {
  24. backup['metadata']['creationTimestamp']: backup
  25. for backup in velero['items']
  26. }
  27. if not backups_by_timestamp:
  28. raise ValueError("no backups?")
  29. newest_backup_timestamp = max(backups_by_timestamp.keys())
  30. one_week_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=7)
  31. if datetime.datetime.fromisoformat(newest_backup_timestamp) < one_week_ago:
  32. raise ValueError(f"no backups < 1 week old? {newest_backup_timestamp=}")
  33. newest_backup = backups_by_timestamp[newest_backup_timestamp]
  34. print(f"Using newest backup {newest_backup['metadata']['name']}, taken at {newest_backup['metadata']['creationTimestamp']}")
  35. # delete namespaces
  36. for namespace in namespaces:
  37. subprocess.run(
  38. ["/usr/local/bin/kubectl", "delete", "namespace", namespace],
  39. env=k3s_env,
  40. )
  41. subprocess.run(
  42. ["/usr/local/bin/velero", "restore", "create", "--from-backup", newest_backup['metadata']['name'], "--include-namespaces", ",".join(namespaces), "--wait"],
  43. env=k3s_env,
  44. check=True,
  45. )
  46. for namespace in restart_deployments_in:
  47. subprocess.run(
  48. ["/usr/local/bin/kubectl", "-n", namespace, "rollout", "restart", "deployment"],
  49. env=k3s_env,
  50. check=True,
  51. )
  52. for namespace in restart_statefulsets_in:
  53. subprocess.run(
  54. ["/usr/local/bin/kubectl", "-n", namespace, "rollout", "restart", "statefulset"],
  55. env=k3s_env,
  56. check=True,
  57. )
  58. wait_until_up("https://vaultwarden.bnuuy.org", 300)
  59. ntfy_send(
  60. f"Successfully ran velero restore for backup {newest_backup['metadata']['name']}, "
  61. f"{newest_backup['metadata']['creationTimestamp']}"
  62. )
  63. def wait_until_up(url: str, timeout_sec: int):
  64. start = datetime.datetime.now()
  65. while True:
  66. try:
  67. subprocess.run(["curl", "--fail", url], check=True)
  68. return
  69. except subprocess.CalledProcessError as exc:
  70. if start + datetime.timedelta(seconds=timeout_sec) < datetime.datetime.now():
  71. raise ValueError(f">{timeout_sec} seconds passed & {url} is not up: {exc}")
  72. time.sleep(5)
  73. def ntfy_send(data):
  74. # auth & payload formatting is awful in urllib. just use curl
  75. subprocess.run(["curl", "--fail", "-u", ntfy_auth, "-d", data, ntfy_topic], check=True)
  76. if __name__ == '__main__':
  77. try:
  78. main()
  79. except Exception as e:
  80. ntfy_send(f"Error running velero restore: {str(e)}")
  81. raise