diff --git a/docker-compose.yaml b/docker-compose.yaml index b82b138..bf59199 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -48,6 +48,7 @@ services: image: mariadb:10 labels: restic-compose-backup.mariadb: true + restic-compose-backup.tags: db,test environment: - MYSQL_ROOT_PASSWORD=my-secret-pw - MYSQL_DATABASE=mydb @@ -71,6 +72,7 @@ services: image: itzg/minecraft-server labels: restic-compose-backup.minecraft: true + restic-compose-backup.tags: "test,foo,bar" restic-compose-backup.volumes.include: "minecraft" environment: - RCON_PASSWORD=minecraft diff --git a/src/restic_compose_backup/cli.py b/src/restic_compose_backup/cli.py index 892f114..b8d7a23 100644 --- a/src/restic_compose_backup/cli.py +++ b/src/restic_compose_backup/cli.py @@ -316,6 +316,7 @@ def cleanup(config, containers): config.keep_monthly, config.keep_yearly, config.keep_tags, + config.filter_tags ) logger.info('Prune stale data freeing storage space') prune_result = restic.prune(config.repository) diff --git a/src/restic_compose_backup/config.py b/src/restic_compose_backup/config.py index 354a0a4..f53f4cf 100644 --- a/src/restic_compose_backup/config.py +++ b/src/restic_compose_backup/config.py @@ -28,6 +28,7 @@ def __init__(self, check=True): self.keep_monthly = os.environ.get('KEEP_MONTHLY') or "12" self.keep_yearly = os.environ.get('KEEP_YEARLY') or "3" self.keep_tags = os.environ.get('KEEP_TAGS') or "keep" + self.filter_tags = os.environ.get('FILTER_TAGS') or "" if check: self.check() diff --git a/src/restic_compose_backup/containers.py b/src/restic_compose_backup/containers.py index c43c64f..d1b107b 100644 --- a/src/restic_compose_backup/containers.py +++ b/src/restic_compose_backup/containers.py @@ -186,6 +186,11 @@ def is_backup_process_container(self) -> bool: """Is this container the running backup process?""" return self.get_label(self.backup_process_label) == 'True' + @property + def tags(self) -> str: + """Gets all backup tags""" + return self.get_label(enums.LABEL_RESTIC_TAGS) + @property def is_running(self) -> bool: """bool: Is the container running?""" diff --git a/src/restic_compose_backup/containers_db.py b/src/restic_compose_backup/containers_db.py index 736b807..d07a5c2 100644 --- a/src/restic_compose_backup/containers_db.py +++ b/src/restic_compose_backup/containers_db.py @@ -54,6 +54,7 @@ def backup(self): config.repository, self.backup_destination_path(), self.dump_command(), + tags=self.tags ) def backup_destination_path(self) -> str: @@ -115,6 +116,7 @@ def backup(self): config.repository, self.backup_destination_path(), self.dump_command(), + tags=self.tags ) def backup_destination_path(self) -> str: @@ -175,6 +177,7 @@ def backup(self): config.repository, self.backup_destination_path(), self.dump_command(), + tags=self.tags ) def backup_destination_path(self) -> str: diff --git a/src/restic_compose_backup/containers_minecraft.py b/src/restic_compose_backup/containers_minecraft.py index c3a68a6..56e24b6 100644 --- a/src/restic_compose_backup/containers_minecraft.py +++ b/src/restic_compose_backup/containers_minecraft.py @@ -64,7 +64,7 @@ def backup(self) -> bool: for mount in self.filter_mounts(): backup_data = self.get_volume_backup_destination(mount, '/minecraft') logger.info('Backing up %s', mount.source) - vol_result = restic.backup_files(config.repository, source=backup_data) + vol_result = restic.backup_files(config.repository, source=backup_data, tags=self.tags) logger.debug('Minecraft backup exit code: %s', vol_result) if vol_result != 0: logger.error('Minecraft backup exited with non-zero code: %s', vol_result) diff --git a/src/restic_compose_backup/enums.py b/src/restic_compose_backup/enums.py index b32b0ea..a76c666 100644 --- a/src/restic_compose_backup/enums.py +++ b/src/restic_compose_backup/enums.py @@ -10,4 +10,6 @@ LABEL_BACKUP_PROCESS = 'restic-compose-backup.process' -LABEL_MINECRAFT_ENABLED = 'restic-compose-backup.minecraft' \ No newline at end of file +LABEL_MINECRAFT_ENABLED = 'restic-compose-backup.minecraft' + +LABEL_RESTIC_TAGS = 'restic-compose-backup.tags' \ No newline at end of file diff --git a/src/restic_compose_backup/restic.py b/src/restic_compose_backup/restic.py index 35753b5..7900488 100644 --- a/src/restic_compose_backup/restic.py +++ b/src/restic_compose_backup/restic.py @@ -4,7 +4,7 @@ import logging from typing import List, Tuple from subprocess import Popen, PIPE -from restic_compose_backup import commands +from restic_compose_backup import commands, utils logger = logging.getLogger(__name__) @@ -19,25 +19,29 @@ def init_repo(repository: str): ])) -def backup_files(repository: str, source='/volumes'): - return commands.run(restic(repository, [ +def backup_files(repository: str, source='/volumes', tags=''): + args = [ "--verbose", "backup", - source, - ])) + source + ] + args.extend(utils.format_tags(tags)) + return commands.run(restic(repository, args)) -def backup_from_stdin(repository: str, filename: str, source_command: List[str]): +def backup_from_stdin(repository: str, filename: str, source_command: List[str], tags=''): """ Backs up from stdin running the source_command passed in. It will appear in restic with the filename (including path) passed in. """ - dest_command = restic(repository, [ + args = [ 'backup', '--stdin', '--stdin-filename', filename, - ]) + ] + args.extend(utils.format_tags(tags)) + dest_command = restic(repository, args) # pipe source command into dest command source_process = Popen(source_command, stdout=PIPE, bufsize=65536) @@ -75,8 +79,8 @@ def is_initialized(repository: str) -> bool: return commands.run(restic(repository, ["snapshots", '--last'])) == 0 -def forget(repository: str, keeplast: str, hourly: str, daily: str, weekly: str, monthly: str, yearly: str, tags: str): - return commands.run(restic(repository, [ +def forget(repository: str, keeplast: str, hourly: str, daily: str, weekly: str, monthly: str, yearly: str, keep_tags='', filter_tags=''): + args = [ 'forget', '--group-by', 'paths,tags', @@ -91,10 +95,11 @@ def forget(repository: str, keeplast: str, hourly: str, daily: str, weekly: str, '--keep-monthly', monthly, '--keep-yearly', - yearly, - '--keep-tag', - tags, - ])) + yearly + ] + args.extend(utils.format_tags(keep_tags, '--keep-tag')) + args.extend(utils.format_tags(filter_tags)) + return commands.run(restic(repository, args)) def prune(repository: str): diff --git a/src/restic_compose_backup/utils.py b/src/restic_compose_backup/utils.py index 0f96fbe..17a1149 100644 --- a/src/restic_compose_backup/utils.py +++ b/src/restic_compose_backup/utils.py @@ -82,6 +82,25 @@ def strip_root(path): return path +def format_tags(tags: str, arg = "--tag") -> List[str]: + """ + Takes a comma separated list of tags. + Splits them and appends --tag to each tag. + Use the output as the command line argument for the restic cli. + Example: foo,bar,test becomes --tag foo --tag bar --tag test + """ + if not tags: + return [] + + tags = tags.strip() + splitTags = tags.split(",") + output = [] + for tag in splitTags: + tag = tag.strip() + if tag: + output.extend([arg, tag]) + + return output @contextmanager def environment(name, value):