diff --git a/docs/configuration.md b/docs/configuration.md index 6e141aa..f70b842 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,10 +1,18 @@ # Configuration Parameters +## `max_working_branches` + +Default: None + +The maximum number of operational branches that can exist simultaneously. This count excludes branches which have been merged or archived. + +--- + ## `max_branches` Default: None -The maximum number of branches that can exist simultaneously, including merged branches that have not been deleted. It may be desirable to limit the total number of provisioned branches to safeguard against excessive database size. +The maximum total number of branches that can exist simultaneously, including merged branches that have not been deleted. It may be desirable to limit the total number of provisioned branches to safeguard against excessive database size. --- diff --git a/netbox_branching/__init__.py b/netbox_branching/__init__.py index 5174856..a17aa6c 100644 --- a/netbox_branching/__init__.py +++ b/netbox_branching/__init__.py @@ -16,6 +16,9 @@ class AppConfig(PluginConfig): 'netbox_branching.middleware.BranchMiddleware' ] default_settings = { + # The maximum number of working branches (excludes merged & archived branches) + 'max_working_branches': None, + # The maximum number of branches which can be provisioned simultaneously 'max_branches': None, diff --git a/netbox_branching/choices.py b/netbox_branching/choices.py index 6e49d2f..90c6f46 100644 --- a/netbox_branching/choices.py +++ b/netbox_branching/choices.py @@ -30,7 +30,13 @@ class BranchStatusChoices(ChoiceSet): PROVISIONING, SYNCING, MERGING, - REVERTING + REVERTING, + ) + + WORKING = ( + NEW, + READY, + *TRANSITIONAL, ) diff --git a/netbox_branching/models/branches.py b/netbox_branching/models/branches.py index 8887f77..23591d0 100644 --- a/netbox_branching/models/branches.py +++ b/netbox_branching/models/branches.py @@ -127,10 +127,10 @@ def synced_time(self): def clean(self): - # Check whether we're exceeding the maximum number of Branches + # Enforce the maximum number of total branches if not self.pk and (max_branches := get_plugin_config('netbox_branching', 'max_branches')): - branch_count = Branch.objects.count() - if branch_count >= max_branches: + total_branch_count = Branch.objects.count() + if total_branch_count >= max_branches: raise ValidationError( _( "The configured maximum number of branches ({max}) cannot be exceeded. One or more existing " @@ -138,6 +138,17 @@ def clean(self): ).format(max=max_branches) ) + # Enforce the maximum number of active branches + if not self.pk and (max_working_branches := get_plugin_config('netbox_branching', 'max_working_branches')): + working_branch_count = Branch.objects.filter(status__in=BranchStatusChoices.WORKING).count() + if working_branch_count >= max_working_branches: + raise ValidationError( + _( + "The configured maximum number of working branches ({max}) cannot be exceeded. One or more " + "working branches must be merged or archived before a new branch may be created." + ).format(max=max_working_branches) + ) + def save(self, provision=True, *args, **kwargs): """ Args: diff --git a/netbox_branching/template_content.py b/netbox_branching/template_content.py index 3ef7507..13bea68 100644 --- a/netbox_branching/template_content.py +++ b/netbox_branching/template_content.py @@ -17,9 +17,7 @@ class BranchSelector(PluginTemplateExtension): def navbar(self): return self.render('netbox_branching/inc/branch_selector.html', extra_context={ 'active_branch': active_branch.get(), - 'branches': Branch.objects.exclude( - status__in=[BranchStatusChoices.MERGED, BranchStatusChoices.ARCHIVED] - ), + 'branches': Branch.objects.filter(status__in=BranchStatusChoices.WORKING), }) diff --git a/netbox_branching/tests/test_branches.py b/netbox_branching/tests/test_branches.py index 9ba97b9..6cbd01d 100644 --- a/netbox_branching/tests/test_branches.py +++ b/netbox_branching/tests/test_branches.py @@ -4,6 +4,7 @@ from django.db import connection from django.test import TransactionTestCase, override_settings +from netbox_branching.choices import BranchStatusChoices from netbox_branching.constants import MAIN_SCHEMA from netbox_branching.models import Branch from netbox_branching.utilities import get_tables_to_replicate @@ -77,6 +78,30 @@ def test_branch_schema_id(self): branch.refresh_from_db() self.assertEqual(branch.schema_id, schema_id, msg="Schema ID was changed during save()") + @override_settings(PLUGINS_CONFIG={ + 'netbox_branching': { + 'max_working_branches': 2, + } + }) + def test_max_working_branches(self): + """ + Verify that the max_working_branches config parameter is enforced. + """ + Branch.objects.bulk_create(( + Branch(name='Branch 1', status=BranchStatusChoices.MERGED), + Branch(name='Branch 2', status=BranchStatusChoices.READY), + )) + + # Second active branch should be permitted (merged branches don't count) + branch = Branch(name='Branch 3') + branch.full_clean() + branch.save() + + # Attempting to create a third active branch should fail + branch = Branch(name='Branch 4') + with self.assertRaises(ValidationError): + branch.full_clean() + @override_settings(PLUGINS_CONFIG={ 'netbox_branching': { 'max_branches': 2,