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