backups: Allow comments to be added to archives during backup

Tests performed:

- Schedules are able to store and retrieve comments properly. Information about
schedule backups stored in comments is extracted properly.

- Unit tests run.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2021-01-08 15:48:21 -08:00 committed by James Valleroy
parent d3f8e815c1
commit 01e00cdde4
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
6 changed files with 55 additions and 11 deletions

View File

@ -13,6 +13,7 @@ import sys
import tarfile
from plinth.modules.backups import MANIFESTS_FOLDER
from plinth.utils import Version
TIMEOUT = 30
@ -38,6 +39,9 @@ def parse_arguments():
help='Create archive')
create_archive.add_argument('--paths', help='Paths to include in archive',
nargs='+')
create_archive.add_argument('--comment',
help='Comment text to add to archive',
default='')
delete_archive = subparsers.add_parser('delete-archive',
help='Delete archive')
@ -112,13 +116,32 @@ def subcommand_info(arguments):
def subcommand_list_repo(arguments):
"""List repository contents."""
run(['borg', 'list', '--json', arguments.path], arguments)
run(['borg', 'list', '--json', '--format="{comment}"', arguments.path],
arguments)
def _get_borg_version(arugments):
"""Return the version of borgbackup."""
process = run(['borg', '--version'], arugments, stdout=subprocess.PIPE)
return process.stdout.decode().split()[1] # Example: "borg 1.1.9"
def subcommand_create_archive(arguments):
"""Create archive."""
paths = filter(os.path.exists, arguments.paths)
run(['borg', 'create', '--json', arguments.path] + list(paths), arguments)
command = ['borg', 'create', '--json']
if arguments.comment:
comment = arguments.comment
if Version(_get_borg_version(arguments)) < Version('1.1.10'):
# Undo any placeholder escape sequences in comments as this version
# of borg does not support placeholders. XXX: Drop this code when
# support for borg < 1.1.10 is dropped.
comment = comment.replace('{{', '{').replace('}}', '}')
command += ['--comment', comment]
command += [arguments.path] + list(paths)
run(command, arguments)
def subcommand_delete_archive(arguments):

View File

@ -85,7 +85,11 @@ def _backup_handler(packet, encryption_passphrase=None):
paths = packet.directories + packet.files
paths.append(manifest_path)
arguments = ['create-archive', '--path', packet.path, '--paths'] + paths
arguments = ['create-archive', '--path', packet.path]
if packet.archive_comment:
arguments += ['--comment', packet.archive_comment]
arguments += ['--paths'] + paths
input_data = ''
if encryption_passphrase:
input_data = json.dumps(

View File

@ -41,7 +41,8 @@ class BackupError:
class Packet:
"""Information passed to a handlers for backup/restore operations."""
def __init__(self, operation, scope, root, components=None, path=None):
def __init__(self, operation, scope, root, components=None, path=None,
archive_comment=None):
"""Initialize the packet.
operation is either 'backup' or 'restore.
@ -62,6 +63,7 @@ class Packet:
self.root = root
self.components = components
self.path = path
self.archive_comment = archive_comment
self.errors = []
self.directories = []
@ -105,8 +107,8 @@ def restore_full(restore_handler):
_switch_to_subvolume(subvolume)
def backup_apps(backup_handler, path, app_ids=None,
encryption_passphrase=None):
def backup_apps(backup_handler, path, app_ids=None, encryption_passphrase=None,
archive_comment=None):
"""Backup data belonging to a set of applications."""
if not app_ids:
components = get_all_components_for_backup()
@ -123,7 +125,8 @@ def backup_apps(backup_handler, path, app_ids=None,
backup_root = '/'
snapshotted = False
packet = Packet('backup', 'apps', backup_root, components, path)
packet = Packet('backup', 'apps', backup_root, components, path,
archive_comment)
_run_operation(backup_handler, packet,
encryption_passphrase=encryption_passphrase)

View File

@ -166,12 +166,13 @@ class BaseBorgRepository(abc.ABC):
return sorted(archives, key=lambda archive: archive['start'],
reverse=True)
def create_archive(self, archive_name, app_ids):
def create_archive(self, archive_name, app_ids, archive_comment=None):
"""Create a new archive in this repository with given name."""
archive_path = self._get_archive_path(archive_name)
passphrase = self.credentials.get('encryption_passphrase', None)
api.backup_apps(_backup_handler, path=archive_path, app_ids=app_ids,
encryption_passphrase=passphrase)
encryption_passphrase=passphrase,
archive_comment=archive_comment)
def delete_archive(self, archive_name):
"""Delete an archive with given name from this repository."""

View File

@ -63,11 +63,21 @@ def _get_test_app(name):
class TestBackupProcesses:
"""Test cases for backup processes"""
@staticmethod
def test_packet_init():
"""Test that packet is initialized properly."""
packet = api.Packet('backup', 'apps', '/', [])
assert packet.archive_comment is None
packet = api.Packet('backup', 'apps', '/', [],
archive_comment='test comment')
assert packet.archive_comment == 'test comment'
@staticmethod
def test_packet_collected_files_directories():
"""Test that directories/files are collected from manifests."""
components = [_get_backup_component('a'), _get_backup_component('b')]
packet = api.Packet('backup', 'apps', '/', components)
packet = api.Packet('backup', 'apps', '/', components,
archive_comment='test comment')
for component in components:
for section in ['config', 'data', 'secrets']:
for directory in getattr(component, section)['directories']:

View File

@ -88,18 +88,21 @@ def test_create_export_delete_archive(data_directory, backup_directory):
"""
repo_name = 'test_create_and_delete'
archive_name = 'first_archive'
archive_comment = 'test_archive_comment'
path = backup_directory / repo_name
repository = BorgRepository(str(path))
repository.initialize()
archive_path = "::".join([str(path), archive_name])
actions.superuser_run('backups', [
'create-archive', '--path', archive_path, '--paths',
'create-archive', '--path', archive_path, '--comment', archive_comment,
'--paths',
str(data_directory)
])
archive = repository.list_archives()[0]
assert archive['name'] == archive_name
assert archive['comment'] == archive_comment
repository.delete_archive(archive_name)
content = repository.list_archives()