NetBox 3.5 Support (#148)

* NetBox 3.5 updates
This commit is contained in:
Ryan Merolle 2023-06-23 14:41:42 -04:00 committed by GitHub
parent fa38ef4df2
commit 108fbb6751
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 133 additions and 143 deletions

View File

@ -1,8 +1,8 @@
ARG NETBOX_VARIANT=v3.4 ARG NETBOX_VARIANT=v3.5
FROM netboxcommunity/netbox:${NETBOX_VARIANT} FROM netboxcommunity/netbox:${NETBOX_VARIANT}
ARG NETBOX_INITIALIZERS_VARIANT=3.4.* ARG NETBOX_INITIALIZERS_VARIANT=3.5.*
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive

View File

@ -7,7 +7,7 @@ from os.path import abspath, dirname, join
# Read secret from file # Read secret from file
def _read_secret(secret_name, default=None): def _read_secret(secret_name, default=None):
try: try:
f = open("/run/secrets/" + secret_name, encoding="utf-8") f = open(f"/run/secrets/{secret_name}", encoding="utf-8")
except OSError: except OSError:
return default return default
else: else:
@ -74,8 +74,7 @@ REDIS = {
environ.get("REDIS_CACHE_PASSWORD", environ.get("REDIS_PASSWORD", "")), environ.get("REDIS_CACHE_PASSWORD", environ.get("REDIS_PASSWORD", "")),
), ),
"DATABASE": int(environ.get("REDIS_CACHE_DATABASE", 1)), "DATABASE": int(environ.get("REDIS_CACHE_DATABASE", 1)),
"SSL": environ.get("REDIS_CACHE_SSL", environ.get("REDIS_SSL", "False")).lower() "SSL": environ.get("REDIS_CACHE_SSL", environ.get("REDIS_SSL", "False")).lower() == "true",
== "true",
"INSECURE_SKIP_TLS_VERIFY": environ.get( "INSECURE_SKIP_TLS_VERIFY": environ.get(
"REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY", "REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY",
environ.get("REDIS_INSECURE_SKIP_TLS_VERIFY", "False"), environ.get("REDIS_INSECURE_SKIP_TLS_VERIFY", "False"),
@ -252,9 +251,7 @@ REMOTE_AUTH_BACKEND = environ.get(
"netbox.authentication.RemoteUserBackend", "netbox.authentication.RemoteUserBackend",
) )
REMOTE_AUTH_HEADER = environ.get("REMOTE_AUTH_HEADER", "HTTP_REMOTE_USER") REMOTE_AUTH_HEADER = environ.get("REMOTE_AUTH_HEADER", "HTTP_REMOTE_USER")
REMOTE_AUTH_AUTO_CREATE_USER = ( REMOTE_AUTH_AUTO_CREATE_USER = environ.get("REMOTE_AUTH_AUTO_CREATE_USER", "True").lower() == "true"
environ.get("REMOTE_AUTH_AUTO_CREATE_USER", "True").lower() == "true"
)
REMOTE_AUTH_DEFAULT_GROUPS = list( REMOTE_AUTH_DEFAULT_GROUPS = list(
filter(None, environ.get("REMOTE_AUTH_DEFAULT_GROUPS", "").split(" ")), filter(None, environ.get("REMOTE_AUTH_DEFAULT_GROUPS", "").split(" ")),
) )

View File

@ -78,23 +78,31 @@
"extensions": [ "extensions": [
"DavidAnson.vscode-markdownlint", "DavidAnson.vscode-markdownlint",
"GitHub.codespaces", "GitHub.codespaces",
"GitHub.copilot",
"GitHub.copilot-labs", "GitHub.copilot-labs",
"GitHub.vscode-pull-request-github", "GitHub.vscode-pull-request-github",
"Gruntfuggly.todo-tree", "Gruntfuggly.todo-tree",
"Tyriar.sort-lines", "Tyriar.sort-lines",
"aaron-bond.better-comments", "aaron-bond.better-comments",
"batisteo.vscode-django", "batisteo.vscode-django",
"charliermarsh.ruff",
"codezombiech.gitignore", "codezombiech.gitignore",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"exiasr.hadolint",
"formulahendry.auto-rename-tag", "formulahendry.auto-rename-tag",
"mintlify.document", "mintlify.document",
"ms-python.isort",
"ms-python.pylint",
"ms-python.python", "ms-python.python",
"ms-python.vscode-pylance", "ms-python.vscode-pylance",
"ms-vscode.makefile-tools",
"mutantdino.resourcemonitor", "mutantdino.resourcemonitor",
"oderwat.indent-rainbow",
"paulomenezes.duplicated-code", "paulomenezes.duplicated-code",
"redhat.vscode-yaml",
"searKing.preview-vscode", "searKing.preview-vscode",
"sourcery.sourcery" "sourcery.sourcery",
"wholroyd.jinja",
"yzhang.markdown-all-in-one"
] ]
} }
}, },

