diff --git a/netbox_config_backup/__init__.py b/netbox_config_backup/__init__.py index d094da3..a5cad9b 100644 --- a/netbox_config_backup/__init__.py +++ b/netbox_config_backup/__init__.py @@ -1,6 +1,12 @@ +from datetime import datetime from importlib.metadata import metadata +from django.utils import timezone +from django_rq import get_queue + +from core.choices import JobStatusChoices from netbox.plugins import PluginConfig +from netbox_config_backup.utils.logger import get_logger metadata = metadata('netbox_config_backup') @@ -14,7 +20,7 @@ class NetboxConfigBackup(PluginConfig): author_email = metadata.get('Author-email') base_url = 'configbackup' min_version = '4.1.0' - max_version = '4.1.99' + max_version = '4.2.99' required_settings = [ 'repository', 'committer', @@ -35,9 +41,17 @@ def ready(self, *args, **kwargs): if 'rqworker' in sys.argv[1]: from netbox import settings from netbox_config_backup.jobs.backup import BackupRunner - from netbox_config_backup.models import BackupJob, Backup frequency = settings.PLUGINS_CONFIG.get('netbox_config_backup', {}).get('frequency') / 60 - BackupRunner.enqueue_once(interval=frequency) + lastjob = BackupRunner.get_jobs().order_by('pk').last() + + if lastjob.status in JobStatusChoices.ENQUEUED_STATE_CHOICES and lastjob.scheduled < timezone.now(): + BackupRunner.enqueue_once(interval=frequency) + elif lastjob.status in JobStatusChoices.TERMINAL_STATE_CHOICES: + scheduled = lastjob.created + timezone.timedelta(minutes=frequency) + if scheduled < timezone.now(): + scheduled = None + BackupRunner.enqueue_once(interval=frequency, schedule_at=scheduled) + config = NetboxConfigBackup diff --git a/netbox_config_backup/jobs/backup.py b/netbox_config_backup/jobs/backup.py index 142b3d1..aa194a9 100644 --- a/netbox_config_backup/jobs/backup.py +++ b/netbox_config_backup/jobs/backup.py @@ -59,8 +59,17 @@ def clean_stale_jobs(self): job.data.update({'error': 'Process terminated'}) job.save() - def schedule_jobs(self): - backups = Backup.objects.filter(status=StatusChoices.STATUS_ACTIVE, device__isnull=False) + def schedule_jobs(self, backup=None, device=None): + if backup: + logging.debug(f'Scheduling backup for backup: {backup}') + backups = Backup.objects.filter(pk=backup.pk, status=StatusChoices.STATUS_ACTIVE, device__isnull=False) + elif device: + logging.debug(f'Scheduling backup for device: {device}') + backups = Backup.objects.filter(device=device, status=StatusChoices.STATUS_ACTIVE, device__isnull=False) + else: + logging.debug(f'Scheduling all backups') + backups = Backup.objects.filter(status=StatusChoices.STATUS_ACTIVE, device__isnull=False) + for backup in backups: if can_backup(backup): logger.debug(f'Queuing device {backup.device} for backup') @@ -92,7 +101,7 @@ def schedule_jobs(self): job.save() def run_processes(self): - for job in BackupJob.objects.filter(status=JobStatusChoices.STATUS_SCHEDULED): + for job in BackupJob.objects.filter(runner=self.job, status=JobStatusChoices.STATUS_SCHEDULED): try: process = self.fork_process(job) process.join(1) @@ -134,10 +143,10 @@ def handle_processes(self): job.data.update({'error': 'Process terminated'}) job.save() - def run(self, *args, **kwargs): + def run(self, backup=None, device=None, *args, **kwargs): try: self.clean_stale_jobs() - self.schedule_jobs() + self.schedule_jobs(backup=backup, device=device) self.run_processes() while(True): self.handle_processes() diff --git a/netbox_config_backup/management/commands/runbackup.py b/netbox_config_backup/management/commands/runbackup.py index 9b846d3..d63673c 100644 --- a/netbox_config_backup/management/commands/runbackup.py +++ b/netbox_config_backup/management/commands/runbackup.py @@ -1,11 +1,7 @@ -import uuid - from django.core.management.base import BaseCommand -from django.db import transaction -from django.utils import timezone -from netbox_config_backup.models import BackupJob -from netbox_config_backup.utils.rq import can_backup +from netbox_config_backup.jobs.backup import BackupRunner +from netbox_config_backup.models import Backup class Command(BaseCommand): @@ -13,16 +9,19 @@ def add_arguments(self, parser): parser.add_argument('--time', dest='time', help="time") parser.add_argument('--device', dest='device', help="Device Name") + def run_backup(self, backup=None): + BackupRunner.enqueue(backup=backup, immediate=True) + def handle(self, *args, **options): - from netbox_config_backup.models import Backup if options['device']: - print(f'Running:{options.get("device")}| ') + print(f'Running backup for: {options.get("device")}') backup = Backup.objects.filter(device__name=options['device']).first() + if not backup: + backup = Backup.objects.filter(name=options['device']).first() if backup: self.run_backup(backup) else: raise Exception('Device not found') else: - for backup in Backup.objects.all(): - self.run_backup(backup) + self.run_backup() diff --git a/netbox_config_backup/models/backups.py b/netbox_config_backup/models/backups.py index 49ed9c0..8be11e8 100644 --- a/netbox_config_backup/models/backups.py +++ b/netbox_config_backup/models/backups.py @@ -31,6 +31,7 @@ class Backup(PrimaryModel): device = models.ForeignKey( to=Device, on_delete=models.SET_NULL, + related_name='backups', blank=True, null=True ) diff --git a/netbox_config_backup/tables.py b/netbox_config_backup/tables.py index 52eb3d6..e4f5755 100644 --- a/netbox_config_backup/tables.py +++ b/netbox_config_backup/tables.py @@ -8,6 +8,9 @@ class ActionButtonsColumn(tables.TemplateColumn): attrs = {'td': {'class': 'text-end text-nowrap noprint min-width'}} template_code = """ + + + diff --git a/netbox_config_backup/template_content.py b/netbox_config_backup/template_content.py index 43107e3..e69de29 100644 --- a/netbox_config_backup/template_content.py +++ b/netbox_config_backup/template_content.py @@ -1,49 +0,0 @@ -import logging - -from netbox.plugins import PluginTemplateExtension - -from netbox_config_backup.models import Backup, BackupJob, BackupCommitTreeChange -from netbox_config_backup.tables import BackupsTable -from netbox_config_backup.utils.backups import get_backup_tables -from utilities.htmx import htmx_partial - -logger = logging.getLogger(f"netbox_config_backup") - - -class DeviceBackups(PluginTemplateExtension): - model = 'dcim.device' - - def full_width_page(self): - request = self.context.get('request') - def build_table(instance): - bctc = BackupCommitTreeChange.objects.filter( - backup=instance, - file__isnull=False - ) - table = BackupsTable(bctc, user=request.user) - table.configure(request) - - return table - - device = self.context.get('object', None) - devices = Backup.objects.filter(device=device) if device is not None else Backup.objects.none() - if devices.count() > 0: - instance = devices.first() - table = build_table(instance) - - if htmx_partial(request): - return self.render('htmx/table.html', extra_context={ - 'object': instance, - 'table': table, - 'preferences': {}, - }) - return self.render('netbox_config_backup/inc/backup_tables.html', extra_context={ - 'object': instance, - 'table': table, - 'preferences': request.user.config, - }) - - return '' - - -template_extensions = [DeviceBackups] diff --git a/netbox_config_backup/templates/netbox_config_backup/backups.html b/netbox_config_backup/templates/netbox_config_backup/backups.html index 43439db..f3d14a1 100644 --- a/netbox_config_backup/templates/netbox_config_backup/backups.html +++ b/netbox_config_backup/templates/netbox_config_backup/backups.html @@ -21,12 +21,14 @@ {% block bulk_edit_controls %} {{ block.super }} - {% with diff_view=object|viewname:"diff" %} + {% if backup %} + {% with diff_view=backup|viewname:"diff" %} - - {% endwith %} + + {% endwith %} + {% endif %} {% endblock bulk_edit_controls %} diff --git a/netbox_config_backup/templates/netbox_config_backup/compliance.html b/netbox_config_backup/templates/netbox_config_backup/compliance.html new file mode 100644 index 0000000..87c75e8 --- /dev/null +++ b/netbox_config_backup/templates/netbox_config_backup/compliance.html @@ -0,0 +1,34 @@ +{% extends 'generic/object.html' %} +{% load helpers %} + +{% block subtitle %} +
{% for line in diff %}{% spaceless %} + {% if line == '+++' or line == '---' %} + {% elif line|make_list|first == '@' %} + {{line}} + {% elif line|make_list|first == '+' %} + {{line|make_list|slice:'1:'|join:''}} + {% elif line|make_list|first == '-' %} + {{line|make_list|slice:'1:'|join:''}} + {% else %} + {{line}} + {% endif %} + {% endspaceless %}{% endfor %}+