mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Add tests for plugin configuration, min/max version
This commit is contained in:
@ -1,7 +1,10 @@
|
|||||||
import collections
|
import collections
|
||||||
import inspect
|
import inspect
|
||||||
|
from pkg_resources import parse_version
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
|
||||||
@ -70,6 +73,37 @@ class PluginConfig(AppConfig):
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls, user_config):
|
||||||
|
|
||||||
|
# Enforce version constraints
|
||||||
|
current_version = parse_version(settings.VERSION)
|
||||||
|
if cls.min_version is not None:
|
||||||
|
min_version = parse_version(cls.min_version)
|
||||||
|
if current_version < min_version:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
f"Plugin {cls.__module__} requires NetBox minimum version {cls.min_version}."
|
||||||
|
)
|
||||||
|
if cls.max_version is not None:
|
||||||
|
max_version = parse_version(cls.max_version)
|
||||||
|
if current_version > max_version:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
f"Plugin {cls.__module__} requires NetBox maximum version {cls.max_version}."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify required configuration settings
|
||||||
|
for setting in cls.required_settings:
|
||||||
|
if setting not in user_config:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
f"Plugin {cls.__module__} requires '{setting}' to be present in the PLUGINS_CONFIG section of "
|
||||||
|
f"configuration.py."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply default configuration values
|
||||||
|
for setting, value in cls.default_settings.items():
|
||||||
|
if setting not in user_config:
|
||||||
|
user_config[setting] = value
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Template content injection
|
# Template content injection
|
||||||
|
@ -7,6 +7,8 @@ class DummyPluginConfig(PluginConfig):
|
|||||||
version = '0.0'
|
version = '0.0'
|
||||||
description = 'For testing purposes only'
|
description = 'For testing purposes only'
|
||||||
base_url = 'dummy-plugin'
|
base_url = 'dummy-plugin'
|
||||||
|
min_version = '1.0'
|
||||||
|
max_version = '9.0'
|
||||||
middleware = [
|
middleware = [
|
||||||
'extras.tests.dummy_plugin.middleware.DummyMiddleware'
|
'extras.tests.dummy_plugin.middleware.DummyMiddleware'
|
||||||
]
|
]
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
from unittest import skipIf
|
from unittest import skipIf
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.test import Client, TestCase, override_settings
|
from django.test import Client, TestCase, override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from extras.registry import registry
|
from extras.registry import registry
|
||||||
|
from extras.tests.dummy_plugin import config as dummy_config
|
||||||
|
|
||||||
|
|
||||||
@skipIf('extras.tests.dummy_plugin.DummyPluginConfig' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS")
|
@skipIf('extras.tests.dummy_plugin' not in settings.PLUGINS, "dummy_plugin not in settings.PLUGINS")
|
||||||
class PluginTest(TestCase):
|
class PluginTest(TestCase):
|
||||||
|
|
||||||
def test_config(self):
|
def test_config(self):
|
||||||
@ -77,3 +79,52 @@ class PluginTest(TestCase):
|
|||||||
Check that plugin middleware is registered.
|
Check that plugin middleware is registered.
|
||||||
"""
|
"""
|
||||||
self.assertIn('extras.tests.dummy_plugin.middleware.DummyMiddleware', settings.MIDDLEWARE)
|
self.assertIn('extras.tests.dummy_plugin.middleware.DummyMiddleware', settings.MIDDLEWARE)
|
||||||
|
|
||||||
|
@override_settings(VERSION='0.9')
|
||||||
|
def test_min_version(self):
|
||||||
|
"""
|
||||||
|
Check enforcement of minimum NetBox version.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
dummy_config.validate({})
|
||||||
|
|
||||||
|
@override_settings(VERSION='10.0')
|
||||||
|
def test_max_version(self):
|
||||||
|
"""
|
||||||
|
Check enforcement of maximum NetBox version.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
dummy_config.validate({})
|
||||||
|
|
||||||
|
def test_required_settings(self):
|
||||||
|
"""
|
||||||
|
Validate enforcement of required settings.
|
||||||
|
"""
|
||||||
|
class DummyConfigWithRequiredSettings(dummy_config):
|
||||||
|
required_settings = ['foo']
|
||||||
|
|
||||||
|
# Validation should pass when all required settings are present
|
||||||
|
DummyConfigWithRequiredSettings.validate({'foo': True})
|
||||||
|
|
||||||
|
# Validation should fail when a required setting is missing
|
||||||
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
DummyConfigWithRequiredSettings.validate({})
|
||||||
|
|
||||||
|
def test_default_settings(self):
|
||||||
|
"""
|
||||||
|
Validate population of default config settings.
|
||||||
|
"""
|
||||||
|
class DummyConfigWithDefaultSettings(dummy_config):
|
||||||
|
default_settings = {
|
||||||
|
'bar': 123,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Populate the default value if setting has not been specified
|
||||||
|
user_config = {}
|
||||||
|
DummyConfigWithDefaultSettings.validate(user_config)
|
||||||
|
self.assertEqual(user_config['bar'], 123)
|
||||||
|
|
||||||
|
# Don't overwrite specified values
|
||||||
|
user_config = {'bar': 456}
|
||||||
|
DummyConfigWithDefaultSettings.validate(user_config)
|
||||||
|
self.assertEqual(user_config['bar'], 456)
|
||||||
|
@ -18,6 +18,10 @@ PLUGINS = [
|
|||||||
'extras.tests.dummy_plugin',
|
'extras.tests.dummy_plugin',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
PLUGINS_CONFIG = {
|
||||||
|
'foo': True,
|
||||||
|
}
|
||||||
|
|
||||||
REDIS = {
|
REDIS = {
|
||||||
'tasks': {
|
'tasks': {
|
||||||
'HOST': 'localhost',
|
'HOST': 'localhost',
|
||||||
|
@ -10,7 +10,6 @@ from urllib.parse import urlsplit
|
|||||||
from django.contrib.messages import constants as messages
|
from django.contrib.messages import constants as messages
|
||||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||||
from django.core.validators import URLValidator
|
from django.core.validators import URLValidator
|
||||||
from pkg_resources import parse_version
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -658,36 +657,16 @@ for plugin_name in PLUGINS:
|
|||||||
f"__init__.py file and point to the PluginConfig subclass."
|
f"__init__.py file and point to the PluginConfig subclass."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check version constraints
|
# Validate user-provided configuration settings and assign defaults
|
||||||
parsed_min_version = parse_version(plugin_config.min_version or VERSION)
|
if plugin_name not in PLUGINS_CONFIG:
|
||||||
parsed_max_version = parse_version(plugin_config.max_version or VERSION)
|
PLUGINS_CONFIG[plugin_name] = {}
|
||||||
if plugin_config.min_version and plugin_config.max_version and parsed_min_version > parsed_max_version:
|
plugin_config.validate(PLUGINS_CONFIG[plugin_name])
|
||||||
raise ImproperlyConfigured(f"Plugin {plugin_name} specifies invalid version constraints!")
|
|
||||||
if plugin_config.min_version and parsed_min_version > parse_version(VERSION):
|
|
||||||
raise ImproperlyConfigured(f"Plugin {plugin_name} requires NetBox minimum version {plugin_config.min_version}!")
|
|
||||||
if plugin_config.max_version and parsed_max_version < parse_version(VERSION):
|
|
||||||
raise ImproperlyConfigured(f"Plugin {plugin_name} requires NetBox maximum version {plugin_config.max_version}!")
|
|
||||||
|
|
||||||
# Add middleware
|
# Add middleware
|
||||||
plugin_middleware = plugin_config.middleware
|
plugin_middleware = plugin_config.middleware
|
||||||
if plugin_middleware and type(plugin_middleware) in (list, tuple):
|
if plugin_middleware and type(plugin_middleware) in (list, tuple):
|
||||||
MIDDLEWARE.extend(plugin_middleware)
|
MIDDLEWARE.extend(plugin_middleware)
|
||||||
|
|
||||||
# Verify required configuration settings
|
|
||||||
if plugin_name not in PLUGINS_CONFIG:
|
|
||||||
PLUGINS_CONFIG[plugin_name] = {}
|
|
||||||
for setting in plugin_config.required_settings:
|
|
||||||
if setting not in PLUGINS_CONFIG[plugin_name]:
|
|
||||||
raise ImproperlyConfigured(
|
|
||||||
f"Plugin {plugin_name} requires '{setting}' to be present in the PLUGINS_CONFIG section of "
|
|
||||||
f"configuration.py."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply default configuration values
|
|
||||||
for setting, value in plugin_config.default_settings.items():
|
|
||||||
if setting not in PLUGINS_CONFIG[plugin_name]:
|
|
||||||
PLUGINS_CONFIG[plugin_name][setting] = value
|
|
||||||
|
|
||||||
# Apply cacheops config
|
# Apply cacheops config
|
||||||
if type(plugin_config.caching_config) is not dict:
|
if type(plugin_config.caching_config) is not dict:
|
||||||
raise ImproperlyConfigured(f"Plugin {plugin_name} caching_config must be a dictionary.")
|
raise ImproperlyConfigured(f"Plugin {plugin_name} caching_config must be a dictionary.")
|
||||||
|
Reference in New Issue
Block a user