Mini Shell

Direktori : /opt/bakmgr/lib/python3.6/site-packages/bakmgr/api/restic/
Upload File :
Current File : //opt/bakmgr/lib/python3.6/site-packages/bakmgr/api/restic/errors.py

"""Custom restic error subclasses"""
import signal
import logging
from typing import TYPE_CHECKING, Union
from subprocess import CalledProcessError, CompletedProcess
import re
import os

if TYPE_CHECKING:
    from .base import Restic


class ResticError(Exception):
    """Base class for restic errors

    Args:
        proc (CalledProcessError | CompletedProcess): subprocess result
        restic: (Restic object)
    """

    def __init__(
        self,
        proc: Union[CalledProcessError, CompletedProcess],
        restic: 'Restic',
    ):
        super().__init__(proc.stderr)
        self.stderr = proc.stderr
        self.returncode = proc.returncode
        self.restic = restic

    def __new__(
        cls, proc: Union[CalledProcessError, CompletedProcess], *_, **__
    ):
        if cls is not ResticError:
            return Exception.__new__(cls)
        stderr = proc.stderr.strip()
        if (
            'dial tcp' in stderr
            or 'TLS handshake timeout' in stderr
            or '502 Bad Gateway' in stderr
        ):
            return Exception.__new__(ResticConnError)
        if 'The access key ID you provided does not exist' in stderr:
            return Exception.__new__(ResticAccessError)
        if (
            'Stat: The specified key does not exist' in stderr
            or 'The specified bucket does not exist' in stderr
        ):
            return Exception.__new__(ResticInitError)
        if 'unable to create lock in backend' in stderr:
            return Exception.__new__(ResticLockedError)
        index_errs = (
            'rebuild-index',
            'unable to load index',
            'returned error, retrying after',
            'Load(<index',
            'Load(<lock',
        )
        if any(x in stderr for x in index_errs) or (
            stderr.startswith('Fatal:') and 'invalid data returned' in stderr
        ):
            return Exception.__new__(ResticBadIndexError)
        if proc.returncode < 0 and abs(proc.returncode) == signal.SIGPIPE:
            # https://github.com/restic/restic/issues/1466
            # https://github.com/restic/restic/pull/2546
            return Exception.__new__(ResticConnError)
        return Exception.__new__(cls)

    def __str__(self):
        name = type(self).__name__
        if self.returncode < 0:
            signum = abs(self.returncode)
            try:
                # https://github.com/PyCQA/pylint/issues/2804
                sig = signal.Signals(signum).name  # pylint: disable=no-member
            except ValueError:
                sig = 'signal %d' % signum
            if signum == signal.SIGPIPE:
                # https://github.com/restic/restic/issues/1466
                return f'{name}: Timeout: received SIGPIPE. {self.stderr}'
            return f'{name}: (killed with {sig}) {self.stderr}'
        return f'{name}: (return code {self.returncode}) {self.stderr}'


class ResticBadIndexError(ResticError):
    """Raised when restic raises an index corruption error"""


class ResticAccessError(ResticError):
    """Raised when S3 access/secret keys are incorrect for restic"""


class ResticInitError(ResticError):
    """Raised when a restic repo connects but is not initialized"""


class ResticConnError(ResticError):
    """Raised when a connection is refused or times out from ceph"""


class ResticLockedError(ResticError):
    """Raised when an operation on a repo fails due to a lock held on it"""

    def __init__(
        self,
        proc: Union[CalledProcessError, CompletedProcess],
        restic: 'Restic',
    ):
        super().__init__(proc, restic)
        pid_re = re.compile(r'locked\ (?:exclusively\ )?by\ PID\ (\d+)\ ')
        match = pid_re.search(proc.stderr)
        if match:
            self.pid = int(match.group(1))
        else:
            logging.warning('failed to parse PID from %r', proc.stderr)
            self.pid = None

    def unlock_ok(self) -> bool:
        """Check if a locked repo can be unlocked

        Returns:
            bool: true if restic unlock can be run safely
        """
        if self.pid is None:
            return False
        try:
            os.kill(self.pid, 0)
        except ProcessLookupError:
            return True
        return False

Zerion Mini Shell 1.0