backups: Save new backup location to plinth database

Signed-off-by: Joseph Nuthalapati <njoseph@riseup.net>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Joseph Nuthalpati 2019-08-05 21:52:51 +05:30 committed by James Valleroy
parent 7c7ad6d56a
commit de4a019063
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 89 additions and 52 deletions

View File

@ -81,6 +81,7 @@ KNOWN_ERRORS = [{
class BaseBorgRepository(ABC):
"""Base class for all kinds of Borg repositories."""
uuid = None
is_mounted = True
def __init__(self, uuid=None, path=None, credentials=None, **kwargs):
"""
@ -262,6 +263,27 @@ class BaseBorgRepository(ABC):
create_subvolume=False, backup_file=archive_path,
encryption_passphrase=passphrase)
def _get_network_storage_format(self, store_credentials, verified):
storage = {
'path': self._path,
'storage_type': self.storage_type,
'added_by_module': 'backups',
'verified': verified
}
if self.uuid:
storage['uuid'] = self.uuid
if store_credentials:
storage['credentials'] = self.credentials
return storage
def save(self, store_credentials=True, verified=False):
"""
Save the repository in network_storage (kvstore).
- store_credentials: Boolean whether credentials should be stored.
"""
storage = self._get_network_storage_format(store_credentials, verified)
self.uuid = network_storage.update_or_add(storage)
class RootBorgRepository(BaseBorgRepository):
"""Borg repository on the root filesystem."""
@ -287,20 +309,16 @@ class BorgRepository(BaseBorgRepository):
KNOWN_CREDENTIALS = ['encryption_passphrase']
storage_type = 'disk'
@property
def is_mounted(self):
raise NotImplementedError
@property
def name(self):
raise NotImplementedError
# TODO Use disk label as the name
# Also, name isn't being used yet
return self.repo_path
@property
def repo_path(self):
"""
Return the path to use for backups actions.
"""
raise NotImplementedError
def remove_repository(self):
"""Remove a repository from the kvstore and delete its mountpoint"""
self.umount()
network_storage.delete(self.uuid)
class SshBorgRepository(BaseBorgRepository):
@ -331,27 +349,6 @@ class SshBorgRepository(BaseBorgRepository):
['is-mounted', '--mountpoint', self.mountpoint])
return json.loads(output)
def _get_network_storage_format(self, store_credentials, verified):
storage = {
'path': self._path,
'storage_type': self.storage_type,
'added_by_module': 'backups',
'verified': verified
}
if self.uuid:
storage['uuid'] = self.uuid
if store_credentials:
storage['credentials'] = self.credentials
return storage
def save(self, store_credentials=True, verified=False):
"""
Save the repository in network_storage (kvstore).
- store_credentials: Boolean whether credentials should be stored.
"""
storage = self._get_network_storage_format(store_credentials, verified)
self.uuid = network_storage.update_or_add(storage)
def mount(self):
if self.is_mounted:
return
@ -400,19 +397,43 @@ class SshBorgRepository(BaseBorgRepository):
return (arguments, kwargs)
def get_ssh_repositories():
def get_repositories(storage_type):
"""Get all repositories of a given storage type."""
if storage_type == 'disk':
return _get_disk_repositories()
return _get_ssh_repositories()
def _get_ssh_repositories():
"""Get all SSH Repositories including the archive content"""
repositories = {}
for storage in network_storage.get_storages().values():
repository = SshBorgRepository(**storage)
repositories[storage['uuid']] = repository.get_view_content()
if storage['storage_type'] == 'ssh':
repository = SshBorgRepository(**storage)
repositories[storage['uuid']] = repository.get_view_content()
return repositories
def get_repository(uuid):
"""Get a local or SSH repository object instance."""
def _get_disk_repositories():
"""Get all disk repositories including the archive content"""
repositories = {}
for storage in network_storage.get_storages().values():
if storage['storage_type'] == 'disk':
repository = BorgRepository(**storage)
repositories[storage['uuid']] = repository.get_view_content()
return repositories
def create_repository(uuid):
"""Create a local or SSH repository object instance."""
if uuid == ROOT_REPOSITORY_UUID:
return RootBorgRepository(path=ROOT_REPOSITORY)
return SshBorgRepository(uuid=uuid)
storage = network_storage.get(uuid)
if storage['storage_type'] == 'ssh':
return SshBorgRepository(uuid=uuid)
else:
return BorgRepository(uuid=uuid)

View File

@ -60,10 +60,14 @@
<h3>{% trans 'Existing Backups' %}</h3>
{% include "backups_repository.inc" with repository=root_repository uuid='root' editable=False %}
{% include "backups_repository.inc" with repository=root_repository uuid='root' storage_type='root' editable=False %}
{% for uuid,repository in ssh_repositories.items %}
{% include "backups_repository.inc" with editable=True %}
{% include "backups_repository.inc" with editable=True storage_type='ssh' %}
{% endfor %}
{% for uuid,repository in disk_repositories.items %}
{% include "backups_repository.inc" with editable=True storage_type='disk' %}
{% endfor %}
<a title="{% trans 'Add a backup location' %}"

View File

@ -36,6 +36,7 @@
<span class="pull-right">
{% if editable %}
{% if storage_type != 'disk' %}
{% if repository.mounted %}
<!-- With GET redirects, the browser URL points to the
@ -62,6 +63,7 @@
</form>
{% endif %}
{% endif %}
<a title="{% trans 'Remove Backup Location. This will not delete the remote backup.' %}"
role="button" class="repository-remove btn btn-sm btn-default"

View File

@ -43,8 +43,8 @@ from . import (ROOT_REPOSITORY, SESSION_PATH_VARIABLE, api, forms,
split_path)
from .decorators import delete_tmp_backup_file
from .errors import BorgRepositoryDoesNotExistError
from .repository import (RootBorgRepository, SshBorgRepository, get_repository,
get_ssh_repositories)
from .repository import (BorgRepository, RootBorgRepository, SshBorgRepository,
create_repository, get_repositories)
logger = logging.getLogger(__name__)
@ -62,7 +62,8 @@ class IndexView(TemplateView):
context['manual_page'] = backups.manual_page
root_repository = RootBorgRepository(path=ROOT_REPOSITORY)
context['root_repository'] = root_repository.get_view_content()
context['ssh_repositories'] = get_ssh_repositories()
context['ssh_repositories'] = get_repositories('ssh')
context['disk_repositories'] = get_repositories('disk')
return context
@ -82,7 +83,7 @@ class CreateArchiveView(SuccessMessageMixin, FormView):
def form_valid(self, form):
"""Create the archive on valid form submission."""
repository = get_repository(form.cleaned_data['repository'])
repository = create_repository(form.cleaned_data['repository'])
if hasattr(repository, 'mount'):
repository.mount()
@ -100,7 +101,7 @@ class DeleteArchiveView(SuccessMessageMixin, TemplateView):
"""Return additional context for rendering the template."""
context = super().get_context_data(**kwargs)
context['title'] = _('Delete Archive')
repository = get_repository(self.kwargs['uuid'])
repository = create_repository(self.kwargs['uuid'])
context['archive'] = repository.get_archive(self.kwargs['name'])
if context['archive'] is None:
raise Http404
@ -109,7 +110,7 @@ class DeleteArchiveView(SuccessMessageMixin, TemplateView):
def post(self, request, uuid, name):
"""Delete the archive."""
repository = get_repository(uuid)
repository = create_repository(uuid)
repository.delete_archive(name)
messages.success(request, _('Archive deleted.'))
return redirect('backups:index')
@ -218,12 +219,12 @@ class RestoreArchiveView(BaseRestoreView):
"""Save some data used to instantiate the form."""
name = unquote(self.kwargs['name'])
uuid = self.kwargs['uuid']
repository = get_repository(uuid)
repository = create_repository(uuid)
return repository.get_archive_apps(name)
def form_valid(self, form):
"""Restore files from the archive on valid form submission."""
repository = get_repository(self.kwargs['uuid'])
repository = create_repository(self.kwargs['uuid'])
selected_apps = form.cleaned_data['selected_apps']
repository.restore_archive(self.kwargs['name'], selected_apps)
return super().form_valid(form)
@ -233,7 +234,7 @@ class DownloadArchiveView(View):
"""View to export and download an archive as stream."""
def get(self, request, uuid, name):
repository = get_repository(uuid)
repository = create_repository(uuid)
filename = f'{name}.tar.gz'
response = StreamingHttpResponse(repository.get_download_stream(name),
@ -257,12 +258,21 @@ class AddRepositoryView(SuccessMessageMixin, FormView):
def form_valid(self, form):
"""Create and save a Borg repository."""
path = pathlib.Path(
form.cleaned_data.get('disk')) / 'FreedomBoxBackups'
path = os.path.join(form.cleaned_data.get('disk'), 'FreedomBoxBackups')
encryption = form.cleaned_data.get('encryption')
encryption_passphrase = form.cleaned_data.get('encryption_passphrase')
if form.cleaned_data.get('encryption') == 'none':
if encryption == 'none':
encryption_passphrase = None
credentials = {'encryption_passphrase': encryption_passphrase}
repository = BorgRepository(path=path, credentials=credentials)
try:
repository.get_info()
except BorgRepositoryDoesNotExistError:
repository.create_repository(encryption)
repository.save(store_credentials=True, verified=True)
return super().form_valid(form)
class AddRemoteRepositoryView(SuccessMessageMixin, FormView):
"""View to create a new remote backup repository."""