View File

@ -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_rClfWNj SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNjaa
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
SUPERUSER_EMAIL=admin@example.com SUPERUSER_EMAIL=admin@example.com
SUPERUSER_NAME=admin SUPERUSER_NAME=admin

View File

@ -10,6 +10,7 @@ pycodestyle
pydocstyle pydocstyle
pylint pylint
pylint-django pylint-django
ruff
sourcery-analytics
wily wily
yapf yapf
sourcery-analytics

View File

@ -1,3 +1,3 @@
[flake8] [flake8]
max-line-length = 160 max-line-length = 140
extend-ignore = E203 extend-ignore = E203

View File

@ -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.2.0 placeholder: v1.3.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.4.3 placeholder: v3.5.4
validations: validations:
required: true required: true
- type: textarea - type: textarea

View File

@ -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.4.3 placeholder: v3.5.4
validations: validations:
required: true required: true
- type: dropdown - type: dropdown

View File

@ -1,8 +0,0 @@
[settings]
profile = black
; vertical hanging indent mode also used in black configuration
multi_line_output = 3
; necessary because black expect the trailing comma
include_trailing_comma = true

View File

@ -1,4 +1,4 @@
{ {
"threshold": 10, "threshold": 10,
"ignore": ["**/tests/**"] "ignore": ["**/migrations/**", "**/tests/**"]
} }

View File

@ -21,13 +21,13 @@ repos:
- "--profile=black" - "--profile=black"
exclude: ^.devcontainer/ exclude: ^.devcontainer/
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.1.0 rev: 23.3.0
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.4.0 rev: v2.5.1
hooks: hooks:
- id: add-trailing-comma - id: add-trailing-comma
args: args:
@ -38,13 +38,13 @@ repos:
- id: flake8 - id: flake8
exclude: ^.devcontainer/ exclude: ^.devcontainer/
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.3.1 rev: v3.7.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: args:
- "--py39-plus" - "--py39-plus"
- repo: https://github.com/adrienverge/yamllint - repo: https://github.com/adrienverge/yamllint
rev: v1.29.0 rev: v1.32.0
hooks: hooks:
- id: yamllint - id: yamllint
- repo: https://github.com/econchick/interrogate - repo: https://github.com/econchick/interrogate
@ -59,9 +59,13 @@ 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.33.0 rev: v0.35.0
hooks: hooks:
- id: markdownlint - id: markdownlint
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.272
hooks:
- id: ruff
#- repo: local #- repo: local
# hooks: # hooks:
# - id: wily # - id: wily

View File

@ -1,9 +1,9 @@
ARG NETBOX_VARIANT=v3.4 ARG NETBOX_VARIANT=v3.5
FROM netboxcommunity/netbox:${NETBOX_VARIANT} FROM netboxcommunity/netbox:${NETBOX_VARIANT}
RUN mkdir -pv /plugins/netbox-acls 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 && \
RUN 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.10/site-packages/netbox_acls

View File

@ -41,6 +41,7 @@ Each Plugin Version listed below has been tested with its corresponding NetBox V
| 3.2 | 1.0.1 | | 3.2 | 1.0.1 |
| 3.3 | 1.1.0 | | 3.3 | 1.1.0 |
| 3.4 | 1.2.2 | | 3.4 | 1.2.2 |
| 3.5 | 1.3.0 |
## Installing ## Installing

View File

