Skip to content

Commit

Permalink
feat: add backups_sync management command
Browse files Browse the repository at this point in the history
  • Loading branch information
nijel committed Nov 27, 2024
1 parent 8cd3876 commit c64b430
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 12 deletions.
33 changes: 27 additions & 6 deletions weblate_web/hetzner.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,42 @@ def create_storage_folder(
handle.write(last_report.ssh_key)


def generate_subaccount_data(
dirname: str, service: Service, customer: Customer
) -> dict[str, str]:
return {

Check warning on line 48 in weblate_web/hetzner.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/hetzner.py#L48

Added line #L48 was not covered by tests
"homedirectory": f"weblate/{dirname}",
"ssh": "1",
"external_reachability": "1",
"comment": f"Weblate backup service {service.pk} ({customer.name})",
}


def create_storage_subaccount(
dirname: str, service: Service, customer: Customer
) -> dict:
# Create account on the service
url = SUBACCOUNTS_API.format(settings.STORAGE_BOX)
response = requests.post(
url,
data={
"homedirectory": f"weblate/{dirname}",
"ssh": "1",
"external_reachability": "1",
"comment": f"Weblate backup service {service.pk} ({customer.name})",
},
data=generate_subaccount_data(dirname, service, customer),
auth=(settings.STORAGE_USER, settings.STORAGE_PASSWORD),
timeout=720,
)
response.raise_for_status()
return response.json()


def generate_ssh_url(data: dict) -> str:
return "ssh://{}@{}:23/./backups".format(

Check warning on line 72 in weblate_web/hetzner.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/hetzner.py#L72

Added line #L72 was not covered by tests
data["subaccount"]["username"], data["subaccount"]["server"]
)


def get_storage_subaccounts() -> list[dict]:
url = SUBACCOUNTS_API.format(settings.STORAGE_BOX)
response = requests.get(

Check warning on line 79 in weblate_web/hetzner.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/hetzner.py#L78-L79

Added lines #L78 - L79 were not covered by tests
url, auth=(settings.STORAGE_USER, settings.STORAGE_PASSWORD), timeout=720
)
response.raise_for_status()
return response.json()

Check warning on line 83 in weblate_web/hetzner.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/hetzner.py#L82-L83

Added lines #L82 - L83 were not covered by tests
99 changes: 99 additions & 0 deletions weblate_web/management/commands/backups_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#
# Copyright © Michal Čihař <[email protected]>
#
# This file is part of Weblate <https://weblate.org/>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#


from django.conf import settings
from django.core.management.base import BaseCommand

Check warning on line 22 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L21-L22

Added lines #L21 - L22 were not covered by tests

from weblate_web.hetzner import (

Check warning on line 24 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L24

Added line #L24 was not covered by tests
generate_ssh_url,
generate_subaccount_data,
get_storage_subaccounts,
)
from weblate_web.models import Service

Check warning on line 29 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L29

Added line #L29 was not covered by tests


class Command(BaseCommand):
help = "syncrhonizes backup API"

Check warning on line 33 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L32-L33

Added lines #L32 - L33 were not covered by tests

def add_arguments(self, parser):
parser.add_argument(

Check warning on line 36 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L35-L36

Added lines #L35 - L36 were not covered by tests
"--delete",
default=False,
action="store_true",
help="Delete stale backup repositories",
)

def handle(self, *args, **options):
backup_services: dict[str, Service] = {

Check warning on line 44 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L43-L44

Added lines #L43 - L44 were not covered by tests
service.backup_repository: service
for service in Service.objects.exclude(backup_repository="")
}
processed_repositories = set()
backup_storages = get_storage_subaccounts()

Check warning on line 49 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L48-L49

Added lines #L48 - L49 were not covered by tests

for storage in backup_storages:
# Skip non-weblate subaccounts
homedirectory: str = storage["subaccount"]["homedirectory"]

Check warning on line 53 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L53

Added line #L53 was not covered by tests
if not homedirectory.startswith("weblate/"):
continue

Check warning on line 55 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L55

Added line #L55 was not covered by tests
# Generate SSH URL used for borg
ssh_url = generate_ssh_url(storage)
processed_repositories.add(ssh_url)

Check warning on line 58 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L57-L58

Added lines #L57 - L58 were not covered by tests

# Fetch matching service
try:
service = backup_services[ssh_url]
except KeyError:
self.stderr.write(f"unused URL: {ssh_url}")
continue

Check warning on line 65 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L61-L65

Added lines #L61 - L65 were not covered by tests

# Validate service
customer = service.customer

Check warning on line 68 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L68

Added line #L68 was not covered by tests
if customer is None:
self.stderr.write(f"missing customer: {service.pk}")
continue

Check warning on line 71 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L70-L71

Added lines #L70 - L71 were not covered by tests

# Sync our data
update = False

Check warning on line 74 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L74

Added line #L74 was not covered by tests
if not service.backup_box:
service.backup_box = settings.STORAGE_BOX
update = True
dirname = homedirectory.removeprefix("weblate/")

Check warning on line 78 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L76-L78

Added lines #L76 - L78 were not covered by tests
if service.backup_directory != dirname:
service.backup_directory = dirname
update = True

Check warning on line 81 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L80-L81

Added lines #L80 - L81 were not covered by tests
if update:
self.stdout.write("Updating data for {service.pk} ({customer.name})")
service.save(update_fields=["backup_box", "backup_directory"])

Check warning on line 84 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L83-L84

Added lines #L83 - L84 were not covered by tests

# Sync Hetzner data
storage_data = generate_subaccount_data(dirname, service, customer)

Check warning on line 87 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L87

Added line #L87 was not covered by tests
if storage["subaccount"]["comment"] != storage_data["comment"]:
username: str = storage["subaccount"]["username"]
self.stdout.write(

Check warning on line 90 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L89-L90

Added lines #L89 - L90 were not covered by tests
f"Updating Hetzner data for {username} for {service.pk} ({customer.name})"
)

for service in backup_services.values():
if not service.has_paid_backup():
self.stderr.write(f"not paid: {service.pk} ({service.customer})")

Check warning on line 96 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L96

Added line #L96 was not covered by tests

for extra in set(backup_services) - processed_repositories:
self.stderr.write(f"unused: {extra}")

Check warning on line 99 in weblate_web/management/commands/backups_sync.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/management/commands/backups_sync.py#L99

Added line #L99 was not covered by tests
13 changes: 7 additions & 6 deletions weblate_web/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from weblate_web.payments.utils import send_notification
from weblate_web.zammad import create_dedicated_hosting_ticket

from .hetzner import create_storage_folder, create_storage_subaccount
from .hetzner import create_storage_folder, create_storage_subaccount, generate_ssh_url

if TYPE_CHECKING:
from fakturace.invoices import Invoice
Expand Down Expand Up @@ -794,11 +794,14 @@ def update_status(self):
self.limit_projects = package.limit_projects
self.save()

def create_backup(self):
def has_paid_backup(self) -> bool:
subscriptions = self.hosted_subscriptions | self.backup_subscriptions
return subscriptions.filter(expires__gt=timezone.now()).exists()

def create_backup(self):
if (
not self.backup_repository
and subscriptions.filter(expires__gt=timezone.now()).exists()
and self.has_paid_backup()
and (last_report := self.last_report)
):
self.create_backup_repository(last_report)
Expand All @@ -821,9 +824,7 @@ def create_backup_repository(self, last_report: Report):
# Create account on the service
data = create_storage_subaccount(dirname, self, self.customer)

self.backup_repository = "ssh://{}@{}:23/./backups".format(
data["subaccount"]["username"], data["subaccount"]["server"]
)
self.backup_repository = generate_ssh_url(data)

Check warning on line 827 in weblate_web/models.py

View check run for this annotation

Codecov / codecov/patch

weblate_web/models.py#L827

Added line #L827 was not covered by tests
self.backup_box = settings.STORAGE_BOX
self.backup_directory = dirname
self.save(update_fields=["backup_repository", "backup_box", "backup_directory"])
Expand Down

0 comments on commit c64b430

Please sign in to comment.