This commit is contained in:
parent
88eac5b37d
commit
4d8dbc6ffe
|
|
@ -145,6 +145,24 @@ Set this configuration parameter to `True` for NetBox deployments which do not h
|
|||
|
||||
---
|
||||
|
||||
## JINJA_ENVIRONMENT_PARAMS
|
||||
|
||||
Default: `[]`
|
||||
|
||||
A list of system environment variable names which may be referenced from within Jinja2 templates via the built-in [`env`](#jinja2_filters) filter. Patterns may include wildcards (matched using Python's `fnmatch` syntax). Any variable whose name does not match an entry in this list cannot be referenced from a template. For example:
|
||||
|
||||
```python
|
||||
JINJA_ENVIRONMENT_PARAMS = [
|
||||
'WEBHOOK_TOKEN_*',
|
||||
'DEFAULT_SECRET_ID',
|
||||
]
|
||||
```
|
||||
|
||||
!!! info "Parameter names are case-sensitive"
|
||||
For example, `FOO_*` will match `FOO_BAR` but `foo_*` will not.
|
||||
|
||||
---
|
||||
|
||||
## JINJA2_FILTERS
|
||||
|
||||
Default: `{}`
|
||||
|
|
@ -160,6 +178,18 @@ JINJA2_FILTERS = {
|
|||
}
|
||||
```
|
||||
|
||||
NetBox also registers the following filters by default. Any entry defined in `JINJA2_FILTERS` with the same name will override the default.
|
||||
|
||||
| Filter | Description |
|
||||
|---|---|
|
||||
| `env` | Returns the value of the system environment variable with the given name, provided its name matches an entry in [`JINJA_ENVIRONMENT_PARAMS`](#jinja_environment_params). Returns `None` if the variable is not defined or its name is not whitelisted. |
|
||||
|
||||
For example, given `JINJA_ENVIRONMENT_PARAMS = ['WEBHOOK_TOKEN_*']`, a Jinja2 template may reference an environment variable as:
|
||||
|
||||
```
|
||||
Authorization: Bearer {{ 'WEBHOOK_TOKEN_3' | env }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## LOGGING
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ from extras.models import (
|
|||
from extras.models.mixins import RenderTemplateMixin
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.exceptions import AbortRequest
|
||||
from utilities.jinja2 import render_jinja2
|
||||
from utilities.jinja2 import env_filter, render_jinja2
|
||||
from utilities.tables import get_table_for_model
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||
|
||||
|
|
@ -973,6 +973,48 @@ class ConfigTemplateDebugTestCase(TestCase):
|
|||
render_jinja2("{% debug %}", {}, debug=False)
|
||||
|
||||
|
||||
class JinjaEnvFilterTestCase(TestCase):
|
||||
"""
|
||||
Tests for the env() Jinja2 filter and the JINJA_ENVIRONMENT_PARAMS configuration parameter.
|
||||
"""
|
||||
|
||||
def test_env_filter_returns_value_for_matching_name(self):
|
||||
with patch.dict('os.environ', {'NETBOX_TEST_TOKEN': 'secret'}, clear=False), \
|
||||
self.settings(JINJA_ENVIRONMENT_PARAMS=['NETBOX_TEST_TOKEN']):
|
||||
self.assertEqual(env_filter('NETBOX_TEST_TOKEN'), 'secret')
|
||||
|
||||
def test_env_filter_returns_none_for_unmatched_name(self):
|
||||
with patch.dict('os.environ', {'NETBOX_OTHER_TOKEN': 'secret'}, clear=False), \
|
||||
self.settings(JINJA_ENVIRONMENT_PARAMS=['NETBOX_TEST_TOKEN']):
|
||||
self.assertIsNone(env_filter('NETBOX_OTHER_TOKEN'))
|
||||
|
||||
def test_env_filter_wildcard_match(self):
|
||||
with patch.dict('os.environ', {'NETBOX_TEST_TOKEN_1': 'one', 'NETBOX_TEST_TOKEN_2': 'two'}, clear=False), \
|
||||
self.settings(JINJA_ENVIRONMENT_PARAMS=['NETBOX_TEST_TOKEN_*']):
|
||||
self.assertEqual(env_filter('NETBOX_TEST_TOKEN_1'), 'one')
|
||||
self.assertEqual(env_filter('NETBOX_TEST_TOKEN_2'), 'two')
|
||||
|
||||
def test_env_filter_returns_none_for_missing_env_var(self):
|
||||
with self.settings(JINJA_ENVIRONMENT_PARAMS=['NETBOX_MISSING_VAR']):
|
||||
self.assertIsNone(env_filter('NETBOX_MISSING_VAR'))
|
||||
|
||||
def test_env_filter_empty_whitelist_returns_none(self):
|
||||
with patch.dict('os.environ', {'NETBOX_TEST_TOKEN': 'secret'}, clear=False), \
|
||||
self.settings(JINJA_ENVIRONMENT_PARAMS=[]):
|
||||
self.assertIsNone(env_filter('NETBOX_TEST_TOKEN'))
|
||||
|
||||
def test_env_filter_registered_by_default(self):
|
||||
with patch.dict('os.environ', {'NETBOX_TEST_TOKEN': 'secret'}, clear=False), \
|
||||
self.settings(JINJA_ENVIRONMENT_PARAMS=['NETBOX_TEST_TOKEN']):
|
||||
output = render_jinja2("{{ 'NETBOX_TEST_TOKEN' | env }}", {})
|
||||
self.assertEqual(output, 'secret')
|
||||
|
||||
def test_user_defined_filter_overrides_default(self):
|
||||
with self.settings(JINJA2_FILTERS={'env': lambda name: 'overridden'}):
|
||||
output = render_jinja2("{{ 'NETBOX_TEST_TOKEN' | env }}", {})
|
||||
self.assertEqual(output, 'overridden')
|
||||
|
||||
|
||||
class ExportTemplateContextTestCase(TestCase):
|
||||
"""
|
||||
Tests for ExportTemplate.get_context() including public model population.
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ HTTP_CLIENT_IP_HEADERS = getattr(configuration, 'HTTP_CLIENT_IP_HEADERS', (
|
|||
HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', {})
|
||||
INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
|
||||
ISOLATED_DEPLOYMENT = getattr(configuration, 'ISOLATED_DEPLOYMENT', False)
|
||||
JINJA_ENVIRONMENT_PARAMS = getattr(configuration, 'JINJA_ENVIRONMENT_PARAMS', [])
|
||||
JINJA2_FILTERS = getattr(configuration, 'JINJA2_FILTERS', {})
|
||||
LANGUAGE_CODE = getattr(configuration, 'DEFAULT_LANGUAGE', 'en-us')
|
||||
LANGUAGE_COOKIE_PATH = CSRF_COOKIE_PATH
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import fnmatch
|
||||
import os
|
||||
|
||||
from django.apps import apps
|
||||
from jinja2 import BaseLoader, TemplateNotFound
|
||||
from jinja2.meta import find_referenced_templates
|
||||
|
|
@ -6,11 +9,30 @@ from jinja2.sandbox import SandboxedEnvironment
|
|||
from netbox.config import get_config
|
||||
|
||||
__all__ = (
|
||||
'DEFAULT_JINJA2_FILTERS',
|
||||
'DataFileLoader',
|
||||
'env_filter',
|
||||
'render_jinja2',
|
||||
)
|
||||
|
||||
|
||||
def env_filter(name):
|
||||
"""
|
||||
Jinja2 filter which returns the value of an environment variable, provided its name matches one of the patterns
|
||||
listed in the JINJA_ENVIRONMENT_PARAMS configuration parameter. Patterns may include wildcards. Returns None if the
|
||||
variable is not defined or its name does not match an allowed pattern.
|
||||
"""
|
||||
patterns = get_config().JINJA_ENVIRONMENT_PARAMS or []
|
||||
if not any(fnmatch.fnmatchcase(name, pattern) for pattern in patterns):
|
||||
return None
|
||||
return os.environ.get(name)
|
||||
|
||||
|
||||
DEFAULT_JINJA2_FILTERS = {
|
||||
'env': env_filter,
|
||||
}
|
||||
|
||||
|
||||
class DataFileLoader(BaseLoader):
|
||||
"""
|
||||
Custom Jinja2 loader to facilitate populating template content from DataFiles.
|
||||
|
|
@ -74,7 +96,11 @@ def render_jinja2(template_code, context, environment_params=None, data_file=Non
|
|||
environment_params['loader'] = loader
|
||||
|
||||
environment = SandboxedEnvironment(**environment_params)
|
||||
environment.filters.update(get_config().JINJA2_FILTERS)
|
||||
|
||||
# Register default filters, then apply any user-defined filters. User-defined entries take precedence so that
|
||||
# existing JINJA2_FILTERS configurations are never overridden.
|
||||
filters = {**DEFAULT_JINJA2_FILTERS, **get_config().JINJA2_FILTERS}
|
||||
environment.filters.update(filters)
|
||||
|
||||
if data_file:
|
||||
template = environment.get_template(data_file.path)
|
||||
|
|
|
|||
Loading…
Reference in New Issue