@ -14,7 +14,7 @@ from os.path import abspath, dirname
# Read secret from file # Read secret from file
def _read_secret(secret_name, default=None): def _read_secret(secret_name, default=None):
try: try:
f = open("/run/secrets/" + secret_name, encoding="utf-8") f = open(f"/run/secrets/{secret_name}", encoding="utf-8")
except OSError: except OSError:
return default return default
else: else:
@ -81,8 +81,7 @@ REDIS = {
environ.get("REDIS_CACHE_PASSWORD", environ.get("REDIS_PASSWORD", "")), environ.get("REDIS_CACHE_PASSWORD", environ.get("REDIS_PASSWORD", "")),
), ),
"DATABASE": int(environ.get("REDIS_CACHE_DATABASE", 1)), "DATABASE": int(environ.get("REDIS_CACHE_DATABASE", 1)),
"SSL": environ.get("REDIS_CACHE_SSL", environ.get("REDIS_SSL", "False")).lower() "SSL": environ.get("REDIS_CACHE_SSL", environ.get("REDIS_SSL", "False")).lower() == "true",
== "true",
"INSECURE_SKIP_TLS_VERIFY": environ.get( "INSECURE_SKIP_TLS_VERIFY": environ.get(
"REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY", "REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY",
environ.get("REDIS_INSECURE_SKIP_TLS_VERIFY", "False"), environ.get("REDIS_INSECURE_SKIP_TLS_VERIFY", "False"),

2
env/netbox.env vendored
View File

@ -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_rClfWNj SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNjaa
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
SUPERUSER_EMAIL=admin@example.com SUPERUSER_EMAIL=admin@example.com
SUPERUSER_NAME=admin SUPERUSER_NAME=admin

View File

@ -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.4.0" min_version = "3.5.0"
max_version = "3.4.99" max_version = "3.5.99"
config = NetBoxACLsConfig config = NetBoxACLsConfig

View File

@ -4,8 +4,7 @@ while Django itself handles the database abstraction.
""" """
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from drf_spectacular.utils import extend_schema_field
from drf_yasg.utils import swagger_serializer_method
from ipam.api.serializers import NestedPrefixSerializer from ipam.api.serializers import NestedPrefixSerializer
from netbox.api.fields import ContentTypeField from netbox.api.fields import ContentTypeField
from netbox.api.serializers import NetBoxModelSerializer from netbox.api.serializers import NetBoxModelSerializer
@ -32,13 +31,9 @@ __all__ = [
# Sets a standard error message for ACL rules with an action of remark, but no remark set. # Sets a standard error message for ACL rules with an action of remark, but no remark set.
error_message_no_remark = "Action is set to remark, you MUST add a remark." error_message_no_remark = "Action is set to remark, you MUST add a remark."
# Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set. # Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set.
error_message_action_remark_source_prefix_set = ( error_message_action_remark_source_prefix_set = "Action is set to remark, Source Prefix CANNOT be set."
"Action is set to remark, Source Prefix CANNOT be set."
)
# Sets a standard error message for ACL rules with an action not set to remark, but no remark is set. # Sets a standard error message for ACL rules with an action not set to remark, but no remark is set.
error_message_remark_without_action_remark = ( error_message_remark_without_action_remark = "CANNOT set remark unless action is set to remark."
"CANNOT set remark unless action is set to remark."
)
# Sets a standard error message for ACL rules no associated to an ACL of the same type. # Sets a standard error message for ACL rules no associated to an ACL of the same type.
error_message_acl_type = "Provided parent Access List is not of right type." error_message_acl_type = "Provided parent Access List is not of right type."
@ -81,7 +76,7 @@ class AccessListSerializer(NetBoxModelSerializer):
"rule_count", "rule_count",
) )
@swagger_serializer_method(serializer_or_field=serializers.DictField) @extend_schema_field(serializers.DictField())
def get_assigned_object(self, obj): def get_assigned_object(self, obj):
serializer = get_serializer_for_model( serializer = get_serializer_for_model(
obj.assigned_object, obj.assigned_object,
@ -99,11 +94,7 @@ class AccessListSerializer(NetBoxModelSerializer):
error_message = {} error_message = {}
# 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 ( if self.instance and self.instance.type != data.get("type") and self.instance.rule_count > 0:
self.instance
and self.instance.type != data.get("type")
and self.instance.rule_count > 0
):
error_message["type"] = [ error_message["type"] = [
"This ACL has ACL rules associated, CANNOT change ACL type.", "This ACL has ACL rules associated, CANNOT change ACL type.",
] ]
@ -149,7 +140,7 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer):
"last_updated", "last_updated",
) )
@swagger_serializer_method(serializer_or_field=serializers.DictField) @extend_schema_field(serializers.DictField())
def get_assigned_object(self, obj): def get_assigned_object(self, obj):
serializer = get_serializer_for_model( serializer = get_serializer_for_model(
obj.assigned_object, obj.assigned_object,
@ -168,24 +159,14 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer):
acl_host = data["access_list"].assigned_object acl_host = data["access_list"].assigned_object
if data["assigned_object_type"].model == "interface": if data["assigned_object_type"].model == "interface":
interface_host = ( interface_host = data["assigned_object_type"].get_object_for_this_type(id=data["assigned_object_id"]).device
data["assigned_object_type"]
.get_object_for_this_type(id=data["assigned_object_id"])
.device
)
elif data["assigned_object_type"].model == "vminterface": elif data["assigned_object_type"].model == "vminterface":
interface_host = ( interface_host = data["assigned_object_type"].get_object_for_this_type(id=data["assigned_object_id"]).virtual_machine
data["assigned_object_type"]
.get_object_for_this_type(id=data["assigned_object_id"])
.virtual_machine
)
else: else:
interface_host = None interface_host = None
# Check that the associated interface's parent host has the selected ACL defined. # Check that the associated interface's parent host has the selected ACL defined.
if acl_host != interface_host: if acl_host != interface_host:
error_acl_not_assigned_to_host = ( error_acl_not_assigned_to_host = "Access List not present on the selected interface's host."
"Access List not present on the selected interface's host."
)
error_message["access_list"] = [error_acl_not_assigned_to_host] error_message["access_list"] = [error_acl_not_assigned_to_host]
error_message["assigned_object_id"] = [error_acl_not_assigned_to_host] error_message["assigned_object_id"] = [error_acl_not_assigned_to_host]

View File

@ -10,6 +10,5 @@ ACL_HOST_ASSIGNMENT_MODELS = Q(
) )
ACL_INTERFACE_ASSIGNMENT_MODELS = Q( ACL_INTERFACE_ASSIGNMENT_MODELS = Q(
Q(app_label="dcim", model="interface") Q(app_label="dcim", model="interface") | Q(app_label="virtualization", model="vminterface"),
| Q(app_label="virtualization", model="vminterface"),
) )

