velero_restore_new.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import datetime
  2. import os
  3. import json
  4. import subprocess
  5. import sys
  6. namespaces = ["vaultwarden", "postgres"]
  7. k3s_env = {"KUBECONFIG": "/etc/rancher/k3s/k3s.yaml"}
  8. ntfy_topic = "https://ntfy.jibby.org/velero-restore"
  9. ntfy_auth = os.environ["NTFY_AUTH"]
  10. restart_deployments_in = ["vaultwarden"]
  11. restart_statefulsets_in = ["postgres"]
  12. def main():
  13. if sys.version_info.major < 3 or sys.version_info.minor < 11:
  14. raise RuntimeError("Python 3.11 or greater required")
  15. velero_str = subprocess.run(
  16. ["/usr/local/bin/velero", "backup", "get", "-o", "json"],
  17. env=k3s_env,
  18. check=True,
  19. capture_output=True,
  20. ).stdout
  21. velero = json.loads(velero_str)
  22. backups_by_timestamp = {
  23. backup['metadata']['creationTimestamp']: backup
  24. for backup in velero['items']
  25. }
  26. if not backups_by_timestamp:
  27. raise ValueError("no backups?")
  28. newest_backup_timestamp = max(backups_by_timestamp.keys())
  29. one_week_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(days=7)
  30. if datetime.datetime.fromisoformat(newest_backup_timestamp) < one_week_ago:
  31. raise ValueError(f"no backups < 1 week old? {newest_backup_timestamp=}")
  32. newest_backup = backups_by_timestamp[newest_backup_timestamp]
  33. print(f"Using newest backup {newest_backup['metadata']['name']}, taken at {newest_backup['metadata']['creationTimestamp']}")
  34. # delete namespaces
  35. for namespace in namespaces:
  36. subprocess.run(
  37. ["/usr/local/bin/kubectl", "delete", "namespace", namespace],
  38. env=k3s_env,
  39. check=False, # OK if this namespace doesn't exist,
  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. def ntfy_send(data):
  73. # auth & payload formatting is awful in urllib. just use curl
  74. subprocess.run(["curl", "--fail", "-u", ntfy_auth, "-d", data, ntfy_topic], check=True)
  75. if __name__ == '__main__':
  76. try:
  77. main()
  78. except Exception as e:
  79. ntfy_send(f"Error running velero restore: {str(e)}")
  80. raise