mirror of
https://github.com/netinvent/npbackup.git
synced 2024-09-21 15:26:24 +08:00
Add permission and ready decorator to functions
This commit is contained in:
parent
bee0c0c840
commit
771550be76
|
@ -25,8 +25,7 @@ from npbackup.restic_wrapper import ResticRunner
|
|||
from npbackup.core.restic_source_binary import get_restic_internal_binary
|
||||
from npbackup.path_helper import CURRENT_DIR, BASEDIR
|
||||
from npbackup.__version__ import __intname__ as NAME, __version__ as VERSION
|
||||
from npbackup import configuration
|
||||
|
||||
from time import sleep
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
@ -119,6 +118,7 @@ class NPBackupRunner:
|
|||
self._stdout = None
|
||||
self._stderr = None
|
||||
|
||||
self._is_ready = False
|
||||
if repo_config:
|
||||
self.repo_config = repo_config
|
||||
|
||||
|
@ -129,17 +129,15 @@ class NPBackupRunner:
|
|||
self.minimum_backup_age = None
|
||||
self._exec_time = None
|
||||
|
||||
self.is_ready = False
|
||||
# Create an instance of restic wrapper
|
||||
self.create_restic_runner()
|
||||
# Configure that instance
|
||||
self.apply_config_to_restic_runner()
|
||||
else:
|
||||
self.is_ready = False
|
||||
|
||||
|
||||
@property
|
||||
def backend_version(self) -> bool:
|
||||
if self.is_ready:
|
||||
if self._is_ready:
|
||||
return self.restic_runner.binary_version
|
||||
return None
|
||||
|
||||
|
@ -199,7 +197,7 @@ class NPBackupRunner:
|
|||
|
||||
@property
|
||||
def has_binary(self) -> bool:
|
||||
if self.is_ready:
|
||||
if self._is_ready:
|
||||
return True if self.restic_runner.binary else False
|
||||
return False
|
||||
|
||||
|
@ -211,6 +209,19 @@ class NPBackupRunner:
|
|||
def exec_time(self, value: int):
|
||||
self._exec_time = value
|
||||
|
||||
def write_logs(self, msg: str, error: bool=False):
|
||||
"""
|
||||
Write logs to log file and stdout / stderr queues if exist for GUI usage
|
||||
"""
|
||||
if error:
|
||||
logger.error(msg)
|
||||
if self.stderr:
|
||||
self.stderr.put(msg)
|
||||
else:
|
||||
logger.info(msg)
|
||||
if self.stdout:
|
||||
self.stdout.put(msg)
|
||||
|
||||
# pylint does not understand why this function does not take a self parameter
|
||||
# It's a decorator, and the inner function will have the self argument instead
|
||||
# pylint: disable=no-self-argument
|
||||
|
@ -219,29 +230,22 @@ class NPBackupRunner:
|
|||
Decorator that calculates time of a function execution
|
||||
"""
|
||||
|
||||
@wraps(fn)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
start_time = datetime.utcnow()
|
||||
# pylint: disable=E1102 (not-callable)
|
||||
result = fn(self, *args, **kwargs)
|
||||
self.exec_time = (datetime.utcnow() - start_time).total_seconds()
|
||||
logger.info("Runner took {} seconds".format(self.exec_time))
|
||||
logger.info(f"Runner took {self.exec_time} seconds for {fn.__name__}")
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
def write_logs(self, msg: str, error: bool=False):
|
||||
logger.info(msg)
|
||||
if error:
|
||||
if self.stderr:
|
||||
self.stderr.put(msg)
|
||||
else:
|
||||
if self.stdout:
|
||||
self.stdout.put(msg)
|
||||
|
||||
def close_queues(fn: Callable):
|
||||
"""
|
||||
Function that sends None to both stdout and stderr queues so GUI gets proper results
|
||||
Decorator that sends None to both stdout and stderr queues so GUI gets proper results
|
||||
"""
|
||||
@wraps(fn)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
close_queues = kwargs.pop("close_queues", True)
|
||||
result = fn(self, *args, **kwargs)
|
||||
|
@ -253,6 +257,46 @@ class NPBackupRunner:
|
|||
return result
|
||||
return wrapper
|
||||
|
||||
def is_ready(fn: Callable):
|
||||
""""
|
||||
Decorator that checks if NPBackupRunner is ready to run, and logs accordingly
|
||||
"""
|
||||
@wraps(fn)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if not self._is_ready:
|
||||
self.write_logs(f"Runner cannot execute {fn.__name__}. Backend not ready", error=True)
|
||||
return False
|
||||
return fn(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
def has_permission(fn: Callable):
|
||||
"""
|
||||
Decorator that checks permissions before running functions
|
||||
"""
|
||||
@wraps(fn)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
required_permissions = {
|
||||
"backup": ["backup", "restore", "full"],
|
||||
"check_recent_backups": ["backup", "restore", "full"],
|
||||
"list": ["backup", "restore", "full"],
|
||||
"ls": ["backup", "restore", "full"],
|
||||
"find": ["backup", "restore", "full"],
|
||||
"restore": ["restore", "full"],
|
||||
"check": ["restore", "full"],
|
||||
"forget": ["full"],
|
||||
"prune": ["full"],
|
||||
"raw": ["full"]
|
||||
}
|
||||
try:
|
||||
operation = fn.__name__
|
||||
# TODO: enforce permissions
|
||||
self.write_logs(f"Permissions required are {required_permissions[operation]}")
|
||||
except (IndexError, KeyError):
|
||||
self.write_logs("You don't have sufficient permissions")
|
||||
return False
|
||||
return fn(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
def create_restic_runner(self) -> None:
|
||||
can_run = True
|
||||
try:
|
||||
|
@ -303,7 +347,7 @@ class NPBackupRunner:
|
|||
"No password nor password command given. Repo password cannot be empty"
|
||||
)
|
||||
can_run = False
|
||||
self.is_ready = can_run
|
||||
self._is_ready = can_run
|
||||
if not can_run:
|
||||
return None
|
||||
self.restic_runner = ResticRunner(
|
||||
|
@ -324,7 +368,8 @@ class NPBackupRunner:
|
|||
self.restic_runner.binary = binary
|
||||
|
||||
def apply_config_to_restic_runner(self) -> None:
|
||||
if not self.is_ready:
|
||||
if not self._is_ready:
|
||||
self.write_logs("Runner settings cannot be applied to backend", error=True)
|
||||
return None
|
||||
try:
|
||||
if self.repo_config.g("repo_opts.upload_speed"):
|
||||
|
@ -438,20 +483,20 @@ class NPBackupRunner:
|
|||
# ACTUAL RUNNER FUNCTIONS #
|
||||
###########################
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def list(self) -> Optional[dict]:
|
||||
if not self.is_ready:
|
||||
return False
|
||||
logger.info("Listing snapshots")
|
||||
snapshots = self.restic_runner.snapshots()
|
||||
return snapshots
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def find(self, path: str) -> bool:
|
||||
if not self.is_ready:
|
||||
return False
|
||||
logger.info("Searching for path {}".format(path))
|
||||
result = self.restic_runner.find(path=path)
|
||||
if result:
|
||||
|
@ -461,25 +506,25 @@ class NPBackupRunner:
|
|||
return True
|
||||
return False
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def ls(self, snapshot: str) -> Optional[dict]:
|
||||
if not self.is_ready:
|
||||
return False
|
||||
logger.info("Showing content of snapshot {}".format(snapshot))
|
||||
result = self.restic_runner.ls(snapshot)
|
||||
return result
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def check_recent_backups(self) -> bool:
|
||||
"""
|
||||
Checks for backups in timespan
|
||||
Returns True or False if found or not
|
||||
Returns None if no information is available
|
||||
"""
|
||||
if not self.is_ready:
|
||||
return None
|
||||
if self.minimum_backup_age == 0:
|
||||
logger.info("No minimal backup age set. Set for backup")
|
||||
|
||||
|
@ -503,14 +548,14 @@ class NPBackupRunner:
|
|||
logger.error("Cannot connect to repository or repository empty.")
|
||||
return result, backup_tz
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def backup(self, force: bool = False) -> bool:
|
||||
"""
|
||||
Run backup after checking if no recent backup exists, unless force == True
|
||||
"""
|
||||
if not self.is_ready:
|
||||
return False
|
||||
# Preflight checks
|
||||
paths = self.repo_config.g("backup_opts.paths")
|
||||
if not paths:
|
||||
|
@ -661,11 +706,11 @@ class NPBackupRunner:
|
|||
)
|
||||
return result
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def restore(self, snapshot: str, target: str, restore_includes: List[str]) -> bool:
|
||||
if not self.is_ready:
|
||||
return False
|
||||
if not self.repo_config.g("permissions") in ['restore', 'full']:
|
||||
msg = "You don't have permissions to restore this repo"
|
||||
self.output_queue.put(msg)
|
||||
|
@ -679,50 +724,52 @@ class NPBackupRunner:
|
|||
)
|
||||
return result
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def forget(self, snapshot: str) -> bool:
|
||||
if not self.is_ready:
|
||||
return False
|
||||
logger.info("Forgetting snapshot {}".format(snapshot))
|
||||
result = self.restic_runner.forget(snapshot)
|
||||
return result
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def check(self, read_data: bool = True) -> bool:
|
||||
if not self.is_ready:
|
||||
return False
|
||||
self.write_logs("Checking repository")
|
||||
sleep(1)
|
||||
result = self.restic_runner.check(read_data)
|
||||
return result
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def prune(self) -> bool:
|
||||
if not self.is_ready:
|
||||
return False
|
||||
logger.info("Pruning snapshots")
|
||||
result = self.restic_runner.prune()
|
||||
return result
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def repair(self, order: str) -> bool:
|
||||
if not self.is_ready:
|
||||
return False
|
||||
logger.info("Repairing {} in repo".format(order))
|
||||
result = self.restic_runner.repair(order)
|
||||
return result
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
@has_permission
|
||||
@is_ready
|
||||
@close_queues
|
||||
def raw(self, command: str) -> bool:
|
||||
logger.info("Running raw command: {}".format(command))
|
||||
result = self.restic_runner.raw(command=command)
|
||||
return result
|
||||
|
||||
#@close_queues
|
||||
@exec_timer
|
||||
def group_runner(
|
||||
self, repo_list: list, operation: str, **kwargs
|
||||
|
@ -730,10 +777,10 @@ class NPBackupRunner:
|
|||
group_result = True
|
||||
|
||||
# Make sure we don't close the stdout/stderr queues when running multiple operations
|
||||
#kwargs = {
|
||||
# **kwargs,
|
||||
# **{'close_queues': False}
|
||||
#}
|
||||
kwargs = {
|
||||
**kwargs,
|
||||
**{'close_queues': False}
|
||||
}
|
||||
|
||||
for repo in repo_list:
|
||||
self.write_logs(f"Running {operation} for repo {repo}")
|
||||
|
@ -744,6 +791,6 @@ class NPBackupRunner:
|
|||
self.write_logs(f"Operation {operation} failed for repo {repo}", error=True)
|
||||
group_result = False
|
||||
self.write_logs("Finished execution group operations")
|
||||
from time import sleep
|
||||
sleep(2)
|
||||
self.close_queues(lambda *args, **kwargs: None, **kwargs)
|
||||
return group_result
|
|
@ -178,7 +178,7 @@ def get_gui_data(repo_config: dict) -> Tuple[bool, List[str]]:
|
|||
except ValueError:
|
||||
sg.Popup(_t("config_gui.no_runner"))
|
||||
return None, None
|
||||
if not runner.is_ready:
|
||||
if not runner._is_ready:
|
||||
sg.Popup(_t("config_gui.runner_not_configured"))
|
||||
return None, None
|
||||
if not runner.has_binary:
|
||||
|
|
Loading…
Reference in a new issue