Exemple #1
0
    def filter_mounts(self):
        """Get all mounts for this container matching include/exclude filters"""
        filtered = []

        # If exclude_bind_mounts is true, only volume mounts are kept in the list of mounts
        exclude_bind_mounts = utils.is_true(config.exclude_bind_mounts)
        mounts = list(
            filter(lambda m: not exclude_bind_mounts or m.type == "volume",
                   self._mounts))

        if not self.volume_backup_enabled:
            return filtered

        if self._include:
            for mount in mounts:
                for pattern in self._include:
                    if pattern in mount.source:
                        break
                else:
                    continue

                filtered.append(mount)

        elif self._exclude:
            for mount in mounts:
                for pattern in self._exclude:
                    if pattern in mount.source:
                        break
                else:
                    filtered.append(mount)
        else:
            return mounts

        return filtered
    def backup_destination_path(self) -> str:
        destination = Path("/databases")

        if utils.is_true(config.include_project_name):
            project_name = self.project_name
            if project_name != "":
                destination /= project_name

        destination /= self.service_name

        return destination
Exemple #3
0
    def backup_destination_path(self) -> str:
        destination = Path("/databases")

        if utils.is_true(config.include_project_name):
            project_name = self.project_name
            if project_name != "":
                destination /= project_name

        destination /= self.service_name
        destination /= f"{self.get_credentials()['database']}.sql"

        return destination
Exemple #4
0
    def get_volume_backup_destination(self, mount, source_prefix) -> str:
        """Get the destination path for backups of the given mount"""
        destination = Path(source_prefix)

        if utils.is_true(config.include_project_name):
            project_name = self.project_name
            if project_name != '':
                destination /= project_name

        destination /= self.service_name
        destination /= Path(utils.strip_root(mount.destination))

        return str(destination)
def status(config, containers):
    """Outputs the backup config for the compose setup"""
    logger.info("Status for compose project '%s'", containers.project_name)
    logger.info("Repository: '%s'", config.repository)
    logger.info("Backup currently running?: %s",
                containers.backup_process_running)
    logger.info("Include project name in backup path?: %s",
                utils.is_true(config.include_project_name))
    logger.debug("Exclude bind mounts from backups?: %s",
                 utils.is_true(config.exclude_bind_mounts))
    logger.info("Checking docker availability")

    utils.list_containers()

    if containers.stale_backup_process_containers:
        utils.remove_containers(containers.stale_backup_process_containers)

    # Check if repository is initialized with restic snapshots
    if not restic.is_initialized(config.repository):
        logger.info(
            "Could not get repository info. Attempting to initialize it.")
        result = restic.init_repo(config.repository)
        if result == 0:
            logger.info("Successfully initialized repository: %s",
                        config.repository)
        else:
            logger.error("Failed to initialize repository")

    logger.info("%s Detected Config %s", "-" * 25, "-" * 25)

    # Start making snapshots
    backup_containers = containers.containers_for_backup()
    for container in backup_containers:
        logger.info('service: %s', container.service_name)

        if container.volume_backup_enabled:
            for mount in container.filter_mounts():
                logger.info(
                    ' - volume: %s -> %s',
                    mount.source,
                    container.get_volume_backup_destination(mount, '/volumes'),
                )

        if container.database_backup_enabled:
            instance = container.instance
            ping = instance.ping()
            logger.info(
                ' - %s (is_ready=%s) -> %s',
                instance.container_type,
                ping == 0,
                instance.backup_destination_path(),
            )
            if ping != 0:
                logger.error("Database '%s' in service %s cannot be reached",
                             instance.container_type, container.service_name)

    if len(backup_containers) == 0:
        logger.info(
            "No containers in the project has 'restic-compose-backup.*' label")

    logger.info("-" * 67)
def start_backup_process(config, containers):
    """The actual backup process running inside the spawned container"""
    if not utils.is_true(os.environ.get('BACKUP_PROCESS_CONTAINER')):
        logger.error(
            "Cannot run backup process in this container. Use backup command instead. "
            "This will spawn a new container with the necessary mounts.")
        alerts.send(
            subject="Cannot run backup process in this container",
            body=
            ("Cannot run backup process in this container. Use backup command instead. "
             "This will spawn a new container with the necessary mounts."))
        exit(1)

    status(config, containers)
    errors = False

    # Did we actually get any volumes mounted?
    try:
        has_volumes = os.stat('/volumes') is not None
    except FileNotFoundError:
        logger.warning("Found no volumes to back up")
        has_volumes = False

    # Warn if there is nothing to do
    if len(containers.containers_for_backup()) == 0 and not has_volumes:
        logger.error("No containers for backup found")
        exit(1)

    if has_volumes:
        try:
            logger.info('Backing up volumes')
            vol_result = restic.backup_files(config.repository,
                                             source='/volumes')
            logger.debug('Volume backup exit code: %s', vol_result)
            if vol_result != 0:
                logger.error('Volume backup exited with non-zero code: %s',
                             vol_result)
                errors = True
        except Exception as ex:
            logger.error('Exception raised during volume backup')
            logger.exception(ex)
            errors = True

    # back up databases
    logger.info('Backing up databases')
    for container in containers.containers_for_backup():
        if container.database_backup_enabled:
            try:
                instance = container.instance
                logger.info('Backing up %s in service %s',
                            instance.container_type, instance.service_name)
                result = instance.backup()
                logger.debug('Exit code: %s', result)
                if result != 0:
                    logger.error(
                        'Backup command exited with non-zero code: %s', result)
                    errors = True
            except Exception as ex:
                logger.exception(ex)
                errors = True

    if errors:
        logger.error('Exit code: %s', errors)
        exit(1)

    # Only run cleanup if backup was successful
    result = cleanup(config, container)
    logger.debug('cleanup exit code: %s', result)
    if result != 0:
        logger.error('cleanup exit code: %s', result)
        exit(1)

    # Test the repository for errors
    logger.info("Checking the repository for errors")
    result = restic.check(config.repository)
    if result != 0:
        logger.error('Check exit code: %s', result)
        exit(1)

    logger.info('Backup completed')
Exemple #7
0
 def postgresql_backup_enabled(self) -> bool:
     """bool: If the ``restic-compose-backup.postgres`` label is set"""
     return utils.is_true(self.get_label(enums.LABEL_POSTGRES_ENABLED))
Exemple #8
0
 def mariadb_backup_enabled(self) -> bool:
     """bool: If the ``restic-compose-backup.mariadb`` label is set"""
     return utils.is_true(self.get_label(enums.LABEL_MARIADB_ENABLED))
Exemple #9
0
 def mysql_backup_enabled(self) -> bool:
     """bool: If the ``restic-compose-backup.mysql`` label is set"""
     return utils.is_true(self.get_label(enums.LABEL_MYSQL_ENABLED))
Exemple #10
0
 def volume_backup_enabled(self) -> bool:
     """bool: If the ``restic-compose-backup.volumes`` label is set"""
     return utils.is_true(self.get_label(enums.LABEL_VOLUMES_ENABLED))
 def minecraft_backup_enabled(self) -> bool:
     """bool: If the ``restic-compose-backup.minecraft``` label is set"""
     return utils.is_true(self.get_label(enums.LABEL_MINECRAFT_ENABLED))