View File

@ -7,11 +7,11 @@ Draft for a possible BulkEditForm, but may not be worth wile.
# from django.core.exceptions import ValidationError # from django.core.exceptions import ValidationError
# from django.utils.safestring import mark_safe # from django.utils.safestring import mark_safe
# from netbox.forms import NetBoxModelBulkEditForm # from netbox.forms import NetBoxModelBulkEditForm
# from utilities.forms import ( # from utilities.forms.utils import add_blank_choice
# from utilities.forms.fields import (
# ChoiceField, # ChoiceField,
# DynamicModelChoiceField, # DynamicModelChoiceField,
# StaticSelect, # StaticSelect,
# add_blank_choice,
# ) # )
# from virtualization.models import VirtualMachine # from virtualization.models import VirtualMachine

View File

@ -6,13 +6,13 @@ from dcim.models import Device, Interface, Region, Site, SiteGroup, VirtualChass
from django import forms 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 import ( from utilities.forms.fields import (
ChoiceField, ChoiceField,
DynamicModelChoiceField, DynamicModelChoiceField,
DynamicModelMultipleChoiceField, DynamicModelMultipleChoiceField,
TagFilterField, TagFilterField,
add_blank_choice,
) )
from utilities.forms.utils import add_blank_choice
from virtualization.models import VirtualMachine, VMInterface from virtualization.models import VirtualMachine, VMInterface
from ..choices import ( from ..choices import (

View File

@ -8,7 +8,7 @@ from django.contrib.contenttypes.models import ContentType
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
from utilities.forms import CommentField, DynamicModelChoiceField from utilities.forms.fields import CommentField, DynamicModelChoiceField
from virtualization.models import ( from virtualization.models import (
Cluster, Cluster,
ClusterGroup, ClusterGroup,
@ -39,20 +39,14 @@ help_text_acl_rule_logic = mark_safe(
# Sets a standard help_text value to be used by the various classes for acl action # Sets a standard help_text value to be used by the various classes for acl action
help_text_acl_action = "Action the rule will take (remark, deny, or allow)." help_text_acl_action = "Action the rule will take (remark, deny, or allow)."
# Sets a standard help_text value to be used by the various classes for acl index # Sets a standard help_text value to be used by the various classes for acl index
help_text_acl_rule_index = ( help_text_acl_rule_index = "Determines the order of the rule in the ACL processing. AKA Sequence Number."
"Determines the order of the rule in the ACL processing. AKA Sequence Number."
)
# Sets a standard error message for ACL rules with an action of remark, but no remark set. # Sets a standard error message for ACL rules with an action of remark, but no remark set.
error_message_no_remark = "Action is set to remark, you MUST add a remark." error_message_no_remark = "Action is set to remark, you MUST add a remark."
# Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set. # Sets a standard error message for ACL rules with an action of remark, but no source_prefix is set.
error_message_action_remark_source_prefix_set = ( error_message_action_remark_source_prefix_set = "Action is set to remark, Source Prefix CANNOT be set."
"Action is set to remark, Source Prefix CANNOT be set."
)
# Sets a standard error message for ACL rules with an action not set to remark, but no remark is set. # Sets a standard error message for ACL rules with an action not set to remark, but no remark is set.
error_message_remark_without_action_remark = ( error_message_remark_without_action_remark = "CANNOT set remark unless action is set to remark."
"CANNOT set remark unless action is set to remark."
)
class AccessListForm(NetBoxModelForm): class AccessListForm(NetBoxModelForm):
@ -149,7 +143,6 @@ class AccessListForm(NetBoxModelForm):
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Initialize helper selectors # Initialize helper selectors
instance = kwargs.get("instance") instance = kwargs.get("instance")
initial = kwargs.get("initial", {}).copy() initial = kwargs.get("initial", {}).copy()
@ -168,9 +161,7 @@ class AccessListForm(NetBoxModelForm):
if instance.assigned_object.cluster: if instance.assigned_object.cluster:
initial["cluster"] = instance.assigned_object.cluster initial["cluster"] = instance.assigned_object.cluster
if instance.assigned_object.cluster.group: if instance.assigned_object.cluster.group:
initial[ initial["cluster_group"] = instance.assigned_object.cluster.group
"cluster_group"
] = instance.assigned_object.cluster.group
if instance.assigned_object.cluster.type: if instance.assigned_object.cluster.type:
initial["cluster_type"] = instance.assigned_object.cluster.type initial["cluster_type"] = instance.assigned_object.cluster.type
@ -199,13 +190,9 @@ class AccessListForm(NetBoxModelForm):
virtual_machine = cleaned_data.get("virtual_machine") virtual_machine = cleaned_data.get("virtual_machine")
# Check if more than one host type selected. # Check if more than one host type selected.
if ( if (device and virtual_chassis) or (device and virtual_machine) or (virtual_chassis and virtual_machine):
(device and virtual_chassis)
or (device and virtual_machine)
or (virtual_chassis and virtual_machine)
):
raise forms.ValidationError( raise forms.ValidationError(
"Access Lists must be assigned to one host (either a device, virtual chassis or virtual machine) at a time.", "Access Lists must be assigned to one host at a time. Either a device, virtual chassis or virtual machine."
) )
# Check if no hosts selected. # Check if no hosts selected.
if not device and not virtual_chassis and not virtual_machine: if not device and not virtual_chassis and not virtual_machine:
@ -230,28 +217,20 @@ class AccessListForm(NetBoxModelForm):
).exists() ).exists()
# Check if duplicate entry. # Check if duplicate entry.
if ( if ("name" in self.changed_data or host_type in self.changed_data) and existing_acls:
"name" in self.changed_data or host_type in self.changed_data error_same_acl_name = "An ACL with this name is already associated to this host."
) and existing_acls:
error_same_acl_name = (
"An ACL with this name is already associated to this host."
)
error_message |= { error_message |= {
host_type: [error_same_acl_name], host_type: [error_same_acl_name],
"name": [error_same_acl_name], "name": [error_same_acl_name],
} }
if self.instance.pk: # 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 ( (acl_type == ACLTypeChoices.TYPE_EXTENDED and self.instance.aclstandardrules.exists())
acl_type == ACLTypeChoices.TYPE_EXTENDED or (acl_type == ACLTypeChoices.TYPE_STANDARD and self.instance.aclextendedrules.exists())
and self.instance.aclstandardrules.exists() ):
) or ( error_message["type"] = [
acl_type == ACLTypeChoices.TYPE_STANDARD "This ACL has ACL rules associated, CANNOT change ACL type.",
and self.instance.aclextendedrules.exists() ]
):
error_message["type"] = [
"This ACL has ACL rules associated, CANNOT change ACL type.",
]
if error_message: if error_message:
raise forms.ValidationError(error_message) raise forms.ValidationError(error_message)
@ -261,9 +240,7 @@ class AccessListForm(NetBoxModelForm):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# Set assigned object # Set assigned object
self.instance.assigned_object = ( self.instance.assigned_object = (
self.cleaned_data.get("device") self.cleaned_data.get("device") or self.cleaned_data.get("virtual_chassis") or self.cleaned_data.get("virtual_machine")
or self.cleaned_data.get("virtual_chassis")
or self.cleaned_data.get("virtual_machine")
) )
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@ -324,7 +301,6 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
comments = CommentField() comments = CommentField()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Initialize helper selectors # Initialize helper selectors
instance = kwargs.get("instance") instance = kwargs.get("instance")
initial = kwargs.get("initial", {}).copy() initial = kwargs.get("initial", {}).copy()
@ -376,15 +352,15 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
# Check if both interface and vminterface are set. # Check if both interface and vminterface are set.
if interface and vminterface: if interface and vminterface:
error_too_many_interfaces = "Access Lists must be assigned to one type of interface at a time (VM interface or physical interface)" error_too_many_interfaces = (
"Access Lists must be assigned to one type of interface at a time (VM interface or physical interface)"
)
error_message |= { error_message |= {
"interface": [error_too_many_interfaces], "interface": [error_too_many_interfaces],
"vminterface": [error_too_many_interfaces], "vminterface": [error_too_many_interfaces],
} }
elif not (interface or vminterface): elif not (interface or vminterface):
error_no_interface = ( error_no_interface = "An Access List assignment but specify an Interface or VM Interface."
"An Access List assignment but specify an Interface or VM Interface."
)
error_message |= { error_message |= {
"interface": [error_no_interface], "interface": [error_no_interface],
"vminterface": [error_no_interface], "vminterface": [error_no_interface],
@ -410,9 +386,7 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
# 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.
if access_list_host != host: if access_list_host != host:
error_acl_not_assigned_to_host = ( error_acl_not_assigned_to_host = "Access List not present on selected host."
"Access List not present on selected host."
)
error_message |= { error_message |= {
"access_list": [error_acl_not_assigned_to_host], "access_list": [error_acl_not_assigned_to_host],
assigned_object_type: [error_acl_not_assigned_to_host], assigned_object_type: [error_acl_not_assigned_to_host],
@ -437,9 +411,7 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm):
assigned_object_type=assigned_object_type_id, assigned_object_type=assigned_object_type_id,
direction=direction, direction=direction,
).exists(): ).exists():
error_interface_already_assigned = ( error_interface_already_assigned = "Interfaces can only have 1 Access List assigned in each direction."
"Interfaces can only have 1 Access List assigned in each direction."
)
error_message |= { error_message |= {
"direction": [error_interface_already_assigned], "direction": [error_interface_already_assigned],
assigned_object_type: [error_interface_already_assigned], assigned_object_type: [error_interface_already_assigned],

View File

@ -3,6 +3,7 @@ from netbox.graphql.fields import ObjectField, ObjectListField
from .types import * from .types import *
class Query(ObjectType): class Query(ObjectType):
""" """
Defines the queries available to this plugin via the graphql api. Defines the queries available to this plugin via the graphql api.

View File

@ -72,4 +72,3 @@ class ACLStandardRuleType(NetBoxObjectType):
model = models.ACLStandardRule model = models.ACLStandardRule
fields = "__all__" fields = "__all__"
filterset_class = filtersets.ACLStandardRuleFilterSet filterset_class = filtersets.ACLStandardRuleFilterSet

View File

@ -282,7 +282,9 @@ class Migration(migrations.Migration):
}, },
), ),
# migrations.AddConstraint( # migrations.AddConstraint(
# model_name='accesslist', # model_name="accesslist",
# constraint=models.UniqueConstraint(fields=('assigned_object_type', 'assigned_object_id'), name='accesslist_assigned_object'), # constraint=models.UniqueConstraint(
# fields=("assigned_object_type", "assigned_object_id"), name="accesslist_assigned_object"
# ),
# ), # ),
] ]

