mirror of
https://github.com/lucaspalomodevelop/netbox-acls.git
synced 2026-03-12 23:27:23 +00:00
netbox 3.6 support (#167)
netbox 3.6 support thanks to @abhi1693 & @kbelokon
This commit is contained in:
parent
bc17018346
commit
f0b461616b
@ -1,15 +1,15 @@
|
|||||||
ARG NETBOX_VARIANT=v3.5
|
ARG NETBOX_VARIANT=v3.6
|
||||||
|
|
||||||
FROM netboxcommunity/netbox:${NETBOX_VARIANT}
|
FROM netboxcommunity/netbox:${NETBOX_VARIANT}
|
||||||
|
|
||||||
ARG NETBOX_INITIALIZERS_VARIANT=3.5.*
|
ARG NETBOX_INITIALIZERS_VARIANT=3.6.*
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
# Install APT packages
|
# Install APT packages
|
||||||
# hadolint ignore=DL3008
|
# hadolint ignore=DL3008
|
||||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
&& apt-get -y install --no-install-recommends curl git make openssh-client python3.10-dev sudo wget zsh \
|
&& apt-get -y install --no-install-recommends curl git make openssh-client python3.11-dev sudo wget zsh \
|
||||||
&& apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*
|
&& apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install development & ide dependencies
|
# Install development & ide dependencies
|
||||||
@ -17,12 +17,11 @@ COPY requirements-dev.txt /tmp/pip-tmp/
|
|||||||
RUN /opt/netbox/venv/bin/python3 -m pip install --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements-dev.txt \
|
RUN /opt/netbox/venv/bin/python3 -m pip install --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements-dev.txt \
|
||||||
&& rm -rf /tmp/*
|
&& rm -rf /tmp/*
|
||||||
|
|
||||||
ARG USERNAME=vscode
|
ARG USERNAME=ubuntu
|
||||||
ARG USER_UID=1000
|
ARG USER_UID=1000
|
||||||
ARG USER_GID=$USER_UID
|
ARG USER_GID=$USER_UID
|
||||||
|
|
||||||
RUN useradd -l -md /home/vscode -s /usr/bin/zsh -u $USER_UID $USERNAME \
|
RUN usermod -aG sudo $USERNAME \
|
||||||
&& usermod -aG sudo $USERNAME \
|
|
||||||
&& echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \
|
&& echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \
|
||||||
&& mkdir /opt/netbox/netbox/netbox-acls \
|
&& mkdir /opt/netbox/netbox/netbox-acls \
|
||||||
&& chown $USERNAME:$USERNAME /opt/netbox /etc/netbox /opt/unit -R
|
&& chown $USERNAME:$USERNAME /opt/netbox /etc/netbox /opt/unit -R
|
||||||
|
|||||||
@ -1,19 +1,46 @@
|
|||||||
# Based on https://github.com/netbox-community/netbox-docker/blob/release/configuration/configuration.py
|
####
|
||||||
|
## We recommend to not edit this file.
|
||||||
|
## Create separate files to overwrite the settings.
|
||||||
|
## See `extra.py` as an example.
|
||||||
|
####
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from os import environ
|
from os import environ
|
||||||
from os.path import abspath, dirname, join
|
from os.path import abspath, dirname, join
|
||||||
|
from typing import Any, Callable, Tuple
|
||||||
|
|
||||||
|
# For reference see https://docs.netbox.dev/en/stable/configuration/
|
||||||
|
# Based on https://github.com/netbox-community/netbox/blob/develop/netbox/netbox/configuration_example.py
|
||||||
|
|
||||||
|
###
|
||||||
|
# NetBox-Docker Helper functions
|
||||||
|
###
|
||||||
|
|
||||||
# Read secret from file
|
# Read secret from file
|
||||||
def _read_secret(secret_name, default=None):
|
def _read_secret(secret_name: str, default: str | None = None) -> str | None:
|
||||||
try:
|
try:
|
||||||
f = open(f"/run/secrets/{secret_name}", encoding="utf-8")
|
f = open(f'/run/secrets/{secret_name}', 'r', encoding='utf-8')
|
||||||
except OSError:
|
except EnvironmentError:
|
||||||
return default
|
return default
|
||||||
else:
|
else:
|
||||||
with f:
|
with f:
|
||||||
return f.readline().strip()
|
return f.readline().strip()
|
||||||
|
|
||||||
|
# If the `map_fn` isn't defined, then the value that is read from the environment (or the default value if not found) is returned.
|
||||||
|
# If the `map_fn` is defined, then `map_fn` is invoked and the value (that was read from the environment or the default value if not found)
|
||||||
|
# is passed to it as a parameter. The value returned from `map_fn` is then the return value of this function.
|
||||||
|
# The `map_fn` is not invoked, if the value (that was read from the environment or the default value if not found) is None.
|
||||||
|
def _environ_get_and_map(variable_name: str, default: str | None = None, map_fn: Callable[[str], Any | None] = None) -> Any | None:
|
||||||
|
env_value = environ.get(variable_name, default)
|
||||||
|
|
||||||
|
if env_value is None:
|
||||||
|
return env_value
|
||||||
|
|
||||||
|
return env_value if not map_fn else map_fn(env_value)
|
||||||
|
|
||||||
|
_AS_BOOL = lambda value : value.lower() == 'true'
|
||||||
|
_AS_INT = lambda value : int(value)
|
||||||
|
_AS_LIST = lambda value : list(filter(None, value.split(' ')))
|
||||||
|
|
||||||
_BASE_DIR = dirname(dirname(abspath(__file__)))
|
_BASE_DIR = dirname(dirname(abspath(__file__)))
|
||||||
|
|
||||||
@ -27,59 +54,49 @@ _BASE_DIR = dirname(dirname(abspath(__file__)))
|
|||||||
# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
|
# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
|
||||||
#
|
#
|
||||||
# Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local']
|
# Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local']
|
||||||
ALLOWED_HOSTS = environ.get("ALLOWED_HOSTS", "*").split(" ")
|
ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ')
|
||||||
|
# ensure that '*' or 'localhost' is always in ALLOWED_HOSTS (needed for health checks)
|
||||||
|
if '*' not in ALLOWED_HOSTS and 'localhost' not in ALLOWED_HOSTS:
|
||||||
|
ALLOWED_HOSTS.append('localhost')
|
||||||
|
|
||||||
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
|
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
|
||||||
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
|
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
|
||||||
DATABASE = {
|
DATABASE = {
|
||||||
"NAME": environ.get("DB_NAME", "netbox"), # Database name
|
'NAME': environ.get('DB_NAME', 'netbox'), # Database name
|
||||||
"USER": environ.get("DB_USER", ""), # PostgreSQL username
|
'USER': environ.get('DB_USER', ''), # PostgreSQL username
|
||||||
"PASSWORD": _read_secret("db_password", environ.get("DB_PASSWORD", "")),
|
'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')),
|
||||||
# PostgreSQL password
|
# PostgreSQL password
|
||||||
"HOST": environ.get("DB_HOST", "localhost"), # Database server
|
'HOST': environ.get('DB_HOST', 'localhost'), # Database server
|
||||||
"PORT": environ.get("DB_PORT", ""), # Database port (leave blank for default)
|
'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default)
|
||||||
"OPTIONS": {"sslmode": environ.get("DB_SSLMODE", "prefer")},
|
'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')},
|
||||||
# Database connection SSLMODE
|
# Database connection SSLMODE
|
||||||
"CONN_MAX_AGE": int(environ.get("DB_CONN_MAX_AGE", "300")),
|
'CONN_MAX_AGE': _environ_get_and_map('DB_CONN_MAX_AGE', '300', _AS_INT),
|
||||||
# Max database connection age
|
# Max database connection age
|
||||||
"DISABLE_SERVER_SIDE_CURSORS": environ.get(
|
'DISABLE_SERVER_SIDE_CURSORS': _environ_get_and_map('DB_DISABLE_SERVER_SIDE_CURSORS', 'False', _AS_BOOL),
|
||||||
"DB_DISABLE_SERVER_SIDE_CURSORS",
|
# Disable the use of server-side cursors transaction pooling
|
||||||
"False",
|
|
||||||
).lower()
|
|
||||||
== "true",
|
|
||||||
# Disable the use of server-side cursors transaction pooling
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
|
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
|
||||||
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
|
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
|
||||||
# to use two separate database IDs.
|
# to use two separate database IDs.
|
||||||
REDIS = {
|
REDIS = {
|
||||||
"tasks": {
|
'tasks': {
|
||||||
"HOST": environ.get("REDIS_HOST", "localhost"),
|
'HOST': environ.get('REDIS_HOST', 'localhost'),
|
||||||
"PORT": int(environ.get("REDIS_PORT", 6379)),
|
'PORT': _environ_get_and_map('REDIS_PORT', 6379, _AS_INT),
|
||||||
"PASSWORD": _read_secret("redis_password", environ.get("REDIS_PASSWORD", "")),
|
'USERNAME': environ.get('REDIS_USERNAME', ''),
|
||||||
"DATABASE": int(environ.get("REDIS_DATABASE", 0)),
|
'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')),
|
||||||
"SSL": environ.get("REDIS_SSL", "False").lower() == "true",
|
'DATABASE': _environ_get_and_map('REDIS_DATABASE', 0, _AS_INT),
|
||||||
"INSECURE_SKIP_TLS_VERIFY": environ.get(
|
'SSL': _environ_get_and_map('REDIS_SSL', 'False', _AS_BOOL),
|
||||||
"REDIS_INSECURE_SKIP_TLS_VERIFY",
|
'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False', _AS_BOOL),
|
||||||
"False",
|
|
||||||
).lower()
|
|
||||||
== "true",
|
|
||||||
},
|
},
|
||||||
"caching": {
|
'caching': {
|
||||||
"HOST": environ.get("REDIS_CACHE_HOST", environ.get("REDIS_HOST", "localhost")),
|
'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')),
|
||||||
"PORT": int(environ.get("REDIS_CACHE_PORT", environ.get("REDIS_PORT", 6379))),
|
'PORT': _environ_get_and_map('REDIS_CACHE_PORT', environ.get('REDIS_PORT', '6379'), _AS_INT),
|
||||||
"PASSWORD": _read_secret(
|
'USERNAME': environ.get('REDIS_CACHE_USERNAME', environ.get('REDIS_USERNAME', '')),
|
||||||
"redis_cache_password",
|
'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))),
|
||||||
environ.get("REDIS_CACHE_PASSWORD", environ.get("REDIS_PASSWORD", "")),
|
'DATABASE': _environ_get_and_map('REDIS_CACHE_DATABASE', '1', _AS_INT),
|
||||||
),
|
'SSL': _environ_get_and_map('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False'), _AS_BOOL),
|
||||||
"DATABASE": int(environ.get("REDIS_CACHE_DATABASE", 1)),
|
'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False'), _AS_BOOL),
|
||||||
"SSL": environ.get("REDIS_CACHE_SSL", environ.get("REDIS_SSL", "False")).lower() == "true",
|
|
||||||
"INSECURE_SKIP_TLS_VERIFY": environ.get(
|
|
||||||
"REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY",
|
|
||||||
environ.get("REDIS_INSECURE_SKIP_TLS_VERIFY", "False"),
|
|
||||||
).lower()
|
|
||||||
== "true",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +104,7 @@ REDIS = {
|
|||||||
# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and
|
# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and
|
||||||
# symbols. NetBox will not run without this defined. For more information, see
|
# symbols. NetBox will not run without this defined. For more information, see
|
||||||
# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY
|
# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY
|
||||||
SECRET_KEY = _read_secret("secret_key", environ.get("SECRET_KEY", ""))
|
SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', ''))
|
||||||
|
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
@ -96,195 +113,203 @@ SECRET_KEY = _read_secret("secret_key", environ.get("SECRET_KEY", ""))
|
|||||||
# #
|
# #
|
||||||
#########################
|
#########################
|
||||||
|
|
||||||
# Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of
|
# # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of
|
||||||
# application errors (assuming correct email settings are provided).
|
# # application errors (assuming correct email settings are provided).
|
||||||
ADMINS = [
|
# ADMINS = [
|
||||||
# ['John Doe', 'jdoe@example.com'],
|
# # ['John Doe', 'jdoe@example.com'],
|
||||||
]
|
# ]
|
||||||
|
|
||||||
# URL schemes that are allowed within links in NetBox
|
if 'ALLOWED_URL_SCHEMES' in environ:
|
||||||
ALLOWED_URL_SCHEMES = (
|
ALLOWED_URL_SCHEMES = _environ_get_and_map('ALLOWED_URL_SCHEMES', None, _AS_LIST)
|
||||||
"file",
|
|
||||||
"ftp",
|
|
||||||
"ftps",
|
|
||||||
"http",
|
|
||||||
"https",
|
|
||||||
"irc",
|
|
||||||
"mailto",
|
|
||||||
"sftp",
|
|
||||||
"ssh",
|
|
||||||
"tel",
|
|
||||||
"telnet",
|
|
||||||
"tftp",
|
|
||||||
"vnc",
|
|
||||||
"xmpp",
|
|
||||||
)
|
|
||||||
|
|
||||||
# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
|
# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
|
||||||
# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
|
# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
|
||||||
BANNER_TOP = environ.get("BANNER_TOP", "")
|
if 'BANNER_TOP' in environ:
|
||||||
BANNER_BOTTOM = environ.get("BANNER_BOTTOM", "")
|
BANNER_TOP = environ.get('BANNER_TOP', None)
|
||||||
|
if 'BANNER_BOTTOM' in environ:
|
||||||
|
BANNER_BOTTOM = environ.get('BANNER_BOTTOM', None)
|
||||||
|
|
||||||
# Text to include on the login page above the login form. HTML is allowed.
|
# Text to include on the login page above the login form. HTML is allowed.
|
||||||
BANNER_LOGIN = environ.get("BANNER_LOGIN", "")
|
if 'BANNER_LOGIN' in environ:
|
||||||
|
BANNER_LOGIN = environ.get('BANNER_LOGIN', None)
|
||||||
# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set:
|
|
||||||
# BASE_PATH = 'netbox/'
|
|
||||||
BASE_PATH = environ.get("BASE_PATH", "")
|
|
||||||
|
|
||||||
# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
|
# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
|
||||||
CHANGELOG_RETENTION = int(environ.get("CHANGELOG_RETENTION", 90))
|
if 'CHANGELOG_RETENTION' in environ:
|
||||||
|
CHANGELOG_RETENTION = _environ_get_and_map('CHANGELOG_RETENTION', None, _AS_INT)
|
||||||
|
|
||||||
|
# Maximum number of days to retain job results (scripts and reports). Set to 0 to retain job results in the database indefinitely. (Default: 90)
|
||||||
|
if 'JOB_RETENTION' in environ:
|
||||||
|
JOB_RETENTION = _environ_get_and_map('JOB_RETENTION', None, _AS_INT)
|
||||||
|
# JOBRESULT_RETENTION was renamed to JOB_RETENTION in the v3.5.0 release of NetBox. For backwards compatibility, map JOBRESULT_RETENTION to JOB_RETENTION
|
||||||
|
elif 'JOBRESULT_RETENTION' in environ:
|
||||||
|
JOB_RETENTION = _environ_get_and_map('JOBRESULT_RETENTION', None, _AS_INT)
|
||||||
|
|
||||||
# API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be
|
# API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be
|
||||||
# allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or
|
# allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or
|
||||||
# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
|
# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
|
||||||
CORS_ORIGIN_ALLOW_ALL = environ.get("CORS_ORIGIN_ALLOW_ALL", "False").lower() == "true"
|
CORS_ORIGIN_ALLOW_ALL = _environ_get_and_map('CORS_ORIGIN_ALLOW_ALL', 'False', _AS_BOOL)
|
||||||
CORS_ORIGIN_WHITELIST = list(
|
CORS_ORIGIN_WHITELIST = _environ_get_and_map('CORS_ORIGIN_WHITELIST', 'https://localhost', _AS_LIST)
|
||||||
filter(None, environ.get("CORS_ORIGIN_WHITELIST", "https://localhost").split(" ")),
|
CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in _environ_get_and_map('CORS_ORIGIN_REGEX_WHITELIST', '', _AS_LIST)]
|
||||||
)
|
|
||||||
CORS_ORIGIN_REGEX_WHITELIST = [
|
|
||||||
re.compile(r)
|
|
||||||
for r in list(
|
|
||||||
filter(None, environ.get("CORS_ORIGIN_REGEX_WHITELIST", "").split(" ")),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
|
# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
|
||||||
# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
|
# sensitive information about your installation. Only enable debugging while performing testing.
|
||||||
# on a production system.
|
# Never enable debugging on a production system.
|
||||||
DEBUG = environ.get("DEBUG", "False").lower() == "true"
|
DEBUG = _environ_get_and_map('DEBUG', 'False', _AS_BOOL)
|
||||||
|
|
||||||
# Set to True to enable DEVELOPER Mode. WARNING: ONLY netbox developers or plugin developers need this access.
|
# This parameter serves as a safeguard to prevent some potentially dangerous behavior,
|
||||||
DEVELOPER = environ.get("DEVELOPER_MODE", "False").lower() == "true"
|
# such as generating new database schema migrations.
|
||||||
|
# Set this to True only if you are actively developing the NetBox code base.
|
||||||
|
DEVELOPER = _environ_get_and_map('DEVELOPER', 'False', _AS_BOOL)
|
||||||
|
|
||||||
# Email settings
|
# Email settings
|
||||||
EMAIL = {
|
EMAIL = {
|
||||||
"SERVER": environ.get("EMAIL_SERVER", "localhost"),
|
'SERVER': environ.get('EMAIL_SERVER', 'localhost'),
|
||||||
"PORT": int(environ.get("EMAIL_PORT", 25)),
|
'PORT': _environ_get_and_map('EMAIL_PORT', 25, _AS_INT),
|
||||||
"USERNAME": environ.get("EMAIL_USERNAME", ""),
|
'USERNAME': environ.get('EMAIL_USERNAME', ''),
|
||||||
"PASSWORD": _read_secret("email_password", environ.get("EMAIL_PASSWORD", "")),
|
'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')),
|
||||||
"USE_SSL": environ.get("EMAIL_USE_SSL", "False").lower() == "true",
|
'USE_SSL': _environ_get_and_map('EMAIL_USE_SSL', 'False', _AS_BOOL),
|
||||||
"USE_TLS": environ.get("EMAIL_USE_TLS", "False").lower() == "true",
|
'USE_TLS': _environ_get_and_map('EMAIL_USE_TLS', 'False', _AS_BOOL),
|
||||||
"SSL_CERTFILE": environ.get("EMAIL_SSL_CERTFILE", ""),
|
'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''),
|
||||||
"SSL_KEYFILE": environ.get("EMAIL_SSL_KEYFILE", ""),
|
'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''),
|
||||||
"TIMEOUT": int(environ.get("EMAIL_TIMEOUT", 10)), # seconds
|
'TIMEOUT': _environ_get_and_map('EMAIL_TIMEOUT', 10, _AS_INT), # seconds
|
||||||
"FROM_EMAIL": environ.get("EMAIL_FROM", ""),
|
'FROM_EMAIL': environ.get('EMAIL_FROM', ''),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table
|
# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table
|
||||||
# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True.
|
# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True.
|
||||||
ENFORCE_GLOBAL_UNIQUE = environ.get("ENFORCE_GLOBAL_UNIQUE", "False").lower() == "true"
|
if 'ENFORCE_GLOBAL_UNIQUE' in environ:
|
||||||
|
ENFORCE_GLOBAL_UNIQUE = _environ_get_and_map('ENFORCE_GLOBAL_UNIQUE', None, _AS_BOOL)
|
||||||
|
|
||||||
# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
|
# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
|
||||||
# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
|
# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
|
||||||
EXEMPT_VIEW_PERMISSIONS = list(
|
EXEMPT_VIEW_PERMISSIONS = _environ_get_and_map('EXEMPT_VIEW_PERMISSIONS', '', _AS_LIST)
|
||||||
filter(None, environ.get("EXEMPT_VIEW_PERMISSIONS", "").split(" ")),
|
|
||||||
)
|
# HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks).
|
||||||
|
# HTTP_PROXIES = {
|
||||||
|
# 'http': 'http://10.10.1.10:3128',
|
||||||
|
# 'https': 'http://10.10.1.10:1080',
|
||||||
|
# }
|
||||||
|
|
||||||
|
# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing
|
||||||
|
# NetBox from an internal IP.
|
||||||
|
INTERNAL_IPS = _environ_get_and_map('INTERNAL_IPS', '127.0.0.1 ::1', _AS_LIST)
|
||||||
|
|
||||||
# Enable GraphQL API.
|
# Enable GraphQL API.
|
||||||
GRAPHQL_ENABLED = environ.get("GRAPHQL_ENABLED", "True").lower() == "true"
|
if 'GRAPHQL_ENABLED' in environ:
|
||||||
|
GRAPHQL_ENABLED = _environ_get_and_map('GRAPHQL_ENABLED', None, _AS_BOOL)
|
||||||
|
|
||||||
# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
|
# # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
|
||||||
# https://docs.djangoproject.com/en/stable/topics/logging/
|
# # https://docs.djangoproject.com/en/stable/topics/logging/
|
||||||
LOGGING = {}
|
# LOGGING = {}
|
||||||
|
|
||||||
|
# Automatically reset the lifetime of a valid session upon each authenticated request. Enables users to remain
|
||||||
|
# authenticated to NetBox indefinitely.
|
||||||
|
LOGIN_PERSISTENCE = _environ_get_and_map('LOGIN_PERSISTENCE', 'False', _AS_BOOL)
|
||||||
|
|
||||||
# Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users
|
# Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users
|
||||||
# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
|
# are permitted to access most data in NetBox (excluding secrets) but not make any changes.
|
||||||
LOGIN_REQUIRED = environ.get("LOGIN_REQUIRED", "False").lower() == "true"
|
LOGIN_REQUIRED = _environ_get_and_map('LOGIN_REQUIRED', 'False', _AS_BOOL)
|
||||||
|
|
||||||
# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to
|
# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to
|
||||||
# re-authenticate. (Default: 1209600 [14 days])
|
# re-authenticate. (Default: 1209600 [14 days])
|
||||||
LOGIN_TIMEOUT = int(environ.get("LOGIN_TIMEOUT", 1209600))
|
LOGIN_TIMEOUT = _environ_get_and_map('LOGIN_TIMEOUT', 1209600, _AS_INT)
|
||||||
|
|
||||||
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
||||||
MAINTENANCE_MODE = environ.get("MAINTENANCE_MODE", "False").lower() == "true"
|
if 'MAINTENANCE_MODE' in environ:
|
||||||
|
MAINTENANCE_MODE = _environ_get_and_map('MAINTENANCE_MODE', None, _AS_BOOL)
|
||||||
|
|
||||||
|
# Maps provider
|
||||||
|
if 'MAPS_URL' in environ:
|
||||||
|
MAPS_URL = environ.get('MAPS_URL', None)
|
||||||
|
|
||||||
# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
|
# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
|
||||||
# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
|
# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
|
||||||
# all objects by specifying "?limit=0".
|
# all objects by specifying "?limit=0".
|
||||||
MAX_PAGE_SIZE = int(environ.get("MAX_PAGE_SIZE", 1000))
|
if 'MAX_PAGE_SIZE' in environ:
|
||||||
|
MAX_PAGE_SIZE = _environ_get_and_map('MAX_PAGE_SIZE', None, _AS_INT)
|
||||||
|
|
||||||
# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
|
# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
|
||||||
# the default value of this setting is derived from the installed location.
|
# the default value of this setting is derived from the installed location.
|
||||||
MEDIA_ROOT = environ.get("MEDIA_ROOT", join(_BASE_DIR, "media"))
|
MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media'))
|
||||||
|
|
||||||
# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
|
# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
|
||||||
METRICS_ENABLED = environ.get("METRICS_ENABLED", "False").lower() == "true"
|
METRICS_ENABLED = _environ_get_and_map('METRICS_ENABLED', 'False', _AS_BOOL)
|
||||||
|
|
||||||
# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
|
|
||||||
NAPALM_USERNAME = environ.get("NAPALM_USERNAME", "")
|
|
||||||
NAPALM_PASSWORD = _read_secret("napalm_password", environ.get("NAPALM_PASSWORD", ""))
|
|
||||||
|
|
||||||
# NAPALM timeout (in seconds). (Default: 30)
|
|
||||||
NAPALM_TIMEOUT = int(environ.get("NAPALM_TIMEOUT", 30))
|
|
||||||
|
|
||||||
# NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
|
|
||||||
# be provided as a dictionary.
|
|
||||||
NAPALM_ARGS = {}
|
|
||||||
|
|
||||||
# Determine how many objects to display per page within a list. (Default: 50)
|
# Determine how many objects to display per page within a list. (Default: 50)
|
||||||
PAGINATE_COUNT = int(environ.get("PAGINATE_COUNT", 50))
|
if 'PAGINATE_COUNT' in environ:
|
||||||
|
PAGINATE_COUNT = _environ_get_and_map('PAGINATE_COUNT', None, _AS_INT)
|
||||||
|
|
||||||
# Enable installed plugins. Add the name of each plugin to the list.
|
# # Enable installed plugins. Add the name of each plugin to the list.
|
||||||
PLUGINS = []
|
# PLUGINS = []
|
||||||
|
|
||||||
# Plugins configuration settings. These settings are used by various plugins that the user may have installed.
|
# # Plugins configuration settings. These settings are used by various plugins that the user may have installed.
|
||||||
# Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings.
|
# # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings.
|
||||||
PLUGINS_CONFIG = {}
|
# PLUGINS_CONFIG = {
|
||||||
|
# }
|
||||||
|
|
||||||
# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
|
# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
|
||||||
# prefer IPv4 instead.
|
# prefer IPv4 instead.
|
||||||
PREFER_IPV4 = environ.get("PREFER_IPV4", "False").lower() == "true"
|
if 'PREFER_IPV4' in environ:
|
||||||
|
PREFER_IPV4 = _environ_get_and_map('PREFER_IPV4', None, _AS_BOOL)
|
||||||
|
|
||||||
|
# The default value for the amperage field when creating new power feeds.
|
||||||
|
if 'POWERFEED_DEFAULT_AMPERAGE' in environ:
|
||||||
|
POWERFEED_DEFAULT_AMPERAGE = _environ_get_and_map('POWERFEED_DEFAULT_AMPERAGE', None, _AS_INT)
|
||||||
|
|
||||||
|
# The default value (percentage) for the max_utilization field when creating new power feeds.
|
||||||
|
if 'POWERFEED_DEFAULT_MAX_UTILIZATION' in environ:
|
||||||
|
POWERFEED_DEFAULT_MAX_UTILIZATION = _environ_get_and_map('POWERFEED_DEFAULT_MAX_UTILIZATION', None, _AS_INT)
|
||||||
|
|
||||||
|
# The default value for the voltage field when creating new power feeds.
|
||||||
|
if 'POWERFEED_DEFAULT_VOLTAGE' in environ:
|
||||||
|
POWERFEED_DEFAULT_VOLTAGE = _environ_get_and_map('POWERFEED_DEFAULT_VOLTAGE', None, _AS_INT)
|
||||||
|
|
||||||
# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1.
|
# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1.
|
||||||
RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = int(
|
if 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT' in environ:
|
||||||
environ.get("RACK_ELEVATION_DEFAULT_UNIT_HEIGHT", 22),
|
RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', None, _AS_INT)
|
||||||
)
|
if 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH' in environ:
|
||||||
RACK_ELEVATION_DEFAULT_UNIT_WIDTH = int(
|
RACK_ELEVATION_DEFAULT_UNIT_WIDTH = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', None, _AS_INT)
|
||||||
environ.get("RACK_ELEVATION_DEFAULT_UNIT_WIDTH", 220),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remote authentication support
|
# Remote authentication support
|
||||||
REMOTE_AUTH_ENABLED = environ.get("REMOTE_AUTH_ENABLED", "False").lower() == "true"
|
REMOTE_AUTH_ENABLED = _environ_get_and_map('REMOTE_AUTH_ENABLED', 'False', _AS_BOOL)
|
||||||
REMOTE_AUTH_BACKEND = environ.get(
|
REMOTE_AUTH_BACKEND = _environ_get_and_map('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend', _AS_LIST)
|
||||||
"REMOTE_AUTH_BACKEND",
|
REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER')
|
||||||
"netbox.authentication.RemoteUserBackend",
|
REMOTE_AUTH_AUTO_CREATE_USER = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_USER', 'False', _AS_BOOL)
|
||||||
)
|
REMOTE_AUTH_DEFAULT_GROUPS = _environ_get_and_map('REMOTE_AUTH_DEFAULT_GROUPS', '', _AS_LIST)
|
||||||
REMOTE_AUTH_HEADER = environ.get("REMOTE_AUTH_HEADER", "HTTP_REMOTE_USER")
|
# REMOTE_AUTH_DEFAULT_PERMISSIONS = {}
|
||||||
REMOTE_AUTH_AUTO_CREATE_USER = environ.get("REMOTE_AUTH_AUTO_CREATE_USER", "True").lower() == "true"
|
|
||||||
REMOTE_AUTH_DEFAULT_GROUPS = list(
|
|
||||||
filter(None, environ.get("REMOTE_AUTH_DEFAULT_GROUPS", "").split(" ")),
|
|
||||||
)
|
|
||||||
|
|
||||||
# This repository is used to check whether there is a new release of NetBox available. Set to None to disable the
|
# This repository is used to check whether there is a new release of NetBox available. Set to None to disable the
|
||||||
# version check or use the URL below to check for release in the official NetBox repository.
|
# version check or use the URL below to check for release in the official NetBox repository.
|
||||||
# https://api.github.com/repos/netbox-community/netbox/releases
|
RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None)
|
||||||
RELEASE_CHECK_URL = environ.get("RELEASE_CHECK_URL", None)
|
# RELEASE_CHECK_URL = 'https://api.github.com/repos/netbox-community/netbox/releases'
|
||||||
|
|
||||||
# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
|
|
||||||
# this setting is derived from the installed location.
|
|
||||||
REPORTS_ROOT = environ.get("REPORTS_ROOT", "/etc/netbox/reports")
|
|
||||||
|
|
||||||
# Maximum execution time for background tasks, in seconds.
|
# Maximum execution time for background tasks, in seconds.
|
||||||
RQ_DEFAULT_TIMEOUT = int(environ.get("RQ_DEFAULT_TIMEOUT", 300))
|
RQ_DEFAULT_TIMEOUT = _environ_get_and_map('RQ_DEFAULT_TIMEOUT', 300, _AS_INT)
|
||||||
|
|
||||||
# The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of
|
# The name to use for the csrf token cookie.
|
||||||
# this setting is derived from the installed location.
|
CSRF_COOKIE_NAME = environ.get('CSRF_COOKIE_NAME', 'csrftoken')
|
||||||
SCRIPTS_ROOT = environ.get("SCRIPTS_ROOT", "/etc/netbox/scripts")
|
|
||||||
|
# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag.
|
||||||
|
# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like:
|
||||||
|
# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev
|
||||||
|
CSRF_TRUSTED_ORIGINS = _environ_get_and_map('CSRF_TRUSTED_ORIGINS', '', _AS_LIST)
|
||||||
|
|
||||||
|
# The name to use for the session cookie.
|
||||||
|
SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME', 'sessionid')
|
||||||
|
|
||||||
# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
|
# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
|
||||||
# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
|
# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
|
||||||
# database access.) Note that the user as which NetBox runs must have read and write permissions to this path.
|
# database access.) Note that the user as which NetBox runs must have read and write permissions to this path.
|
||||||
SESSION_FILE_PATH = environ.get("SESSIONS_ROOT", None)
|
SESSION_FILE_PATH = environ.get('SESSION_FILE_PATH', environ.get('SESSIONS_ROOT', None))
|
||||||
|
|
||||||
# Time zone (default: UTC)
|
# Time zone (default: UTC)
|
||||||
TIME_ZONE = environ.get("TIME_ZONE", "UTC")
|
TIME_ZONE = environ.get('TIME_ZONE', 'UTC')
|
||||||
|
|
||||||
# Date/time formatting. See the following link for supported formats:
|
# Date/time formatting. See the following link for supported formats:
|
||||||
# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
|
# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
|
||||||
DATE_FORMAT = environ.get("DATE_FORMAT", "N j, Y")
|
DATE_FORMAT = environ.get('DATE_FORMAT', 'N j, Y')
|
||||||
SHORT_DATE_FORMAT = environ.get("SHORT_DATE_FORMAT", "Y-m-d")
|
SHORT_DATE_FORMAT = environ.get('SHORT_DATE_FORMAT', 'Y-m-d')
|
||||||
TIME_FORMAT = environ.get("TIME_FORMAT", "g:i a")
|
TIME_FORMAT = environ.get('TIME_FORMAT', 'g:i a')
|
||||||
SHORT_TIME_FORMAT = environ.get("SHORT_TIME_FORMAT", "H:i:s")
|
SHORT_TIME_FORMAT = environ.get('SHORT_TIME_FORMAT', 'H:i:s')
|
||||||
DATETIME_FORMAT = environ.get("DATETIME_FORMAT", "N j, Y g:i a")
|
DATETIME_FORMAT = environ.get('DATETIME_FORMAT', 'N j, Y g:i a')
|
||||||
SHORT_DATETIME_FORMAT = environ.get("SHORT_DATETIME_FORMAT", "Y-m-d H:i")
|
SHORT_DATETIME_FORMAT = environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
||||||
@ -104,5 +104,5 @@
|
|||||||
//"postAttachCommand": "source /opt/netbox/venv/bin/activate",
|
//"postAttachCommand": "source /opt/netbox/venv/bin/activate",
|
||||||
|
|
||||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
"remoteUser": "vscode"
|
"remoteUser": "ubuntu"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,10 +16,10 @@ services:
|
|||||||
interval: 15s
|
interval: 15s
|
||||||
test: "curl -f http://localhost:8080/api/ || exit 1"
|
test: "curl -f http://localhost:8080/api/ || exit 1"
|
||||||
volumes:
|
volumes:
|
||||||
- ./configuration:/etc/netbox/config:z,ro
|
- ./configuration:/etc/netbox/config:ro
|
||||||
#- ./reports:/etc/netbox/reports:z,ro
|
#- netbox-media-files:/opt/netbox/netbox/media:rw
|
||||||
#- ./scripts:/etc/netbox/scripts:z,ro
|
#- netbox-reports-files:/opt/netbox/netbox/reports:rw
|
||||||
#- netbox-media-files:/opt/netbox/netbox/media:z
|
#- netbox-scripts-files:/opt/netbox/netbox/scripts:rw
|
||||||
#netbox-worker:
|
#netbox-worker:
|
||||||
# <<: *netbox
|
# <<: *netbox
|
||||||
# depends_on:
|
# depends_on:
|
||||||
|
|||||||
2
.devcontainer/env/netbox.env
vendored
2
.devcontainer/env/netbox.env
vendored
@ -15,7 +15,7 @@ REDIS_DATABASE=0
|
|||||||
REDIS_HOST=redis
|
REDIS_HOST=redis
|
||||||
REDIS_INSECURE_SKIP_TLS_VERIFY=false
|
REDIS_INSECURE_SKIP_TLS_VERIFY=false
|
||||||
REDIS_PASSWORD=H733Kdjndks81
|
REDIS_PASSWORD=H733Kdjndks81
|
||||||
SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNjaa
|
SECRET_KEY='r(m)9nLGnz$(_q3N4z1k(EFsMCjjjzx08x9VhNVcfd%6RF#r!6DE@+V5Zk2X'
|
||||||
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
|
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
|
||||||
SUPERUSER_EMAIL=admin@example.com
|
SUPERUSER_EMAIL=admin@example.com
|
||||||
SUPERUSER_NAME=admin
|
SUPERUSER_NAME=admin
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -23,14 +23,14 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox access-list plugin version
|
label: NetBox access-list plugin version
|
||||||
description: What version of the NetBox access-list plugin are you currently running?
|
description: What version of the NetBox access-list plugin are you currently running?
|
||||||
placeholder: v1.3.0
|
placeholder: v1.4.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.5.4
|
placeholder: v3.6.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -15,7 +15,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.5.4
|
placeholder: v3.6.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
|||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -44,4 +44,4 @@ jobs:
|
|||||||
|
|
||||||
- id: docker-test
|
- id: docker-test
|
||||||
name: Test the image
|
name: Test the image
|
||||||
run: ./test.sh snapshot
|
run: ./test.sh
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-docstring-first
|
- id: check-docstring-first
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
@ -21,24 +21,24 @@ repos:
|
|||||||
- "--profile=black"
|
- "--profile=black"
|
||||||
exclude: ^.devcontainer/
|
exclude: ^.devcontainer/
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.3.0
|
rev: 23.9.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3
|
language_version: python3
|
||||||
exclude: ^.devcontainer/
|
exclude: ^.devcontainer/
|
||||||
- repo: https://github.com/asottile/add-trailing-comma
|
- repo: https://github.com/asottile/add-trailing-comma
|
||||||
rev: v2.5.1
|
rev: v3.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: add-trailing-comma
|
- id: add-trailing-comma
|
||||||
args:
|
args:
|
||||||
- "--py36-plus"
|
- "--py36-plus"
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 6.0.0
|
rev: 6.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
exclude: ^.devcontainer/
|
exclude: ^.devcontainer/
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.7.0
|
rev: v3.15.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args:
|
args:
|
||||||
@ -59,11 +59,11 @@ repos:
|
|||||||
# - id: htmlhint
|
# - id: htmlhint
|
||||||
# args: [--config, .htmlhintrc]
|
# args: [--config, .htmlhintrc]
|
||||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||||
rev: v0.35.0
|
rev: v0.37.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: markdownlint
|
- id: markdownlint
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.0.272
|
rev: v0.0.292
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
#- repo: local
|
#- repo: local
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
ARG NETBOX_VARIANT=v3.5
|
ARG NETBOX_VARIANT=v3.6
|
||||||
|
|
||||||
FROM netboxcommunity/netbox:${NETBOX_VARIANT}
|
FROM netboxcommunity/netbox:${NETBOX_VARIANT}
|
||||||
|
|
||||||
@ -6,4 +6,4 @@ RUN mkdir -pv /plugins/netbox-acls
|
|||||||
COPY . /plugins/netbox-acls
|
COPY . /plugins/netbox-acls
|
||||||
|
|
||||||
RUN /opt/netbox/venv/bin/python3 /plugins/netbox-acls/setup.py develop && \
|
RUN /opt/netbox/venv/bin/python3 /plugins/netbox-acls/setup.py develop && \
|
||||||
cp -rf /plugins/netbox-acls/netbox_acls/ /opt/netbox/venv/lib/python3.10/site-packages/netbox_acls
|
cp -rf /plugins/netbox-acls/netbox_acls/ /opt/netbox/venv/lib/python3.11/site-packages/netbox_acls
|
||||||
|
|||||||
@ -38,10 +38,11 @@ Each Plugin Version listed below has been tested with its corresponding NetBox V
|
|||||||
|
|
||||||
| NetBox Version | Plugin Version |
|
| NetBox Version | Plugin Version |
|
||||||
|:--------------:|:--------------:|
|
|:--------------:|:--------------:|
|
||||||
| 3.2 | 1.0.1 |
|
| 3.6 | 1.4.0 |
|
||||||
| 3.3 | 1.1.0 |
|
|
||||||
| 3.4 | 1.2.2 |
|
|
||||||
| 3.5 | 1.3.0 |
|
| 3.5 | 1.3.0 |
|
||||||
|
| 3.4 | 1.2.2 |
|
||||||
|
| 3.3 | 1.1.0 |
|
||||||
|
| 3.2 | 1.0.1 |
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,6 @@ services:
|
|||||||
build:
|
build:
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
context: .
|
context: .
|
||||||
args:
|
|
||||||
NETBOX_VARIANT: ${NETBOX_VARIANT}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
@ -17,12 +15,12 @@ services:
|
|||||||
|
|
||||||
# postgres
|
# postgres
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:14-alpine
|
image: postgres:15-alpine
|
||||||
env_file: env/postgres.env
|
env_file: env/postgres.env
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
redis:
|
redis:
|
||||||
image: redis:6-alpine
|
image: redis:7-alpine
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
||||||
|
|||||||
2
env/netbox.env
vendored
2
env/netbox.env
vendored
@ -14,7 +14,7 @@ REDIS_DATABASE=0
|
|||||||
REDIS_HOST=redis
|
REDIS_HOST=redis
|
||||||
REDIS_INSECURE_SKIP_TLS_VERIFY=false
|
REDIS_INSECURE_SKIP_TLS_VERIFY=false
|
||||||
REDIS_PASSWORD=H733Kdjndks81
|
REDIS_PASSWORD=H733Kdjndks81
|
||||||
SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNjaa
|
SECRET_KEY='r(m)9nLGnz$(_q3N4z1k(EFsMCjjjzx08x9VhNVcfd%6RF#r!6DE@+V5Zk2X'
|
||||||
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
|
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
|
||||||
SUPERUSER_EMAIL=admin@example.com
|
SUPERUSER_EMAIL=admin@example.com
|
||||||
SUPERUSER_NAME=admin
|
SUPERUSER_NAME=admin
|
||||||
|
|||||||
@ -17,8 +17,8 @@ class NetBoxACLsConfig(PluginConfig):
|
|||||||
version = __version__
|
version = __version__
|
||||||
description = "Manage simple ACLs in NetBox"
|
description = "Manage simple ACLs in NetBox"
|
||||||
base_url = "access-lists"
|
base_url = "access-lists"
|
||||||
min_version = "3.5.0"
|
min_version = "3.6.0"
|
||||||
max_version = "3.5.99"
|
max_version = "3.6.99"
|
||||||
|
|
||||||
|
|
||||||
config = NetBoxACLsConfig
|
config = NetBoxACLsConfig
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from django import forms
|
|||||||
from ipam.models import Prefix
|
from ipam.models import Prefix
|
||||||
from netbox.forms import NetBoxModelFilterSetForm
|
from netbox.forms import NetBoxModelFilterSetForm
|
||||||
from utilities.forms.fields import (
|
from utilities.forms.fields import (
|
||||||
ChoiceField,
|
|
||||||
DynamicModelChoiceField,
|
DynamicModelChoiceField,
|
||||||
DynamicModelMultipleChoiceField,
|
DynamicModelMultipleChoiceField,
|
||||||
TagFilterField,
|
TagFilterField,
|
||||||
@ -74,11 +73,11 @@ class AccessListFilterForm(NetBoxModelFilterSetForm):
|
|||||||
queryset=VirtualChassis.objects.all(),
|
queryset=VirtualChassis.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
type = ChoiceField(
|
type = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ACLTypeChoices),
|
choices=add_blank_choice(ACLTypeChoices),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
default_action = ChoiceField(
|
default_action = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ACLActionChoices),
|
choices=add_blank_choice(ACLActionChoices),
|
||||||
required=False,
|
required=False,
|
||||||
label="Default Action",
|
label="Default Action",
|
||||||
@ -158,7 +157,7 @@ class ACLInterfaceAssignmentFilterForm(NetBoxModelFilterSetForm):
|
|||||||
},
|
},
|
||||||
label="Access List",
|
label="Access List",
|
||||||
)
|
)
|
||||||
direction = ChoiceField(
|
direction = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ACLAssignmentDirectionChoices),
|
choices=add_blank_choice(ACLAssignmentDirectionChoices),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@ -187,7 +186,7 @@ class ACLStandardRuleFilterForm(NetBoxModelFilterSetForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label="Source Prefix",
|
label="Source Prefix",
|
||||||
)
|
)
|
||||||
action = ChoiceField(
|
action = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ACLRuleActionChoices),
|
choices=add_blank_choice(ACLRuleActionChoices),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@ -211,7 +210,7 @@ class ACLExtendedRuleFilterForm(NetBoxModelFilterSetForm):
|
|||||||
queryset=AccessList.objects.all(),
|
queryset=AccessList.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
action = ChoiceField(
|
action = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ACLRuleActionChoices),
|
choices=add_blank_choice(ACLRuleActionChoices),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
@ -225,7 +224,7 @@ class ACLExtendedRuleFilterForm(NetBoxModelFilterSetForm):
|
|||||||
required=False,
|
required=False,
|
||||||
label="Destination Prefix",
|
label="Destination Prefix",
|
||||||
)
|
)
|
||||||
protocol = ChoiceField(
|
protocol = forms.ChoiceField(
|
||||||
choices=add_blank_choice(ACLProtocolChoices),
|
choices=add_blank_choice(ACLProtocolChoices),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,8 +3,8 @@ Defines each django model's GUI form to add or edit objects for each django mode
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dcim.models import Device, Interface, Region, Site, SiteGroup, VirtualChassis
|
from dcim.models import Device, Interface, Region, Site, SiteGroup, VirtualChassis
|
||||||
from django import forms
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from ipam.models import Prefix
|
from ipam.models import Prefix
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
@ -179,63 +179,49 @@ class AccessListForm(NetBoxModelForm):
|
|||||||
- Check if duplicate entry. (Because of GFK.)
|
- Check if duplicate entry. (Because of GFK.)
|
||||||
- Check if Access List has no existing rules before change the Access List's type.
|
- Check if Access List has no existing rules before change the Access List's type.
|
||||||
"""
|
"""
|
||||||
cleaned_data = super().clean()
|
super().clean()
|
||||||
error_message = {}
|
|
||||||
if self.errors.get("name"):
|
if self.errors.get("name"):
|
||||||
return cleaned_data
|
return
|
||||||
name = cleaned_data.get("name")
|
|
||||||
acl_type = cleaned_data.get("type")
|
name = self.cleaned_data.get("name")
|
||||||
device = cleaned_data.get("device")
|
acl_type = self.cleaned_data.get("type")
|
||||||
virtual_chassis = cleaned_data.get("virtual_chassis")
|
device = self.cleaned_data.get("device")
|
||||||
virtual_machine = cleaned_data.get("virtual_machine")
|
virtual_chassis = self.cleaned_data.get("virtual_chassis")
|
||||||
|
virtual_machine = self.cleaned_data.get("virtual_machine")
|
||||||
|
|
||||||
# Check if more than one host type selected.
|
# Check if more than one host type selected.
|
||||||
if (device and virtual_chassis) or (device and virtual_machine) or (virtual_chassis and virtual_machine):
|
if (device and virtual_chassis) or (device and virtual_machine) or (virtual_chassis and virtual_machine):
|
||||||
raise forms.ValidationError(
|
raise ValidationError(
|
||||||
"Access Lists must be assigned to one host at a time. Either a device, virtual chassis or virtual machine."
|
{"__all__": "Access Lists must be assigned to one host at a time. Either a device, virtual chassis or virtual machine."},
|
||||||
)
|
|
||||||
# Check if no hosts selected.
|
|
||||||
if not device and not virtual_chassis and not virtual_machine:
|
|
||||||
raise forms.ValidationError(
|
|
||||||
"Access Lists must be assigned to a device, virtual chassis or virtual machine.",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check if no hosts selected.
|
||||||
|
if not device and not virtual_chassis and not virtual_machine:
|
||||||
|
raise ValidationError({"__all__": "Access Lists must be assigned to a device, virtual chassis or virtual machine."})
|
||||||
|
|
||||||
|
existing_acls = None
|
||||||
if device:
|
if device:
|
||||||
host_type = "device"
|
host_type = "device"
|
||||||
existing_acls = AccessList.objects.filter(name=name, device=device).exists()
|
existing_acls = AccessList.objects.filter(name=name, device=device).exists()
|
||||||
elif virtual_machine:
|
elif virtual_machine:
|
||||||
host_type = "virtual_machine"
|
host_type = "virtual_machine"
|
||||||
existing_acls = AccessList.objects.filter(
|
existing_acls = AccessList.objects.filter(name=name, virtual_machine=virtual_machine).exists()
|
||||||
name=name,
|
elif virtual_chassis:
|
||||||
virtual_machine=virtual_machine,
|
|
||||||
).exists()
|
|
||||||
else:
|
|
||||||
host_type = "virtual_chassis"
|
host_type = "virtual_chassis"
|
||||||
existing_acls = AccessList.objects.filter(
|
existing_acls = AccessList.objects.filter(name=name, virtual_chassis=virtual_chassis).exists()
|
||||||
name=name,
|
|
||||||
virtual_chassis=virtual_chassis,
|
|
||||||
).exists()
|
|
||||||
|
|
||||||
# Check if duplicate entry.
|
# Check if duplicate entry.
|
||||||
if ("name" in self.changed_data or host_type in self.changed_data) and existing_acls:
|
if ("name" in self.changed_data or host_type in self.changed_data) and existing_acls:
|
||||||
error_same_acl_name = "An ACL with this name is already associated to this host."
|
error_same_acl_name = "An ACL with this name is already associated to this host."
|
||||||
error_message |= {
|
raise ValidationError({host_type: [error_same_acl_name], "name": [error_same_acl_name]})
|
||||||
host_type: [error_same_acl_name],
|
|
||||||
"name": [error_same_acl_name],
|
|
||||||
}
|
|
||||||
# Check if Access List has no existing rules before change the Access List's type.
|
# Check if Access List has no existing rules before change the Access List's type.
|
||||||
if self.instance.pk and (
|
if self.instance.pk and (
|
||||||
(acl_type == ACLTypeChoices.TYPE_EXTENDED and self.instance.aclstandardrules.exists())
|
(acl_type == ACLTypeChoices.TYPE_EXTENDED and self.instance.aclstandardrules.exists())
|
||||||
or (acl_type == ACLTypeChoices.TYPE_STANDARD and self.instance.aclextendedrules.exists())
|
or (acl_type == ACLTypeChoices.TYPE_STANDARD and self.instance.aclextendedrules.exists())
|
||||||
):
|
):
|
||||||
error_message["type"] = [
|
raise ValidationError({"type": ["This ACL has ACL rules associated, CANNOT change ACL type."]})
|
||||||
"This ACL has ACL rules associated, CANNOT change ACL type.",
|
|
||||||
]
|
|
||||||
|
|
||||||
if error_message:
|
|
||||||
raise forms.ValidationError(error_message)
|
|
||||||
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# Set assigned object
|
# Set assigned object
|
||||||
@ -343,12 +329,13 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
|
|||||||
- Check for duplicate entry. (Because of GFK)
|
- Check for duplicate entry. (Because of GFK)
|
||||||
- Check that the interface does not have an existing ACL applied in the direction already.
|
- Check that the interface does not have an existing ACL applied in the direction already.
|
||||||
"""
|
"""
|
||||||
cleaned_data = super().clean()
|
super().clean()
|
||||||
|
|
||||||
error_message = {}
|
error_message = {}
|
||||||
access_list = cleaned_data.get("access_list")
|
access_list = self.cleaned_data.get("access_list")
|
||||||
direction = cleaned_data.get("direction")
|
direction = self.cleaned_data.get("direction")
|
||||||
interface = cleaned_data.get("interface")
|
interface = self.cleaned_data.get("interface")
|
||||||
vminterface = cleaned_data.get("vminterface")
|
vminterface = self.cleaned_data.get("vminterface")
|
||||||
|
|
||||||
# Check if both interface and vminterface are set.
|
# Check if both interface and vminterface are set.
|
||||||
if interface and vminterface:
|
if interface and vminterface:
|
||||||
@ -366,22 +353,20 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
|
|||||||
"vminterface": [error_no_interface],
|
"vminterface": [error_no_interface],
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
# Define assigned_object, assigned_object_type, host_type, and host based on interface or vminterface
|
||||||
if interface:
|
if interface:
|
||||||
assigned_object = interface
|
assigned_object = interface
|
||||||
assigned_object_type = "interface"
|
assigned_object_type = "interface"
|
||||||
host_type = "device"
|
host_type = "device"
|
||||||
host = Interface.objects.get(pk=assigned_object.pk).device
|
host = Interface.objects.get(pk=assigned_object.pk).device
|
||||||
assigned_object_id = Interface.objects.get(pk=assigned_object.pk).pk
|
|
||||||
else:
|
else:
|
||||||
assigned_object = vminterface
|
assigned_object = vminterface
|
||||||
assigned_object_type = "vminterface"
|
assigned_object_type = "vminterface"
|
||||||
host_type = "virtual_machine"
|
host_type = "virtual_machine"
|
||||||
host = VMInterface.objects.get(pk=assigned_object.pk).virtual_machine
|
host = VMInterface.objects.get(pk=assigned_object.pk).virtual_machine
|
||||||
assigned_object_id = VMInterface.objects.get(pk=assigned_object.pk).pk
|
|
||||||
|
|
||||||
assigned_object_type_id = ContentType.objects.get_for_model(
|
assigned_object_id = assigned_object.pk
|
||||||
assigned_object,
|
assigned_object_type_id = ContentType.objects.get_for_model(assigned_object).pk
|
||||||
).pk
|
|
||||||
access_list_host = AccessList.objects.get(pk=access_list.pk).assigned_object
|
access_list_host = AccessList.objects.get(pk=access_list.pk).assigned_object
|
||||||
|
|
||||||
# Check that an interface's parent device/virtual_machine is assigned to the Access List.
|
# Check that an interface's parent device/virtual_machine is assigned to the Access List.
|
||||||
@ -392,20 +377,22 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
|
|||||||
assigned_object_type: [error_acl_not_assigned_to_host],
|
assigned_object_type: [error_acl_not_assigned_to_host],
|
||||||
host_type: [error_acl_not_assigned_to_host],
|
host_type: [error_acl_not_assigned_to_host],
|
||||||
}
|
}
|
||||||
# Check for duplicate entry.
|
|
||||||
if ACLInterfaceAssignment.objects.filter(
|
# Check for duplicate entry and existing ACL in the direction.
|
||||||
|
existing_acl = ACLInterfaceAssignment.objects.filter(
|
||||||
access_list=access_list,
|
access_list=access_list,
|
||||||
assigned_object_id=assigned_object_id,
|
assigned_object_id=assigned_object_id,
|
||||||
assigned_object_type=assigned_object_type_id,
|
assigned_object_type=assigned_object_type_id,
|
||||||
direction=direction,
|
direction=direction,
|
||||||
).exists():
|
)
|
||||||
|
if existing_acl.exists():
|
||||||
error_duplicate_entry = "An ACL with this name is already associated to this interface & direction."
|
error_duplicate_entry = "An ACL with this name is already associated to this interface & direction."
|
||||||
error_message |= {
|
error_message |= {
|
||||||
"access_list": [error_duplicate_entry],
|
"access_list": [error_duplicate_entry],
|
||||||
"direction": [error_duplicate_entry],
|
"direction": [error_duplicate_entry],
|
||||||
assigned_object_type: [error_duplicate_entry],
|
assigned_object_type: [error_duplicate_entry],
|
||||||
}
|
}
|
||||||
# Check that the interface does not have an existing ACL applied in the direction already.
|
|
||||||
if ACLInterfaceAssignment.objects.filter(
|
if ACLInterfaceAssignment.objects.filter(
|
||||||
assigned_object_id=assigned_object_id,
|
assigned_object_id=assigned_object_id,
|
||||||
assigned_object_type=assigned_object_type_id,
|
assigned_object_type=assigned_object_type_id,
|
||||||
@ -418,8 +405,7 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if error_message:
|
if error_message:
|
||||||
raise forms.ValidationError(error_message)
|
raise ValidationError(error_message)
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
# Set assigned object
|
# Set assigned object
|
||||||
@ -484,27 +470,27 @@ class ACLStandardRuleForm(NetBoxModelForm):
|
|||||||
- Check if action set to remark, but source_prefix set.
|
- Check if action set to remark, but source_prefix set.
|
||||||
- Check remark set, but action not set to remark.
|
- Check remark set, but action not set to remark.
|
||||||
"""
|
"""
|
||||||
cleaned_data = super().clean()
|
super().clean()
|
||||||
|
cleaned_data = self.cleaned_data
|
||||||
error_message = {}
|
error_message = {}
|
||||||
|
|
||||||
# No need to check for unique_together since there is no usage of GFK
|
action = cleaned_data.get("action")
|
||||||
|
remark = cleaned_data.get("remark")
|
||||||
|
source_prefix = cleaned_data.get("source_prefix")
|
||||||
|
|
||||||
if cleaned_data.get("action") == "remark":
|
if action == "remark":
|
||||||
# Check if action set to remark, but no remark set.
|
# Check if action set to remark, but no remark set.
|
||||||
if not cleaned_data.get("remark"):
|
if not remark:
|
||||||
error_message["remark"] = [error_message_no_remark]
|
error_message["remark"] = [error_message_no_remark]
|
||||||
# Check if action set to remark, but source_prefix set.
|
# Check if action set to remark, but source_prefix set.
|
||||||
if cleaned_data.get("source_prefix"):
|
if source_prefix:
|
||||||
error_message["source_prefix"] = [
|
error_message["source_prefix"] = [error_message_action_remark_source_prefix_set]
|
||||||
error_message_action_remark_source_prefix_set,
|
|
||||||
]
|
|
||||||
# Check remark set, but action not set to remark.
|
# Check remark set, but action not set to remark.
|
||||||
elif cleaned_data.get("remark"):
|
elif remark:
|
||||||
error_message["remark"] = [error_message_remark_without_action_remark]
|
error_message["remark"] = [error_message_remark_without_action_remark]
|
||||||
|
|
||||||
if error_message:
|
if error_message:
|
||||||
raise forms.ValidationError(error_message)
|
raise ValidationError(error_message)
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
|
|
||||||
class ACLExtendedRuleForm(NetBoxModelForm):
|
class ACLExtendedRuleForm(NetBoxModelForm):
|
||||||
@ -583,53 +569,41 @@ class ACLExtendedRuleForm(NetBoxModelForm):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
"""
|
"""
|
||||||
Validates form inputs before submitting:
|
Validates form inputs before submitting:
|
||||||
- Check if action set to remark, but no remark set.
|
- Check if action set to remark, but no remark set.
|
||||||
- Check if action set to remark, but source_prefix set.
|
- Check if action set to remark, but source_prefix set.
|
||||||
- Check if action set to remark, but source_ports set.
|
- Check if action set to remark, but source_ports set.
|
||||||
- Check if action set to remark, but destination_prefix set.
|
- Check if action set to remark, but destination_prefix set.
|
||||||
- Check if action set to remark, but destination_ports set.
|
- Check if action set to remark, but destination_ports set.
|
||||||
- Check if action set to remark, but destination_ports set.
|
- Check if action set to remark, but protocol set.
|
||||||
- Check if action set to remark, but protocol set.
|
- Check remark set, but action not set to remark.
|
||||||
- Check remark set, but action not set to remark.
|
|
||||||
"""
|
"""
|
||||||
cleaned_data = super().clean()
|
super().clean()
|
||||||
|
cleaned_data = self.cleaned_data
|
||||||
error_message = {}
|
error_message = {}
|
||||||
|
|
||||||
# No need to check for unique_together since there is no usage of GFK
|
action = cleaned_data.get("action")
|
||||||
|
remark = cleaned_data.get("remark")
|
||||||
|
source_prefix = cleaned_data.get("source_prefix")
|
||||||
|
source_ports = cleaned_data.get("source_ports")
|
||||||
|
destination_prefix = cleaned_data.get("destination_prefix")
|
||||||
|
destination_ports = cleaned_data.get("destination_ports")
|
||||||
|
protocol = cleaned_data.get("protocol")
|
||||||
|
|
||||||
if cleaned_data.get("action") == "remark":
|
if action == "remark":
|
||||||
# Check if action set to remark, but no remark set.
|
if not remark:
|
||||||
if not cleaned_data.get("remark"):
|
|
||||||
error_message["remark"] = [error_message_no_remark]
|
error_message["remark"] = [error_message_no_remark]
|
||||||
# Check if action set to remark, but source_prefix set.
|
if source_prefix:
|
||||||
if cleaned_data.get("source_prefix"):
|
error_message["source_prefix"] = [error_message_action_remark_source_prefix_set]
|
||||||
error_message["source_prefix"] = [
|
if source_ports:
|
||||||
error_message_action_remark_source_prefix_set,
|
error_message["source_ports"] = ["Action is set to remark, Source Ports CANNOT be set."]
|
||||||
]
|
if destination_prefix:
|
||||||
# Check if action set to remark, but source_ports set.
|
error_message["destination_prefix"] = ["Action is set to remark, Destination Prefix CANNOT be set."]
|
||||||
if cleaned_data.get("source_ports"):
|
if destination_ports:
|
||||||
error_message["source_ports"] = [
|
error_message["destination_ports"] = ["Action is set to remark, Destination Ports CANNOT be set."]
|
||||||
"Action is set to remark, Source Ports CANNOT be set.",
|
if protocol:
|
||||||
]
|
error_message["protocol"] = ["Action is set to remark, Protocol CANNOT be set."]
|
||||||
# Check if action set to remark, but destination_prefix set.
|
elif remark:
|
||||||
if cleaned_data.get("destination_prefix"):
|
|
||||||
error_message["destination_prefix"] = [
|
|
||||||
"Action is set to remark, Destination Prefix CANNOT be set.",
|
|
||||||
]
|
|
||||||
# Check if action set to remark, but destination_ports set.
|
|
||||||
if cleaned_data.get("destination_ports"):
|
|
||||||
error_message["destination_ports"] = [
|
|
||||||
"Action is set to remark, Destination Ports CANNOT be set.",
|
|
||||||
]
|
|
||||||
# Check if action set to remark, but protocol set.
|
|
||||||
if cleaned_data.get("protocol"):
|
|
||||||
error_message["protocol"] = [
|
|
||||||
"Action is set to remark, Protocol CANNOT be set.",
|
|
||||||
]
|
|
||||||
# Check if action not set to remark, but remark set.
|
|
||||||
elif cleaned_data.get("remark"):
|
|
||||||
error_message["remark"] = [error_message_remark_without_action_remark]
|
error_message["remark"] = [error_message_remark_without_action_remark]
|
||||||
|
|
||||||
if error_message:
|
if error_message:
|
||||||
raise forms.ValidationError(error_message)
|
raise ValidationError(error_message)
|
||||||
return cleaned_data
|
|
||||||
|
|||||||
@ -64,7 +64,7 @@ class AccessListTable(NetBoxTable):
|
|||||||
"rule_count",
|
"rule_count",
|
||||||
"default_action",
|
"default_action",
|
||||||
"comments",
|
"comments",
|
||||||
"actions",
|
"action",
|
||||||
"tags",
|
"tags",
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -147,7 +147,6 @@ class ACLStandardRuleTable(NetBoxTable):
|
|||||||
"access_list",
|
"access_list",
|
||||||
"index",
|
"index",
|
||||||
"action",
|
"action",
|
||||||
"actions",
|
|
||||||
"remark",
|
"remark",
|
||||||
"tags",
|
"tags",
|
||||||
"description",
|
"description",
|
||||||
@ -157,7 +156,6 @@ class ACLStandardRuleTable(NetBoxTable):
|
|||||||
"access_list",
|
"access_list",
|
||||||
"index",
|
"index",
|
||||||
"action",
|
"action",
|
||||||
"actions",
|
|
||||||
"remark",
|
"remark",
|
||||||
"source_prefix",
|
"source_prefix",
|
||||||
"tags",
|
"tags",
|
||||||
@ -189,7 +187,6 @@ class ACLExtendedRuleTable(NetBoxTable):
|
|||||||
"access_list",
|
"access_list",
|
||||||
"index",
|
"index",
|
||||||
"action",
|
"action",
|
||||||
"actions",
|
|
||||||
"remark",
|
"remark",
|
||||||
"tags",
|
"tags",
|
||||||
"description",
|
"description",
|
||||||
@ -203,7 +200,6 @@ class ACLExtendedRuleTable(NetBoxTable):
|
|||||||
"access_list",
|
"access_list",
|
||||||
"index",
|
"index",
|
||||||
"action",
|
"action",
|
||||||
"actions",
|
|
||||||
"remark",
|
"remark",
|
||||||
"tags",
|
"tags",
|
||||||
"source_prefix",
|
"source_prefix",
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
__version__ = "1.3.0"
|
__version__ = "1.4.0"
|
||||||
|
|||||||
13
test.sh
13
test.sh
@ -1,16 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Runs the NetBox plugin unit tests
|
# Runs the NetBox plugin unit tests
|
||||||
# Usage:
|
|
||||||
# ./test.sh latest
|
|
||||||
# ./test.sh v2.9.7
|
|
||||||
# ./test.sh develop-2.10
|
|
||||||
|
|
||||||
# exit when a command exits with an exit code != 0
|
# exit when a command exits with an exit code != 0
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# NETBOX_VARIANT is used by `Dockerfile` to determine the tag
|
|
||||||
NETBOX_VARIANT="${1-latest}"
|
|
||||||
|
|
||||||
# The docker compose command to use
|
# The docker compose command to use
|
||||||
doco="docker compose --file docker-compose.yml"
|
doco="docker compose --file docker-compose.yml"
|
||||||
|
|
||||||
@ -27,12 +20,10 @@ test_cleanup() {
|
|||||||
docker image rm docker.io/library/netbox-acls-netbox || echo ''
|
docker image rm docker.io/library/netbox-acls-netbox || echo ''
|
||||||
}
|
}
|
||||||
|
|
||||||
export NETBOX_VARIANT=${NETBOX_VARIANT}
|
echo "🐳🐳🐳 Start testing"
|
||||||
|
|
||||||
echo "🐳🐳🐳 Start testing '${NETBOX_VARIANT}'"
|
|
||||||
|
|
||||||
# Make sure the cleanup script is executed
|
# Make sure the cleanup script is executed
|
||||||
trap test_cleanup EXIT ERR
|
trap test_cleanup EXIT ERR
|
||||||
test_netbox_unit_tests
|
test_netbox_unit_tests
|
||||||
|
|
||||||
echo "🐳🐳🐳 Done testing '${NETBOX_VARIANT}'"
|
echo "🐳🐳🐳 Done testing"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user