diff --git a/netbox/core/exceptions.py b/netbox/core/exceptions.py index 8412b0378df..5790704c21c 100644 --- a/netbox/core/exceptions.py +++ b/netbox/core/exceptions.py @@ -1,2 +1,9 @@ +from django.core.exceptions import ImproperlyConfigured + + class SyncError(Exception): pass + + +class IncompatiblePluginError(ImproperlyConfigured): + pass diff --git a/netbox/netbox/plugins/__init__.py b/netbox/netbox/plugins/__init__.py index 69881a25146..c322018b3a5 100644 --- a/netbox/netbox/plugins/__init__.py +++ b/netbox/netbox/plugins/__init__.py @@ -6,6 +6,7 @@ from django.utils.module_loading import import_string from packaging import version +from core.exceptions import IncompatiblePluginError from netbox.registry import registry from netbox.search import register_search from netbox.utils import register_data_backend @@ -138,14 +139,14 @@ def validate(cls, user_config, netbox_version): if cls.min_version is not None: min_version = version.parse(cls.min_version) if current_version < min_version: - raise ImproperlyConfigured( + raise IncompatiblePluginError( f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version} (current: " f"{netbox_version})." ) if cls.max_version is not None: max_version = version.parse(cls.max_version) if current_version > max_version: - raise ImproperlyConfigured( + raise IncompatiblePluginError( f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version} (current: " f"{netbox_version})." ) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 84b86ba13ad..910fa056c54 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -11,6 +11,7 @@ from django.core.validators import URLValidator from django.utils.translation import gettext_lazy as _ +from core.exceptions import IncompatiblePluginError from netbox.config import PARAMS as CONFIG_PARAMS from netbox.constants import RQ_QUEUE_DEFAULT, RQ_QUEUE_HIGH, RQ_QUEUE_LOW from netbox.plugins import PluginConfig @@ -789,6 +790,7 @@ def _setting(name, default=None): EVENTS_PIPELINE.insert(0, 'extras.events.process_event_queue') # Register any configured plugins +incompatible_plugins = [] for plugin_name in PLUGINS: try: # Import the plugin module @@ -810,6 +812,16 @@ def _setting(name, default=None): f"__init__.py file and point to the PluginConfig subclass." ) + # Validate version compatibility and user-provided configuration settings and assign defaults + if plugin_name not in PLUGINS_CONFIG: + PLUGINS_CONFIG[plugin_name] = {} + try: + plugin_config.validate(PLUGINS_CONFIG[plugin_name], RELEASE.version) + except IncompatiblePluginError as e: + print(f'Unable to load plugin {plugin_name}: {e}') + incompatible_plugins.append(plugin_name) + continue + plugin_module = "{}.{}".format(plugin_config.__module__, plugin_config.__name__) # type: ignore # Gather additional apps to load alongside this plugin @@ -839,11 +851,6 @@ def _setting(name, default=None): sorted_apps = reversed(list(dict.fromkeys(reversed(INSTALLED_APPS)))) INSTALLED_APPS = list(sorted_apps) - # Validate user-provided configuration settings and assign defaults - if plugin_name not in PLUGINS_CONFIG: - PLUGINS_CONFIG[plugin_name] = {} - plugin_config.validate(PLUGINS_CONFIG[plugin_name], RELEASE.version) - # Add middleware plugin_middleware = plugin_config.middleware if plugin_middleware and type(plugin_middleware) in (list, tuple): @@ -865,6 +872,9 @@ def _setting(name, default=None): else: raise ImproperlyConfigured(f"events_pipline in plugin: {plugin_name} must be a list or tuple") +[PLUGINS.remove(x) for x in incompatible_plugins] + + # UNSUPPORTED FUNCTIONALITY: Import any local overrides. try: from .local_settings import *