View File

@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("contenttypes", "0002_remove_content_type_name"), ("contenttypes", "0002_remove_content_type_name"),
("netbox_acls", "0001_initial"), ("netbox_acls", "0001_initial"),

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("netbox_acls", "0002_alter_accesslist_options_and_more"), ("netbox_acls", "0002_alter_accesslist_options_and_more"),
] ]

View File

@ -1,4 +1,4 @@
from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.urls import reverse from django.urls import reverse
from rest_framework import status from rest_framework import status

View File

@ -47,7 +47,11 @@ urlpatterns = (
views.ACLInterfaceAssignmentEditView.as_view(), views.ACLInterfaceAssignmentEditView.as_view(),
name="aclinterfaceassignment_add", name="aclinterfaceassignment_add",
), ),
# path('interface-assignments/edit/', views.ACLInterfaceAssignmentBulkEditView.as_view(), name='aclinterfaceassignment_bulk_edit'), # path(
# "interface-assignments/edit/",
# views.ACLInterfaceAssignmentBulkEditView.as_view(),
# name="aclinterfaceassignment_bulk_edit"
# ),
path( path(
"interface-assignments/delete/", "interface-assignments/delete/",
views.ACLInterfaceAssignmentBulkDeleteView.as_view(), views.ACLInterfaceAssignmentBulkDeleteView.as_view(),

View File

@ -1 +1 @@
__version__ = "1.2.2" __version__ = "1.3.0"

View File

@ -50,7 +50,8 @@ class AccessListView(generic.ObjectView):
def get_extra_context(self, request, instance): def get_extra_context(self, request, instance):
""" """
Depending on the Access List type, the list view will return the required ACL Rule using the previous defined tables in tables.py. Depending on the Access List type, the list view will return
the required ACL Rule using the previous defined tables in tables.py.
""" """
if instance.type == choices.ACLTypeChoices.TYPE_EXTENDED: if instance.type == choices.ACLTypeChoices.TYPE_EXTENDED:
@ -227,8 +228,7 @@ class ACLInterfaceAssignmentEditView(generic.ObjectEditView):
""" """
return { return {
"access_list": request.GET.get("access_list") "access_list": request.GET.get("access_list") or request.POST.get("access_list"),
or request.POST.get("access_list"),
"direction": request.GET.get("direction") or request.POST.get("direction"), "direction": request.GET.get("direction") or request.POST.get("direction"),
} }
@ -360,8 +360,7 @@ class ACLStandardRuleEditView(generic.ObjectEditView):
""" """
return { return {
"access_list": request.GET.get("access_list") "access_list": request.GET.get("access_list") or request.POST.get("access_list"),
or request.POST.get("access_list"),
} }
@ -443,8 +442,7 @@ class ACLExtendedRuleEditView(generic.ObjectEditView):
""" """
return { return {
"access_list": request.GET.get("access_list") "access_list": request.GET.get("access_list") or request.POST.get("access_list"),
or request.POST.get("access_list"),
} }

23
pyproject.toml Normal file
View File

@ -0,0 +1,23 @@
[tool.black]
line-length = 140
[tool.isort]
profile = "black"
include_trailing_comma = true
multi_line_output = 3
[tool.pylint]
max-line-length = 140
[tool.pyright]
include = ["netbox_secrets"]
exclude = [
"**/node_modules",
"**/__pycache__",
]
reportMissingImports = true
reportMissingTypeStubs = false
[tool.ruff]
line-length = 140

View File

@ -9,7 +9,7 @@ from setuptools import find_packages, setup
script_dir = os.path.abspath(os.path.dirname(__file__)) script_dir = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(script_dir, "README.md"), encoding="utf-8") as fh: with open(os.path.join(script_dir, "README.md"), encoding="utf-8") as fh:
long_description = fh.read() long_description = fh.read().replace("(docs/img/", "(https://raw.githubusercontent.com/ryanmerolle/netbox-acls/release/docs/img/")
def read(relative_path): def read(relative_path):
@ -43,7 +43,18 @@ setup(
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
classifiers=[ classifiers=[
"Framework :: Django", "Development Status :: 5 - Production/Stable",
"Natural Language :: English",
"Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Intended Audience :: System Administrators",
"Intended Audience :: Telecommunications Industry",
"Framework :: Django",
"Topic :: System :: Networking",
"Topic :: Internet",
], ],
) )