Просмотр исходного кода

Fixes #19825: Prevent inaccurate config revision activation when not intended (#20219)

Daniel Sheppard 4 месяцев назад
Родитель
Сommit
7719b98697

+ 48 - 0
netbox/core/migrations/0019_configrevision_active.py

@@ -0,0 +1,48 @@
+# Generated by Django 5.2.5 on 2025-09-09 16:48
+
+from django.db import migrations, models
+
+
+def get_active(apps, schema_editor):
+    from django.core.cache import cache
+    ConfigRevision = apps.get_model('core', 'ConfigRevision')
+    version = None
+    revision = None
+
+    # Try and get the latest version from cache
+    try:
+        version = cache.get('config_version')
+    except Exception:
+        pass
+
+    # If there is a version in cache, attempt to set revision to the current version from cache
+    # If the version in cache does not exist or there is no version, try the lastest revision in the database
+    if not version or (version and not (revision := ConfigRevision.objects.filter(pk=version).first())):
+        revision = ConfigRevision.objects.order_by('-created').first()
+
+    # If there is a revision set, set the active revision
+    if revision:
+        revision.active = True
+        revision.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0018_concrete_objecttype'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='configrevision',
+            name='active',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.RunPython(code=get_active, reverse_code=migrations.RunPython.noop),
+        migrations.AddConstraint(
+            model_name='configrevision',
+            constraint=models.UniqueConstraint(
+                condition=models.Q(('active', True)), fields=('active',), name='unique_active_config_revision'
+            ),
+        ),
+    ]

+ 16 - 1
netbox/core/models/config.py

@@ -14,6 +14,9 @@ class ConfigRevision(models.Model):
     """
     An atomic revision of NetBox's configuration.
     """
+    active = models.BooleanField(
+        default=False
+    )
     created = models.DateTimeField(
         verbose_name=_('created'),
         auto_now_add=True
@@ -35,6 +38,13 @@ class ConfigRevision(models.Model):
         ordering = ['-created']
         verbose_name = _('config revision')
         verbose_name_plural = _('config revisions')
+        constraints = [
+            models.UniqueConstraint(
+                fields=('active',),
+                condition=models.Q(active=True),
+                name='unique_active_config_revision',
+            )
+        ]
 
     def __str__(self):
         if not self.pk:
@@ -59,8 +69,13 @@ class ConfigRevision(models.Model):
         """
         cache.set('config', self.data, None)
         cache.set('config_version', self.pk, None)
+
+        # Set all instances of ConfigRevision to false and set this instance to true
+        ConfigRevision.objects.all().update(active=False)
+        ConfigRevision.objects.filter(pk=self.pk).update(active=True)
+
     activate.alters_data = True
 
     @property
     def is_active(self):
-        return cache.get('config_version') == self.pk
+        return self.active

+ 7 - 2
netbox/netbox/config/__init__.py

@@ -78,11 +78,16 @@ class Config:
         from core.models import ConfigRevision
 
         try:
-            revision = ConfigRevision.objects.last()
+            # Enforce the creation date as the ordering parameter
+            revision = ConfigRevision.objects.get(active=True)
+            logger.debug(f"Loaded active configuration revision #{revision.pk}")
+        except (ConfigRevision.DoesNotExist, ConfigRevision.MultipleObjectsReturned):
+            logger.warning("No active configuration revision found - falling back to most recent")
+            revision = ConfigRevision.objects.order_by('-created').first()
             if revision is None:
                 logger.debug("No previous configuration found in database; proceeding with default values")
                 return
-            logger.debug("Loaded configuration data from database")
+            logger.debug(f"Using fallback configuration revision #{revision.pk}")
         except DatabaseError:
             # The database may not be available yet (e.g. when running a management command)
             logger.warning("Skipping config initialization (database unavailable)")