backups: Make all generated archive names consistent

- For scheduled backups and automatic backups generated when uninstalling apps,
make the generated archive name similar to automatic name given no name is
specified when creating an archive.

Tests:

- Set schedule on a local repository, uninstall an application, and create a
backup archive without a name. The timestamp in generated backup archives in all
three cases is similar in format.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2025-01-06 16:41:03 -08:00 committed by James Valleroy
parent d710ab0790
commit 28e4fe0791
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
5 changed files with 21 additions and 14 deletions

View File

@ -241,6 +241,11 @@ class BaseBorgRepository(abc.ABC):
return None return None
def generate_archive_name(self):
"""Return a name to create a backup archive with using time."""
return datetime.datetime.now().astimezone().replace(
microsecond=0).isoformat()
def get_archive_apps(self, archive_name): def get_archive_apps(self, archive_name):
"""Get list of apps included in an archive.""" """Get list of apps included in an archive."""
archive_path = self._get_archive_path(archive_name) archive_path = self._get_archive_path(archive_name)

View File

@ -274,12 +274,14 @@ class Schedule:
logger.info('Running backup for repository %s, periods %s', logger.info('Running backup for repository %s, periods %s',
self.repository_uuid, periods) self.repository_uuid, periods)
repository = self._get_repository()
from . import api from . import api
periods = list(periods) periods = list(periods)
periods.sort() periods.sort()
name = 'scheduled: {periods}: {datetime}'.format( name = 'scheduled: {periods}: {name}'.format(
periods=', '.join(periods), periods=', '.join(periods),
datetime=datetime.now().strftime('%Y-%m-%d:%H:%M')) name=repository.generate_archive_name())
comment = self._serialize_comment({ comment = self._serialize_comment({
'type': 'scheduled', 'type': 'scheduled',
'periods': periods 'periods': periods
@ -290,7 +292,6 @@ class Schedule:
if component.app_id not in self.unselected_apps if component.app_id not in self.unselected_apps
] ]
repository = self._get_repository()
repository.create_archive(name, app_ids, archive_comment=comment) repository.create_archive(name, app_ids, archive_comment=comment)
def _run_cleanup(self, repository): def _run_cleanup(self, repository):

View File

@ -9,7 +9,7 @@ from unittest.mock import MagicMock, call, patch
import pytest import pytest
import plinth.modules.backups.repository # noqa, pylint: disable=unused-import import plinth.modules.backups.repository as repository_module
from plinth.app import App from plinth.app import App
from ..components import BackupRestore from ..components import BackupRestore
@ -431,15 +431,19 @@ def test_run_schedule(get_instance, get_setup_state, schedule_params,
repository.list_archives.side_effect = \ repository.list_archives.side_effect = \
lambda: _get_archives_from_test_data(archives_data) lambda: _get_archives_from_test_data(archives_data)
get_instance.return_value = repository get_instance.return_value = repository
repository.generate_archive_name = lambda: \
repository_module.BaseBorgRepository.generate_archive_name(None)
with patch('plinth.modules.backups.schedule.datetime') as mock_datetime, \ with patch('plinth.modules.backups.schedule.datetime') as mock_datetime, \
patch('plinth.app.App.list') as app_list: patch('plinth.modules.backups.repository.datetime') \
as repo_datetime, patch('plinth.app.App.list') as app_list:
app_list.return_value = [ app_list.return_value = [
_get_test_app('test-app1'), _get_test_app('test-app1'),
_get_test_app('test-app2'), _get_test_app('test-app2'),
_get_test_app('test-app3') _get_test_app('test-app3')
] ]
repo_datetime.datetime.now.return_value = test_now
mock_datetime.now.return_value = test_now mock_datetime.now.return_value = test_now
mock_datetime.strptime = datetime.strptime mock_datetime.strptime = datetime.strptime
mock_datetime.min = datetime.min mock_datetime.min = datetime.min
@ -458,7 +462,8 @@ def test_run_schedule(get_instance, get_setup_state, schedule_params,
run_periods.sort() run_periods.sort()
name = 'scheduled: {periods}: {datetime}'.format( name = 'scheduled: {periods}: {datetime}'.format(
periods=', '.join(run_periods), periods=', '.join(run_periods),
datetime=mock_datetime.now().strftime('%Y-%m-%d:%H:%M')) datetime=repo_datetime.datetime.now().astimezone().replace(
microsecond=0).isoformat())
app_ids = ['test-app1', 'test-app3'] app_ids = ['test-app1', 'test-app3']
archive_comment = json.dumps({ archive_comment = json.dumps({
'type': 'scheduled', 'type': 'scheduled',

View File

@ -7,7 +7,6 @@ import contextlib
import logging import logging
import os import os
import subprocess import subprocess
from datetime import datetime
from urllib.parse import unquote from urllib.parse import unquote
from django.contrib import messages from django.contrib import messages
@ -141,8 +140,7 @@ class CreateArchiveView(FormView):
name = form.cleaned_data['name'] name = form.cleaned_data['name']
if not name: if not name:
name = datetime.now().astimezone().replace( name = repository.generate_archive_name()
microsecond=0).isoformat()
selected_apps = form.cleaned_data['selected_apps'] selected_apps = form.cleaned_data['selected_apps']
with handle_common_errors(self.request): with handle_common_errors(self.request):

View File

@ -3,7 +3,6 @@
Main FreedomBox views. Main FreedomBox views.
""" """
import datetime
import random import random
import time import time
import traceback import traceback
@ -607,10 +606,9 @@ class UninstallView(FormView):
if repository.flags.get('mountable'): if repository.flags.get('mountable'):
repository.mount() repository.mount()
name = datetime.datetime.now().strftime( name = repository.generate_archive_name() + ' ' + str(
'%Y-%m-%d:%H:%M:%S') + ' ' + str( _('before uninstall of {app_id}')).format(
_('before uninstall of {app_id}')).format( app_id=self.app.app_id)
app_id=self.app.app_id)
repository.create_archive(name, [self.app.app_id]) repository.create_archive(name, [self.app.app_id])
# Uninstall # Uninstall