mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-20 10:34:30 +00:00
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:
parent
d3f8e815c1
commit
01e00cdde4
@ -13,6 +13,7 @@ import sys
|
|||||||
import tarfile
|
import tarfile
|
||||||
|
|
||||||
from plinth.modules.backups import MANIFESTS_FOLDER
|
from plinth.modules.backups import MANIFESTS_FOLDER
|
||||||
|
from plinth.utils import Version
|
||||||
|
|
||||||
TIMEOUT = 30
|
TIMEOUT = 30
|
||||||
|
|
||||||
@ -38,6 +39,9 @@ def parse_arguments():
|
|||||||
help='Create archive')
|
help='Create archive')
|
||||||
create_archive.add_argument('--paths', help='Paths to include in archive',
|
create_archive.add_argument('--paths', help='Paths to include in archive',
|
||||||
nargs='+')
|
nargs='+')
|
||||||
|
create_archive.add_argument('--comment',
|
||||||
|
help='Comment text to add to archive',
|
||||||
|
default='')
|
||||||
|
|
||||||
delete_archive = subparsers.add_parser('delete-archive',
|
delete_archive = subparsers.add_parser('delete-archive',
|
||||||
help='Delete archive')
|
help='Delete archive')
|
||||||
@ -112,13 +116,32 @@ def subcommand_info(arguments):
|
|||||||
|
|
||||||
def subcommand_list_repo(arguments):
|
def subcommand_list_repo(arguments):
|
||||||
"""List repository contents."""
|
"""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):
|
def subcommand_create_archive(arguments):
|
||||||
"""Create archive."""
|
"""Create archive."""
|
||||||
paths = filter(os.path.exists, arguments.paths)
|
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):
|
def subcommand_delete_archive(arguments):
|
||||||
|
|||||||
@ -85,7 +85,11 @@ def _backup_handler(packet, encryption_passphrase=None):
|
|||||||
|
|
||||||
paths = packet.directories + packet.files
|
paths = packet.directories + packet.files
|
||||||
paths.append(manifest_path)
|
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 = ''
|
input_data = ''
|
||||||
if encryption_passphrase:
|
if encryption_passphrase:
|
||||||
input_data = json.dumps(
|
input_data = json.dumps(
|
||||||
|
|||||||
@ -41,7 +41,8 @@ class BackupError:
|
|||||||
class Packet:
|
class Packet:
|
||||||
"""Information passed to a handlers for backup/restore operations."""
|
"""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.
|
"""Initialize the packet.
|
||||||
|
|
||||||
operation is either 'backup' or 'restore.
|
operation is either 'backup' or 'restore.
|
||||||
@ -62,6 +63,7 @@ class Packet:
|
|||||||
self.root = root
|
self.root = root
|
||||||
self.components = components
|
self.components = components
|
||||||
self.path = path
|
self.path = path
|
||||||
|
self.archive_comment = archive_comment
|
||||||
self.errors = []
|
self.errors = []
|
||||||
|
|
||||||
self.directories = []
|
self.directories = []
|
||||||
@ -105,8 +107,8 @@ def restore_full(restore_handler):
|
|||||||
_switch_to_subvolume(subvolume)
|
_switch_to_subvolume(subvolume)
|
||||||
|
|
||||||
|
|
||||||
def backup_apps(backup_handler, path, app_ids=None,
|
def backup_apps(backup_handler, path, app_ids=None, encryption_passphrase=None,
|
||||||
encryption_passphrase=None):
|
archive_comment=None):
|
||||||
"""Backup data belonging to a set of applications."""
|
"""Backup data belonging to a set of applications."""
|
||||||
if not app_ids:
|
if not app_ids:
|
||||||
components = get_all_components_for_backup()
|
components = get_all_components_for_backup()
|
||||||
@ -123,7 +125,8 @@ def backup_apps(backup_handler, path, app_ids=None,
|
|||||||
backup_root = '/'
|
backup_root = '/'
|
||||||
snapshotted = False
|
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,
|
_run_operation(backup_handler, packet,
|
||||||
encryption_passphrase=encryption_passphrase)
|
encryption_passphrase=encryption_passphrase)
|
||||||
|
|
||||||
|
|||||||
@ -166,12 +166,13 @@ class BaseBorgRepository(abc.ABC):
|
|||||||
return sorted(archives, key=lambda archive: archive['start'],
|
return sorted(archives, key=lambda archive: archive['start'],
|
||||||
reverse=True)
|
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."""
|
"""Create a new archive in this repository with given name."""
|
||||||
archive_path = self._get_archive_path(archive_name)
|
archive_path = self._get_archive_path(archive_name)
|
||||||
passphrase = self.credentials.get('encryption_passphrase', None)
|
passphrase = self.credentials.get('encryption_passphrase', None)
|
||||||
api.backup_apps(_backup_handler, path=archive_path, app_ids=app_ids,
|
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):
|
def delete_archive(self, archive_name):
|
||||||
"""Delete an archive with given name from this repository."""
|
"""Delete an archive with given name from this repository."""
|
||||||
|
|||||||
@ -63,11 +63,21 @@ def _get_test_app(name):
|
|||||||
class TestBackupProcesses:
|
class TestBackupProcesses:
|
||||||
"""Test cases for backup processes"""
|
"""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
|
@staticmethod
|
||||||
def test_packet_collected_files_directories():
|
def test_packet_collected_files_directories():
|
||||||
"""Test that directories/files are collected from manifests."""
|
"""Test that directories/files are collected from manifests."""
|
||||||
components = [_get_backup_component('a'), _get_backup_component('b')]
|
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 component in components:
|
||||||
for section in ['config', 'data', 'secrets']:
|
for section in ['config', 'data', 'secrets']:
|
||||||
for directory in getattr(component, section)['directories']:
|
for directory in getattr(component, section)['directories']:
|
||||||
|
|||||||
@ -88,18 +88,21 @@ def test_create_export_delete_archive(data_directory, backup_directory):
|
|||||||
"""
|
"""
|
||||||
repo_name = 'test_create_and_delete'
|
repo_name = 'test_create_and_delete'
|
||||||
archive_name = 'first_archive'
|
archive_name = 'first_archive'
|
||||||
|
archive_comment = 'test_archive_comment'
|
||||||
path = backup_directory / repo_name
|
path = backup_directory / repo_name
|
||||||
|
|
||||||
repository = BorgRepository(str(path))
|
repository = BorgRepository(str(path))
|
||||||
repository.initialize()
|
repository.initialize()
|
||||||
archive_path = "::".join([str(path), archive_name])
|
archive_path = "::".join([str(path), archive_name])
|
||||||
actions.superuser_run('backups', [
|
actions.superuser_run('backups', [
|
||||||
'create-archive', '--path', archive_path, '--paths',
|
'create-archive', '--path', archive_path, '--comment', archive_comment,
|
||||||
|
'--paths',
|
||||||
str(data_directory)
|
str(data_directory)
|
||||||
])
|
])
|
||||||
|
|
||||||
archive = repository.list_archives()[0]
|
archive = repository.list_archives()[0]
|
||||||
assert archive['name'] == archive_name
|
assert archive['name'] == archive_name
|
||||||
|
assert archive['comment'] == archive_comment
|
||||||
|
|
||||||
repository.delete_archive(archive_name)
|
repository.delete_archive(archive_name)
|
||||||
content = repository.list_archives()
|
content = repository.list_archives()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user