velero_restore_new.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  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. check=False, # OK if this namespace doesn't exist,
  41. )
  42. # TODO check for pv with mount points in these namespaces
  43. subprocess.run(
  44. ["/usr/local/bin/velero", "restore", "create", "--from-backup", newest_backup['metadata']['name'], "--include-namespaces", ",".join(namespaces), "--wait"],
  45. env=k3s_env,
  46. check=True,
  47. )
  48. for namespace in restart_deployments_in:
  49. subprocess.run(
  50. ["/usr/local/bin/kubectl", "-n", namespace, "rollout", "restart", "deployment"],
  51. env=k3s_env,
  52. check=True,
  53. )
  54. for namespace in restart_statefulsets_in:
  55. subprocess.run(
  56. ["/usr/local/bin/kubectl", "-n", namespace, "rollout", "restart", "statefulset"],
  57. env=k3s_env,
  58. check=True,
  59. )
  60. wait_until_up("https://vaultwarden.bnuuy.org", 300)
  61. ntfy_send(
  62. f"Successfully ran velero restore for backup {newest_backup['metadata']['name']}, "
  63. f"{newest_backup['metadata']['creationTimestamp']}"
  64. )
  65. def wait_until_up(url: str, timeout_sec: int):
  66. start = datetime.datetime.now()
  67. while True:
  68. try:
  69. subprocess.run(["curl", "--fail", url], check=True)
  70. return
  71. except subprocess.CalledProcessError as exc:
  72. if start + datetime.timedelta(seconds=timeout_sec) < datetime.datetime.now():
  73. raise ValueError(f">{timeout_sec} seconds passed & {url} is not up: {exc}")
  74. time.sleep(5)
  75. def ntfy_send(data):
  76. # auth & payload formatting is awful in urllib. just use curl
  77. subprocess.run(["curl", "--fail", "-u", ntfy_auth, "-d", data, ntfy_topic], check=True)
  78. if __name__ == '__main__':
  79. try:
  80. main()
  81. except Exception as e:
  82. ntfy_send(f"Error running velero restore: {str(e)}")
  83. raise