diff --git a/selfprivacy_api/backup/__init__.py b/selfprivacy_api/backup/__init__.py index 7a3b37d..7001d03 100644 --- a/selfprivacy_api/backup/__init__.py +++ b/selfprivacy_api/backup/__init__.py @@ -257,6 +257,25 @@ class Backups: Backups.sync_service_snapshots(service_id, upstream_snapshots) return [snap for snap in upstream_snapshots if snap.service_name == service_id] + @staticmethod + def get_snapshot_by_id(id: str) -> Optional[Snapshot]: + snap = Storage.get_cached_snapshot_by_id(id) + if snap is not None: + return snap + + # Possibly our cache entry got invalidated, let's try one more time + Backups.sync_all_snapshots() + snap = Storage.get_cached_snapshot_by_id(id) + + return snap + + @staticmethod + def sync_all_snapshots(): + upstream_snapshots = Backups.provider().backuper.get_snapshots() + Storage.invalidate_snapshot_storage() + for snapshot in upstream_snapshots: + Storage.cache_snapshot(snapshot) + @staticmethod def restore_service_from_snapshot(service: Service, snapshot_id: str): repo_name = service.get_id() diff --git a/selfprivacy_api/backup/storage.py b/selfprivacy_api/backup/storage.py index dd23210..38155e6 100644 --- a/selfprivacy_api/backup/storage.py +++ b/selfprivacy_api/backup/storage.py @@ -47,6 +47,11 @@ class Storage: for key in redis.keys(prefix + "*"): redis.delete(key) + @staticmethod + def invalidate_snapshot_storage(): + for key in redis.keys(REDIS_SNAPSHOTS_PREFIX + "*"): + redis.delete(key) + @staticmethod def store_testrepo_path(path: str): redis.set(REDIS_REPO_PATH_KEY, path) @@ -97,6 +102,13 @@ class Storage: snapshot_key = Storage.__snapshot_key(snapshot) redis.delete(snapshot_key) + @staticmethod + def get_cached_snapshot_by_id(snapshot_id: str) -> Optional[Snapshot]: + key = redis.keys(REDIS_SNAPSHOTS_PREFIX + snapshot_id) + if not redis.exists(key): + return None + return hash_as_model(redis, key, Snapshot) + @staticmethod def get_cached_snapshots() -> List[Snapshot]: keys = redis.keys(REDIS_SNAPSHOTS_PREFIX + "*") diff --git a/selfprivacy_api/graphql/mutations/backup_mutations.py b/selfprivacy_api/graphql/mutations/backup_mutations.py index 4704df2..8ae19bb 100644 --- a/selfprivacy_api/graphql/mutations/backup_mutations.py +++ b/selfprivacy_api/graphql/mutations/backup_mutations.py @@ -14,7 +14,7 @@ from selfprivacy_api.graphql.queries.providers import BackupProvider from selfprivacy_api.backup import Backups from selfprivacy_api.services import get_all_services, get_service_by_id -from selfprivacy_api.backup.tasks import start_backup +from selfprivacy_api.backup.tasks import start_backup, restore_snapshot @strawberry.input @@ -80,3 +80,13 @@ class BackupMutations: start_backup(service) return GenericJobMutationReturn() + + @strawberry.mutation(permission_classes=[IsAuthenticated]) + def restore_backup(self, snapshot_id: str) -> GenericJobMutationReturn: + """Restore backup""" + snap = Backups.get_snapshot_by_id(snapshot_id) + if snap in None: + raise ValueError(f"No such snapshot: {snapshot_id}") + restore_snapshot(snap) + + return GenericJobMutationReturn()