From e43b5e41f1adff738cce7ac08a592ed8236bbd18 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Thu, 26 Jan 2023 06:31:05 +0530 Subject: [PATCH 01/12] Added testing framework (#121) * added api test case for access list * initial testing framework --- .github/linters/.flake8 | 3 - .github/linters/.isort.cfg | 8 -- .github/linters/.jscpd.json | 3 - .github/workflows/ci.yml | 46 ++++++++++ .github/workflows/super-linter.yml | 30 ------- .jscpd.json | 4 + .pre-commit-config.yaml | 2 + Dockerfile | 9 ++ MANIFEST.in | 1 - configuration/configuration.py | 99 ++++++++++++++++++++ configuration/logging.py | 11 +++ configuration/plugins.py | 13 +++ docker-compose.yml | 29 ++++++ env/netbox.env | 23 +++++ env/postgres.env | 3 + env/redis.env | 1 + netbox_acls/migrations/0003_netbox_acls.py | 16 +++- netbox_acls/tests/__init__.py | 0 netbox_acls/tests/test_api.py | 100 +++++++++++++++++++++ netbox_acls/urls.py | 2 +- setup.py | 5 +- test.sh | 38 ++++++++ 22 files changed, 394 insertions(+), 52 deletions(-) delete mode 100644 .github/linters/.flake8 delete mode 100644 .github/linters/.isort.cfg delete mode 100644 .github/linters/.jscpd.json create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/super-linter.yml create mode 100644 .jscpd.json create mode 100644 Dockerfile create mode 100644 configuration/configuration.py create mode 100644 configuration/logging.py create mode 100644 configuration/plugins.py create mode 100644 docker-compose.yml create mode 100644 env/netbox.env create mode 100644 env/postgres.env create mode 100644 env/redis.env create mode 100644 netbox_acls/tests/__init__.py create mode 100644 netbox_acls/tests/test_api.py create mode 100755 test.sh diff --git a/.github/linters/.flake8 b/.github/linters/.flake8 deleted file mode 100644 index 55e13d2..0000000 --- a/.github/linters/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 160 -extend-ignore = E203 diff --git a/.github/linters/.isort.cfg b/.github/linters/.isort.cfg deleted file mode 100644 index a9a8d64..0000000 --- a/.github/linters/.isort.cfg +++ /dev/null @@ -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 diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json deleted file mode 100644 index a474b30..0000000 --- a/.github/linters/.jscpd.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "threshold": 10 -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4b580b7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + pull_request: + +# This ensures that previous jobs for the workflow are canceled when the ref is +# updated. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + run-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + # Full git history is needed to get a proper list of changed files within `super-linter` + fetch-depth: 0 + + - name: Lint Code Base + uses: github/super-linter/slim@v4 + env: + DEFAULT_BRANCH: dev + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SUPPRESS_POSSUM: true + LINTER_RULES_PATH: / + VALIDATE_ALL_CODEBASE: false + VALIDATE_DOCKERFILE: false + VALIDATE_JSCPD: true + FILTER_REGEX_EXCLUDE: (.*/)?(configuration/.*) + + test: + runs-on: ubuntu-latest + name: Runs plugin tests + needs: run-lint + steps: + - id: git-checkout + name: Checkout + uses: actions/checkout@v3 + + - id: docker-test + name: Test the image + run: ./test.sh snapshot diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml deleted file mode 100644 index 49aa1ee..0000000 --- a/.github/workflows/super-linter.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -# This workflow executes several linters on changed files based on languages used in your code base whenever -# you push a code or open a pull request. -# -# You can adjust the behavior by modifying this file. -# For more information, see: -# https://github.com/github/super-linter -name: Lint Code Base - -on: - push: - branches: ["dev"] - pull_request: - branches: ["dev"] -jobs: - run-lint: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v3 - with: - # Full git history is needed to get a proper list of changed files within `super-linter` - fetch-depth: 0 - - - name: Lint Code Base - uses: github/super-linter/slim@v4 - env: - VALIDATE_ALL_CODEBASE: false - DEFAULT_BRANCH: "dev" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.jscpd.json b/.jscpd.json new file mode 100644 index 0000000..338acfe --- /dev/null +++ b/.jscpd.json @@ -0,0 +1,4 @@ +{ + "threshold": 10, + "ignore": ["**/tests/**"] +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f31f9d..9ebc05c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,8 @@ repos: - id: debug-statements - id: end-of-file-fixer - id: name-tests-test + args: + - "--django" - id: requirements-txt-fixer - id: trailing-whitespace - repo: https://github.com/PyCQA/isort diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..72b0fcd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +ARG NETBOX_VARIANT=v3.4 + +FROM netboxcommunity/netbox:${NETBOX_VARIANT} + +RUN mkdir -pv /plugins/netbox-acls +COPY . /plugins/netbox-acls + +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 diff --git a/MANIFEST.in b/MANIFEST.in index beb736b..35c0df2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,3 @@ include README.md include LICENSE recursive-include netbox_acls/templates * -recursive-include netbox_acls/static * diff --git a/configuration/configuration.py b/configuration/configuration.py new file mode 100644 index 0000000..1ebd734 --- /dev/null +++ b/configuration/configuration.py @@ -0,0 +1,99 @@ +#### +## We recommend to not edit this file. +## Create separate files to overwrite the settings. +## See `extra.py` as an example. +#### + +from os import environ +from os.path import abspath, dirname + +# For reference see https://netbox.readthedocs.io/en/stable/configuration/ +# Based on https://github.com/netbox-community/netbox/blob/master/netbox/netbox/configuration.example.py + +# Read secret from file +def _read_secret(secret_name, default=None): + try: + f = open("/run/secrets/" + secret_name, encoding="utf-8") + except OSError: + return default + else: + with f: + return f.readline().strip() + + +_BASE_DIR = dirname(dirname(abspath(__file__))) + +######################### +# # +# Required settings # +# # +######################### + +# This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write +# 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'] +ALLOWED_HOSTS = environ.get("ALLOWED_HOSTS", "*").split(" ") + +# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters: +# https://docs.djangoproject.com/en/stable/ref/settings/#databases +DATABASE = { + "NAME": environ.get("DB_NAME", "netbox"), # Database name + "USER": environ.get("DB_USER", ""), # PostgreSQL username + "PASSWORD": _read_secret("db_password", environ.get("DB_PASSWORD", "")), + # PostgreSQL password + "HOST": environ.get("DB_HOST", "localhost"), # Database server + "PORT": environ.get("DB_PORT", ""), # Database port (leave blank for default) + "OPTIONS": {"sslmode": environ.get("DB_SSLMODE", "prefer")}, + # Database connection SSLMODE + "CONN_MAX_AGE": int(environ.get("DB_CONN_MAX_AGE", "300")), + # Max database connection age + "DISABLE_SERVER_SIDE_CURSORS": environ.get( + "DB_DISABLE_SERVER_SIDE_CURSORS", + "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 +# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended +# to use two separate database IDs. +REDIS = { + "tasks": { + "HOST": environ.get("REDIS_HOST", "localhost"), + "PORT": int(environ.get("REDIS_PORT", 6379)), + "PASSWORD": _read_secret("redis_password", environ.get("REDIS_PASSWORD", "")), + "DATABASE": int(environ.get("REDIS_DATABASE", 0)), + "SSL": environ.get("REDIS_SSL", "False").lower() == "true", + "INSECURE_SKIP_TLS_VERIFY": environ.get( + "REDIS_INSECURE_SKIP_TLS_VERIFY", + "False", + ).lower() + == "true", + }, + "caching": { + "HOST": environ.get("REDIS_CACHE_HOST", environ.get("REDIS_HOST", "localhost")), + "PORT": int(environ.get("REDIS_CACHE_PORT", environ.get("REDIS_PORT", 6379))), + "PASSWORD": _read_secret( + "redis_cache_password", + environ.get("REDIS_CACHE_PASSWORD", environ.get("REDIS_PASSWORD", "")), + ), + "DATABASE": int(environ.get("REDIS_CACHE_DATABASE", 1)), + "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", + }, +} + +# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file. +# 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 +# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY +SECRET_KEY = _read_secret("secret_key", environ.get("SECRET_KEY", "")) + +DEVELOPER = True diff --git a/configuration/logging.py b/configuration/logging.py new file mode 100644 index 0000000..86914ae --- /dev/null +++ b/configuration/logging.py @@ -0,0 +1,11 @@ +# Remove first comment(#) on each line to implement this working logging example. +# Add LOGLEVEL environment variable to netbox if you use this example & want a different log level. +from os import environ + +# Set LOGLEVEL in netbox.env or docker-compose.overide.yml to override a logging level of INFO. +LOGLEVEL = environ.get("LOGLEVEL", "INFO") + +LOGGING = { + "version": 1, + "disable_existing_loggers": True, +} diff --git a/configuration/plugins.py b/configuration/plugins.py new file mode 100644 index 0000000..eb7c714 --- /dev/null +++ b/configuration/plugins.py @@ -0,0 +1,13 @@ +# Add your plugins and plugin settings here. +# Of course uncomment this file out. + +# To learn how to build images with your required plugins +# See https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins + +PLUGINS = [ + "netbox_acls", +] + +PLUGINS_CONFIG = { # type: ignore + "netbox_acls": {}, +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0b78731 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +version: '3.4' + +services: + netbox: + build: + dockerfile: Dockerfile + context: . + args: + NETBOX_VARIANT: ${NETBOX_VARIANT} + depends_on: + - postgres + - redis + env_file: env/netbox.env + volumes: + - ./configuration:/etc/netbox/config:z,ro + + # postgres + postgres: + image: postgres:14-alpine + env_file: env/postgres.env + + # redis + redis: + image: redis:6-alpine + command: + - sh + - -c # this is to evaluate the $REDIS_PASSWORD from the env + - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose + env_file: env/redis.env diff --git a/env/netbox.env b/env/netbox.env new file mode 100644 index 0000000..ee71174 --- /dev/null +++ b/env/netbox.env @@ -0,0 +1,23 @@ +ALLOWED_HOSTS=* +CORS_ORIGIN_ALLOW_ALL=true +DB_HOST=postgres +DB_NAME=netbox +DB_PASSWORD=J5brHrAXFLQSif0K +DB_USER=netbox +DEBUG=true +ENFORCE_GLOBAL_UNIQUE=true +LOGIN_REQUIRED=false +GRAPHQL_ENABLED=true +MAX_PAGE_SIZE=1000 +MEDIA_ROOT=/opt/netbox/netbox/media +REDIS_DATABASE=0 +REDIS_HOST=redis +REDIS_INSECURE_SKIP_TLS_VERIFY=false +REDIS_PASSWORD=H733Kdjndks81 +SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj +SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 +SUPERUSER_EMAIL=admin@example.com +SUPERUSER_NAME=admin +SUPERUSER_PASSWORD=admin +STARTUP_SCRIPTS=false +WEBHOOKS_ENABLED=true diff --git a/env/postgres.env b/env/postgres.env new file mode 100644 index 0000000..bb7b53c --- /dev/null +++ b/env/postgres.env @@ -0,0 +1,3 @@ +POSTGRES_DB=netbox +POSTGRES_PASSWORD=J5brHrAXFLQSif0K +POSTGRES_USER=netbox diff --git a/env/redis.env b/env/redis.env new file mode 100644 index 0000000..44a1987 --- /dev/null +++ b/env/redis.env @@ -0,0 +1 @@ +REDIS_PASSWORD=H733Kdjndks81 diff --git a/netbox_acls/migrations/0003_netbox_acls.py b/netbox_acls/migrations/0003_netbox_acls.py index ccfcfbe..562e0f3 100644 --- a/netbox_acls/migrations/0003_netbox_acls.py +++ b/netbox_acls/migrations/0003_netbox_acls.py @@ -15,28 +15,36 @@ class Migration(migrations.Migration): model_name="accesslist", name="custom_field_data", field=models.JSONField( - blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder + blank=True, + default=dict, + encoder=utilities.json.CustomFieldJSONEncoder, ), ), migrations.AlterField( model_name="aclextendedrule", name="custom_field_data", field=models.JSONField( - blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder + blank=True, + default=dict, + encoder=utilities.json.CustomFieldJSONEncoder, ), ), migrations.AlterField( model_name="aclinterfaceassignment", name="custom_field_data", field=models.JSONField( - blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder + blank=True, + default=dict, + encoder=utilities.json.CustomFieldJSONEncoder, ), ), migrations.AlterField( model_name="aclstandardrule", name="custom_field_data", field=models.JSONField( - blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder + blank=True, + default=dict, + encoder=utilities.json.CustomFieldJSONEncoder, ), ), ] diff --git a/netbox_acls/tests/__init__.py b/netbox_acls/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/netbox_acls/tests/test_api.py b/netbox_acls/tests/test_api.py new file mode 100644 index 0000000..f01a687 --- /dev/null +++ b/netbox_acls/tests/test_api.py @@ -0,0 +1,100 @@ +from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site +from django.contrib.contenttypes.models import ContentType +from django.urls import reverse +from netbox_acls.choices import * +from netbox_acls.models import * +from rest_framework import status +from utilities.testing import APITestCase, APIViewTestCases + + +class AppTest(APITestCase): + def test_root(self): + url = reverse("plugins-api:netbox_acls-api:api-root") + response = self.client.get(f"{url}?format=api", **self.header) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + +class ACLTestCase( + APIViewTestCases.GetObjectViewTestCase, + APIViewTestCases.ListObjectsViewTestCase, + APIViewTestCases.CreateObjectViewTestCase, + APIViewTestCases.UpdateObjectViewTestCase, + APIViewTestCases.DeleteObjectViewTestCase, +): + """Test the AccessList Test""" + + model = AccessList + view_namespace = "plugins-api:netbox_acls" + brief_fields = ["display", "id", "name", "url"] + + @classmethod + def setUpTestData(cls): + site = Site.objects.create(name="Site 1", slug="site-1") + manufacturer = Manufacturer.objects.create( + name="Manufacturer 1", + slug="manufacturer-1", + ) + devicetype = DeviceType.objects.create( + manufacturer=manufacturer, + model="Device Type 1", + ) + devicerole = DeviceRole.objects.create( + name="Device Role 1", + slug="device-role-1", + ) + device = Device.objects.create( + name="Device 1", + site=site, + device_type=devicetype, + device_role=devicerole, + ) + + access_lists = ( + AccessList( + name="testacl1", + assigned_object_type=ContentType.objects.get_for_model(Device), + assigned_object_id=device.id, + type=ACLTypeChoices.TYPE_STANDARD, + default_action=ACLActionChoices.ACTION_DENY, + ), + AccessList( + name="testacl2", + assigned_object_type=ContentType.objects.get_for_model(Device), + assigned_object_id=device.id, + type=ACLTypeChoices.TYPE_STANDARD, + default_action=ACLActionChoices.ACTION_DENY, + ), + AccessList( + name="testacl3", + assigned_object_type=ContentType.objects.get_for_model(Device), + assigned_object_id=device.id, + type=ACLTypeChoices.TYPE_STANDARD, + default_action=ACLActionChoices.ACTION_DENY, + ), + ) + AccessList.objects.bulk_create(access_lists) + + cls.create_data = [ + { + "name": "testacl4", + "assigned_object_type": "dcim.device", + "assigned_object_id": device.id, + "type": ACLTypeChoices.TYPE_STANDARD, + "default_action": ACLActionChoices.ACTION_DENY, + }, + { + "name": "testacl5", + "assigned_object_type": "dcim.device", + "assigned_object_id": device.id, + "type": ACLTypeChoices.TYPE_EXTENDED, + "default_action": ACLActionChoices.ACTION_DENY, + }, + { + "name": "testacl6", + "assigned_object_type": "dcim.device", + "assigned_object_id": device.id, + "type": ACLTypeChoices.TYPE_STANDARD, + "default_action": ACLActionChoices.ACTION_DENY, + }, + ] diff --git a/netbox_acls/urls.py b/netbox_acls/urls.py index c5f07a1..5c70579 100644 --- a/netbox_acls/urls.py +++ b/netbox_acls/urls.py @@ -5,7 +5,7 @@ Map Views to URLs. from django.urls import include, path from utilities.urls import get_model_urls -from . import models, views +from . import views urlpatterns = ( # Access Lists diff --git a/setup.py b/setup.py index 1b55f67..50d4055 100644 --- a/setup.py +++ b/setup.py @@ -3,12 +3,13 @@ import os.path from setuptools import find_packages, setup -with open("README.md", encoding="utf-8") as fh: +here = os.path.abspath(os.path.dirname(__file__)) + +with open(os.path.join(here, "README.md"), encoding="utf-8") as fh: long_description = fh.read() def read(rel_path): - here = os.path.abspath(os.path.dirname(__file__)) with codecs.open(os.path.join(here, rel_path), "r") as fp: return fp.read() diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..5fc2e06 --- /dev/null +++ b/test.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# 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 +set -e + +# NETBOX_VARIANT is used by `Dockerfile` to determine the tag +NETBOX_VARIANT="${1-latest}" + +# The docker compose command to use +doco="docker compose --file docker-compose.yml" + +test_netbox_unit_tests() { + echo "⏱ Running NetBox Unit Tests" + $doco run --rm netbox python manage.py makemigrations netbox_acls --check + $doco run --rm netbox python manage.py test netbox_acls +} + +test_cleanup() { + echo "💣 Cleaning Up" + $doco down -v + $doco rm -fsv + docker image rm docker.io/library/netbox-acls-netbox || echo '' +} + +export NETBOX_VARIANT=${NETBOX_VARIANT} + +echo "🐳🐳🐳 Start testing '${NETBOX_VARIANT}'" + +# Make sure the cleanup script is executed +trap test_cleanup EXIT ERR +test_netbox_unit_tests + +echo "🐳🐳🐳 Done testing '${NETBOX_VARIANT}'" From 83e9915a11b9a223668b89d848166a71f20ef78a Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Thu, 26 Jan 2023 01:22:02 +0000 Subject: [PATCH 02/12] lint fixes --- .github/workflows/ci.yml | 1 + configuration/configuration.py | 1 + docker-compose.yml | 7 ++++--- netbox_acls/templates/netbox_acls/accesslist.html | 1 + netbox_acls/templates/netbox_acls/aclextendedrule.html | 2 ++ .../templates/netbox_acls/aclinterfaceassignment.html | 1 + netbox_acls/templates/netbox_acls/aclstandardrule.html | 2 ++ netbox_acls/tests/test_api.py | 5 +++-- 8 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b580b7..4adfb50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,4 @@ +--- name: CI on: diff --git a/configuration/configuration.py b/configuration/configuration.py index 1ebd734..353d9d6 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -10,6 +10,7 @@ from os.path import abspath, dirname # For reference see https://netbox.readthedocs.io/en/stable/configuration/ # Based on https://github.com/netbox-community/netbox/blob/master/netbox/netbox/configuration.example.py + # Read secret from file def _read_secret(secret_name, default=None): try: diff --git a/docker-compose.yml b/docker-compose.yml index 0b78731..cc89ee5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,4 @@ +--- version: '3.4' services: @@ -23,7 +24,7 @@ services: redis: image: redis:6-alpine command: - - sh - - -c # this is to evaluate the $REDIS_PASSWORD from the env - - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose + - sh + - -c # this is to evaluate the $REDIS_PASSWORD from the env + - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose env_file: env/redis.env diff --git a/netbox_acls/templates/netbox_acls/accesslist.html b/netbox_acls/templates/netbox_acls/accesslist.html index 86a8083..bd4f003 100644 --- a/netbox_acls/templates/netbox_acls/accesslist.html +++ b/netbox_acls/templates/netbox_acls/accesslist.html @@ -20,6 +20,7 @@
Access List
+ diff --git a/netbox_acls/templates/netbox_acls/aclextendedrule.html b/netbox_acls/templates/netbox_acls/aclextendedrule.html index 47c4ad3..d4abbdf 100644 --- a/netbox_acls/templates/netbox_acls/aclextendedrule.html +++ b/netbox_acls/templates/netbox_acls/aclextendedrule.html @@ -7,6 +7,7 @@
ACL Extended Rule
Access List
Type {{ object.get_type_display }}
+
ACL Extended Rule
Access List @@ -32,6 +33,7 @@
Details
+ diff --git a/netbox_acls/templates/netbox_acls/aclinterfaceassignment.html b/netbox_acls/templates/netbox_acls/aclinterfaceassignment.html index 4cabeb0..606f99e 100644 --- a/netbox_acls/templates/netbox_acls/aclinterfaceassignment.html +++ b/netbox_acls/templates/netbox_acls/aclinterfaceassignment.html @@ -8,6 +8,7 @@
ACL Interface Assignment
Details
Remark {{ object.get_remark_display|placeholder }}
+
Host
Host diff --git a/netbox_acls/templates/netbox_acls/aclstandardrule.html b/netbox_acls/templates/netbox_acls/aclstandardrule.html index 4a83b3c..c8e2562 100644 --- a/netbox_acls/templates/netbox_acls/aclstandardrule.html +++ b/netbox_acls/templates/netbox_acls/aclstandardrule.html @@ -7,6 +7,7 @@
ACL Standard Rule
+
ACL Standard Rule
Access List @@ -32,6 +33,7 @@
Details
+ diff --git a/netbox_acls/tests/test_api.py b/netbox_acls/tests/test_api.py index f01a687..afbd4fa 100644 --- a/netbox_acls/tests/test_api.py +++ b/netbox_acls/tests/test_api.py @@ -1,11 +1,12 @@ from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site from django.contrib.contenttypes.models import ContentType from django.urls import reverse -from netbox_acls.choices import * -from netbox_acls.models import * from rest_framework import status from utilities.testing import APITestCase, APIViewTestCases +from netbox_acls.choices import * +from netbox_acls.models import * + class AppTest(APITestCase): def test_root(self): From 6b5f1561780a8eab44c271b20e4b3461d8da248b Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Thu, 2 Feb 2023 21:52:06 +0000 Subject: [PATCH 03/12] add a top level TODO for tracking --- TODO | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000..5218b1d --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +- TODO: ACL Form Bubble/ICON Extended/Standard +- TODO: Add an Access List to an Interface Custom Fields after comments - DONE +- TODO: ACL rules, look at last number and increment to next 10 +- TODO: Clone for ACL Interface should include device +- TODO: Inconsistent errors for add/edit (where model is using a generic page) +- TODO: Check Constants across codebase for consistency. +- TODO: Test API, Forms, & Models - https://github.com/k01ek/netbox-bgp/tree/main/netbox_bgp/tests , https://github.com/DanSheps/netbox-secretstore/tree/develop/netbox_secretstore/tests & https://github.com/FlxPeters/netbox-plugin-prometheus-sd/tree/main/netbox_prometheus_sd/tests From e261fde52bd473ac739b0e24d95d92a2b7eb7a4d Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Thu, 2 Feb 2023 21:52:35 +0000 Subject: [PATCH 04/12] add todo tree to devcontainer --- .devcontainer/devcontainer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a3fc26f..2d3b513 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -78,7 +78,10 @@ "extensions": [ "DavidAnson.vscode-markdownlint", "GitHub.codespaces", + "GitHub.copilot", + "GitHub.copilot-labs", "GitHub.vscode-pull-request-github", + "Gruntfuggly.todo-tree", "Tyriar.sort-lines", "aaron-bond.better-comments", "batisteo.vscode-django", From b7828625904d004c843e9557aaee3b2e75f2ee85 Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Thu, 2 Feb 2023 21:53:25 +0000 Subject: [PATCH 05/12] update pre-commit & sourcery --- .devcontainer/requirements-dev.txt | 1 + .pre-commit-config.yaml | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.devcontainer/requirements-dev.txt b/.devcontainer/requirements-dev.txt index 13a832d..d4a50d5 100644 --- a/.devcontainer/requirements-dev.txt +++ b/.devcontainer/requirements-dev.txt @@ -12,3 +12,4 @@ pylint pylint-django wily yapf +sourcery-analytics diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9ebc05c..374cc10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,16 +14,18 @@ repos: - id: requirements-txt-fixer - id: trailing-whitespace - repo: https://github.com/PyCQA/isort - rev: 5.11.4 + rev: 5.12.0 hooks: - id: isort args: - "--profile=black" + exclude: ^.devcontainer/ - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 23.1.0 hooks: - id: black language_version: python3 + exclude: ^.devcontainer/ - repo: https://github.com/asottile/add-trailing-comma rev: v2.4.0 hooks: @@ -34,6 +36,7 @@ repos: rev: 6.0.0 hooks: - id: flake8 + exclude: ^.devcontainer/ - repo: https://github.com/asottile/pyupgrade rev: v3.3.1 hooks: @@ -44,6 +47,12 @@ repos: rev: v1.29.0 hooks: - id: yamllint + - repo: https://github.com/econchick/interrogate + rev: 1.5.0 + hooks: + - id: interrogate + args: [--fail-under=90, --verbose] + exclude: (^.devcontainer/|^netbox_acls/migrations/) #- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs # rev: v1.1.2 # hooks: @@ -61,3 +70,13 @@ repos: # verbose: true # language: python # additional_dependencies: [wily] + #- repo: https://github.com/sourcery-ai/sourcery + # rev: v1.0.4b23 + # hooks: + # - id: sourcery + # # The best way to use Sourcery in a pre-commit hook: + # # * review only changed lines: + # # * omit the summary + # args: + # - --diff=git diff HEAD + # - --no-summary From 141a146b68fa906c01c15bc5586706bc4f00fb3d Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Thu, 2 Feb 2023 21:53:50 +0000 Subject: [PATCH 06/12] refactor setup.py for good form --- setup.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 50d4055..5059faa 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,30 @@ +""" +Configuration for setuptools. +""" import codecs import os.path from setuptools import find_packages, setup -here = os.path.abspath(os.path.dirname(__file__)) +script_dir = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(here, "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() -def read(rel_path): - with codecs.open(os.path.join(here, rel_path), "r") as fp: +def read(relative_path): + """ + Read a file and return its contents. + """ + with codecs.open(os.path.join(script_dir, relative_path), "r") as fp: return fp.read() -def get_version(rel_path): - for line in read(rel_path).splitlines(): +def get_version(relative_path): + """ + Extract the version number from a file without importing it. + """ + for line in read(relative_path).splitlines(): if not line.startswith("__version__"): raise RuntimeError("Unable to find version string.") delim = '"' if '"' in line else "'" From b1999e789fd0e3cfb6f3c8bbcb0ff123a2a39463 Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Thu, 2 Feb 2023 21:54:01 +0000 Subject: [PATCH 07/12] add test option to Makefile --- Makefile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index aa6c2f9..fb3ba16 100644 --- a/Makefile +++ b/Makefile @@ -70,9 +70,13 @@ start: .PHONY: all ## Run all PLUGIN DEV targets all: setup makemigrations migrate collectstatic initializers start -#.PHONY: test -#test: -# ${VENV_PY_PATH} /opt/netbox/netbox/manage.py runserver test ${PLUGIN_NAME} +.PHONY: rebuild ## Run PLUGIN DEV targets to rebuild +rebuild: setup makemigrations migrate collectstatic start + +.PHONY: test +test: setup + ${VENV_PY_PATH} ${NETBOX_MANAGE_PATH}/manage.py makemigrations ${PLUGIN_NAME} --check + ${VENV_PY_PATH} ${NETBOX_MANAGE_PATH}/manage.py test ${PLUGIN_NAME} #relpatch: # $(eval GSTATUS := $(shell git status --porcelain)) From 269354f3c056a1f81ceadfcf124a601bdc2a39a6 Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Fri, 3 Feb 2023 06:53:49 -0800 Subject: [PATCH 08/12] fixed acl assignment form filtering (#125) --- netbox_acls/forms/models.py | 51 ++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/netbox_acls/forms/models.py b/netbox_acls/forms/models.py index 4dc1738..8d04d3a 100644 --- a/netbox_acls/forms/models.py +++ b/netbox_acls/forms/models.py @@ -65,19 +65,20 @@ class AccessListForm(NetBoxModelForm): region = DynamicModelChoiceField( queryset=Region.objects.all(), required=False, + initial_params={ + "sites": "$site", + }, ) site_group = DynamicModelChoiceField( queryset=SiteGroup.objects.all(), required=False, label="Site Group", + initial_params={"sites": "$site"}, ) site = DynamicModelChoiceField( queryset=Site.objects.all(), required=False, - query_params={ - "region_id": "$region", - "group_id": "$site_group", - }, + query_params={"region_id": "$region", "group_id": "$site_group"}, ) device = DynamicModelChoiceField( queryset=Device.objects.all(), @@ -101,32 +102,24 @@ class AccessListForm(NetBoxModelForm): queryset=ClusterType.objects.all(), required=False, ) - cluster_group = DynamicModelChoiceField( queryset=ClusterGroup.objects.all(), required=False, - query_params={ - "type_id": "$cluster_type", - }, + query_params={"type_id": "$cluster_type"}, ) - cluster = DynamicModelChoiceField( queryset=Cluster.objects.all(), required=False, - query_params={ - "type_id": "$cluster_type", - "group_id": "$cluster_group", - }, + query_params={"type_id": "$cluster_type", "group_id": "$cluster_group"}, ) virtual_machine = DynamicModelChoiceField( queryset=VirtualMachine.objects.all(), required=False, - label="Virtual Machine", query_params={ + "cluster_id": "$cluster", "cluster_type_id": "$cluster_type", "cluster_group_id": "$cluster_group", - "cluster_id": "$cluster", }, ) @@ -161,14 +154,30 @@ class AccessListForm(NetBoxModelForm): instance = kwargs.get("instance") initial = kwargs.get("initial", {}).copy() if instance: - if type(instance.assigned_object) is Device: + if isinstance(instance.assigned_object, Device): initial["device"] = instance.assigned_object - elif type(instance.assigned_object) is VirtualChassis: - initial["virtual_chassis"] = instance.assigned_object - elif type(instance.assigned_object) is VirtualMachine: - initial["virtual_machine"] = instance.assigned_object - kwargs["initial"] = initial + if instance.assigned_object.site: + initial["site"] = instance.assigned_object.site + if instance.assigned_object.site.group: + initial["site_group"] = instance.assigned_object.site.group + if instance.assigned_object.site.region: + initial["region"] = instance.assigned_object.site.region + elif isinstance(instance.assigned_object, VirtualMachine): + initial["virtual_machine"] = instance.assigned_object + if instance.assigned_object.cluster: + initial["cluster"] = instance.assigned_object.cluster + if instance.assigned_object.cluster.group: + initial[ + "cluster_group" + ] = instance.assigned_object.cluster.group + + if instance.assigned_object.cluster.type: + initial["cluster_type"] = instance.assigned_object.cluster.type + elif isinstance(instance.assigned_object, VirtualChassis): + initial["virtual_chassis"] = instance.assigned_object + + kwargs["initial"] = initial super().__init__(*args, **kwargs) def clean(self): From 1cdc9a49243075ce782fe4d5f739cf3dd89f94ab Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sun, 5 Feb 2023 23:06:56 +0530 Subject: [PATCH 09/12] updated graphql api (#140) --- netbox_acls/graphql/__init__.py | 2 ++ netbox_acls/graphql/schema.py | 21 ++++++++++++++ netbox_acls/{graphql.py => graphql/types.py} | 30 +------------------- netbox_acls/tests/test_api.py | 6 +--- 4 files changed, 25 insertions(+), 34 deletions(-) create mode 100644 netbox_acls/graphql/__init__.py create mode 100644 netbox_acls/graphql/schema.py rename netbox_acls/{graphql.py => graphql/types.py} (73%) diff --git a/netbox_acls/graphql/__init__.py b/netbox_acls/graphql/__init__.py new file mode 100644 index 0000000..dd2a695 --- /dev/null +++ b/netbox_acls/graphql/__init__.py @@ -0,0 +1,2 @@ +from .schema import * +from .types import * diff --git a/netbox_acls/graphql/schema.py b/netbox_acls/graphql/schema.py new file mode 100644 index 0000000..cdaa8a2 --- /dev/null +++ b/netbox_acls/graphql/schema.py @@ -0,0 +1,21 @@ +from graphene import ObjectType +from netbox.graphql.fields import ObjectField, ObjectListField + +from .types import * + +class Query(ObjectType): + """ + Defines the queries available to this plugin via the graphql api. + """ + + access_list = ObjectField(AccessListType) + access_list_list = ObjectListField(AccessListType) + + acl_extended_rule = ObjectField(ACLExtendedRuleType) + acl_extended_rule_list = ObjectListField(ACLExtendedRuleType) + + acl_standard_rule = ObjectField(ACLStandardRuleType) + acl_standard_rule_list = ObjectListField(ACLStandardRuleType) + + +schema = Query diff --git a/netbox_acls/graphql.py b/netbox_acls/graphql/types.py similarity index 73% rename from netbox_acls/graphql.py rename to netbox_acls/graphql/types.py index 8de0dd2..9e05942 100644 --- a/netbox_acls/graphql.py +++ b/netbox_acls/graphql/types.py @@ -2,11 +2,9 @@ Define the object types and queries availble via the graphql api. """ -from graphene import ObjectType -from netbox.graphql.fields import ObjectField, ObjectListField from netbox.graphql.types import NetBoxObjectType -from . import filtersets, models +from .. import filtersets, models __all__ = ( "AccessListType", @@ -15,10 +13,6 @@ __all__ = ( "ACLStandardRuleType", ) -# -# Object types -# - class AccessListType(NetBoxObjectType): """ @@ -79,25 +73,3 @@ class ACLStandardRuleType(NetBoxObjectType): fields = "__all__" filterset_class = filtersets.ACLStandardRuleFilterSet - -# -# Queries -# - - -class Query(ObjectType): - """ - Defines the queries availible to this plugin via the graphql api. - """ - - access_list = ObjectField(AccessListType) - access_list_list = ObjectListField(AccessListType) - - acl_extended_rule = ObjectField(ACLExtendedRuleType) - acl_extended_rule_list = ObjectListField(ACLExtendedRuleType) - - acl_standard_rule = ObjectField(ACLStandardRuleType) - acl_standard_rule_list = ObjectListField(ACLStandardRuleType) - - -schema = Query diff --git a/netbox_acls/tests/test_api.py b/netbox_acls/tests/test_api.py index afbd4fa..77559e6 100644 --- a/netbox_acls/tests/test_api.py +++ b/netbox_acls/tests/test_api.py @@ -17,11 +17,7 @@ class AppTest(APITestCase): class ACLTestCase( - APIViewTestCases.GetObjectViewTestCase, - APIViewTestCases.ListObjectsViewTestCase, - APIViewTestCases.CreateObjectViewTestCase, - APIViewTestCases.UpdateObjectViewTestCase, - APIViewTestCases.DeleteObjectViewTestCase, + APIViewTestCases.APIViewTestCase, ): """Test the AccessList Test""" From fa38ef4df2d1bb0ef9452819c6a8b4b176a6ae3b Mon Sep 17 00:00:00 2001 From: Abhimanyu Saharan Date: Sat, 22 Apr 2023 04:44:17 -0700 Subject: [PATCH 10/12] removed extra gfk validation (#144) --- netbox_acls/api/serializers.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/netbox_acls/api/serializers.py b/netbox_acls/api/serializers.py index b84a0bd..2dc4d04 100644 --- a/netbox_acls/api/serializers.py +++ b/netbox_acls/api/serializers.py @@ -98,21 +98,6 @@ class AccessListSerializer(NetBoxModelSerializer): """ error_message = {} - # Check that the GFK object is valid. - if "assigned_object_type" in data and "assigned_object_id" in data: - # TODO: This can removed after https://github.com/netbox-community/netbox/issues/10221 is fixed. - try: - assigned_object = data[ # noqa: F841 - "assigned_object_type" - ].get_object_for_this_type( - id=data["assigned_object_id"], - ) - except ObjectDoesNotExist: - # Sets a standard error message for invalid GFK - error_message_invalid_gfk = f"Invalid assigned_object {data['assigned_object_type']} ID {data['assigned_object_id']}" - error_message["assigned_object_type"] = [error_message_invalid_gfk] - error_message["assigned_object_id"] = [error_message_invalid_gfk] - # Check if Access List has no existing rules before change the Access List's type. if ( self.instance @@ -182,21 +167,6 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer): error_message = {} acl_host = data["access_list"].assigned_object - # Check that the GFK object is valid. - if "assigned_object_type" in data and "assigned_object_id" in data: - # TODO: This can removed after https://github.com/netbox-community/netbox/issues/10221 is fixed. - try: - assigned_object = data[ # noqa: F841 - "assigned_object_type" - ].get_object_for_this_type( - id=data["assigned_object_id"], - ) - except ObjectDoesNotExist: - # Sets a standard error message for invalid GFK - error_message_invalid_gfk = f"Invalid assigned_object {data['assigned_object_type']} ID {data['assigned_object_id']}" - error_message["assigned_object_type"] = [error_message_invalid_gfk] - error_message["assigned_object_id"] = [error_message_invalid_gfk] - if data["assigned_object_type"].model == "interface": interface_host = ( data["assigned_object_type"] From 108fbb6751fc0dea209043d56ed93a708632af1d Mon Sep 17 00:00:00 2001 From: Ryan Merolle Date: Fri, 23 Jun 2023 14:41:42 -0400 Subject: [PATCH 11/12] NetBox 3.5 Support (#148) * NetBox 3.5 updates --- .devcontainer/Dockerfile-plugin_dev | 4 +- .devcontainer/configuration/configuration.py | 9 +-- .devcontainer/devcontainer.json | 12 ++- .devcontainer/env/netbox.env | 2 +- .devcontainer/requirements-dev.txt | 3 +- .flake8 | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 4 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .isort.cfg | 8 -- .jscpd.json | 4 +- .pre-commit-config.yaml | 14 ++-- Dockerfile | 6 +- README.md | 1 + configuration/configuration.py | 5 +- env/netbox.env | 2 +- netbox_acls/__init__.py | 4 +- netbox_acls/api/serializers.py | 37 +++------ netbox_acls/constants.py | 3 +- netbox_acls/forms/bulk_edit.py | 4 +- netbox_acls/forms/filtersets.py | 4 +- netbox_acls/forms/models.py | 76 ++++++------------- netbox_acls/graphql/schema.py | 1 + netbox_acls/graphql/types.py | 1 - netbox_acls/migrations/0001_initial.py | 6 +- .../0002_alter_accesslist_options_and_more.py | 1 - netbox_acls/migrations/0003_netbox_acls.py | 1 - netbox_acls/tests/test_api.py | 2 +- netbox_acls/urls.py | 6 +- netbox_acls/version.py | 2 +- netbox_acls/views.py | 12 ++- pyproject.toml | 23 ++++++ setup.py | 15 +++- 32 files changed, 133 insertions(+), 143 deletions(-) delete mode 100644 .isort.cfg create mode 100644 pyproject.toml diff --git a/.devcontainer/Dockerfile-plugin_dev b/.devcontainer/Dockerfile-plugin_dev index ea10664..633f053 100644 --- a/.devcontainer/Dockerfile-plugin_dev +++ b/.devcontainer/Dockerfile-plugin_dev @@ -1,8 +1,8 @@ -ARG NETBOX_VARIANT=v3.4 +ARG NETBOX_VARIANT=v3.5 FROM netboxcommunity/netbox:${NETBOX_VARIANT} -ARG NETBOX_INITIALIZERS_VARIANT=3.4.* +ARG NETBOX_INITIALIZERS_VARIANT=3.5.* ARG DEBIAN_FRONTEND=noninteractive diff --git a/.devcontainer/configuration/configuration.py b/.devcontainer/configuration/configuration.py index 004d725..0a41530 100644 --- a/.devcontainer/configuration/configuration.py +++ b/.devcontainer/configuration/configuration.py @@ -7,7 +7,7 @@ from os.path import abspath, dirname, join # Read secret from file def _read_secret(secret_name, default=None): try: - f = open("/run/secrets/" + secret_name, encoding="utf-8") + f = open(f"/run/secrets/{secret_name}", encoding="utf-8") except OSError: return default else: @@ -74,8 +74,7 @@ REDIS = { environ.get("REDIS_CACHE_PASSWORD", environ.get("REDIS_PASSWORD", "")), ), "DATABASE": int(environ.get("REDIS_CACHE_DATABASE", 1)), - "SSL": environ.get("REDIS_CACHE_SSL", environ.get("REDIS_SSL", "False")).lower() - == "true", + "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"), @@ -252,9 +251,7 @@ REMOTE_AUTH_BACKEND = environ.get( "netbox.authentication.RemoteUserBackend", ) REMOTE_AUTH_HEADER = environ.get("REMOTE_AUTH_HEADER", "HTTP_REMOTE_USER") -REMOTE_AUTH_AUTO_CREATE_USER = ( - environ.get("REMOTE_AUTH_AUTO_CREATE_USER", "True").lower() == "true" -) +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(" ")), ) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2d3b513..6453f36 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -78,23 +78,31 @@ "extensions": [ "DavidAnson.vscode-markdownlint", "GitHub.codespaces", - "GitHub.copilot", "GitHub.copilot-labs", "GitHub.vscode-pull-request-github", "Gruntfuggly.todo-tree", "Tyriar.sort-lines", "aaron-bond.better-comments", "batisteo.vscode-django", + "charliermarsh.ruff", "codezombiech.gitignore", "esbenp.prettier-vscode", + "exiasr.hadolint", "formulahendry.auto-rename-tag", "mintlify.document", + "ms-python.isort", + "ms-python.pylint", "ms-python.python", "ms-python.vscode-pylance", + "ms-vscode.makefile-tools", "mutantdino.resourcemonitor", + "oderwat.indent-rainbow", "paulomenezes.duplicated-code", + "redhat.vscode-yaml", "searKing.preview-vscode", - "sourcery.sourcery" + "sourcery.sourcery", + "wholroyd.jinja", + "yzhang.markdown-all-in-one" ] } }, diff --git a/.devcontainer/env/netbox.env b/.devcontainer/env/netbox.env index ba8a54e..13b0f61 100644 --- a/.devcontainer/env/netbox.env +++ b/.devcontainer/env/netbox.env @@ -15,7 +15,7 @@ REDIS_DATABASE=0 REDIS_HOST=redis REDIS_INSECURE_SKIP_TLS_VERIFY=false REDIS_PASSWORD=H733Kdjndks81 -SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj +SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNjaa SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 SUPERUSER_EMAIL=admin@example.com SUPERUSER_NAME=admin diff --git a/.devcontainer/requirements-dev.txt b/.devcontainer/requirements-dev.txt index d4a50d5..80fc9bf 100644 --- a/.devcontainer/requirements-dev.txt +++ b/.devcontainer/requirements-dev.txt @@ -10,6 +10,7 @@ pycodestyle pydocstyle pylint pylint-django +ruff +sourcery-analytics wily yapf -sourcery-analytics diff --git a/.flake8 b/.flake8 index 55e13d2..e599bed 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,3 @@ [flake8] -max-line-length = 160 +max-line-length = 140 extend-ignore = E203 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ac362e9..7008bae 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -23,14 +23,14 @@ body: attributes: label: NetBox access-list plugin version description: What version of the NetBox access-list plugin are you currently running? - placeholder: v1.2.0 + placeholder: v1.3.0 validations: required: true - type: input attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.4.3 + placeholder: v3.5.4 validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index ca31eaf..9f473b7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -15,7 +15,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.4.3 + placeholder: v3.5.4 validations: required: true - type: dropdown diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index a9a8d64..0000000 --- a/.isort.cfg +++ /dev/null @@ -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 diff --git a/.jscpd.json b/.jscpd.json index 338acfe..481edca 100644 --- a/.jscpd.json +++ b/.jscpd.json @@ -1,4 +1,4 @@ { - "threshold": 10, - "ignore": ["**/tests/**"] + "threshold": 10, + "ignore": ["**/migrations/**", "**/tests/**"] } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 374cc10..4580d6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,13 +21,13 @@ repos: - "--profile=black" exclude: ^.devcontainer/ - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black language_version: python3 exclude: ^.devcontainer/ - repo: https://github.com/asottile/add-trailing-comma - rev: v2.4.0 + rev: v2.5.1 hooks: - id: add-trailing-comma args: @@ -38,13 +38,13 @@ repos: - id: flake8 exclude: ^.devcontainer/ - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.7.0 hooks: - id: pyupgrade args: - "--py39-plus" - repo: https://github.com/adrienverge/yamllint - rev: v1.29.0 + rev: v1.32.0 hooks: - id: yamllint - repo: https://github.com/econchick/interrogate @@ -59,9 +59,13 @@ repos: # - id: htmlhint # args: [--config, .htmlhintrc] - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.33.0 + rev: v0.35.0 hooks: - id: markdownlint + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.272 + hooks: + - id: ruff #- repo: local # hooks: # - id: wily diff --git a/Dockerfile b/Dockerfile index 72b0fcd..3b4a257 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -ARG NETBOX_VARIANT=v3.4 +ARG NETBOX_VARIANT=v3.5 FROM netboxcommunity/netbox:${NETBOX_VARIANT} RUN mkdir -pv /plugins/netbox-acls COPY . /plugins/netbox-acls -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 +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 diff --git a/README.md b/README.md index 6af94cf..286a368 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Each Plugin Version listed below has been tested with its corresponding NetBox V | 3.2 | 1.0.1 | | 3.3 | 1.1.0 | | 3.4 | 1.2.2 | +| 3.5 | 1.3.0 | ## Installing diff --git a/configuration/configuration.py b/configuration/configuration.py index 353d9d6..2cf4668 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -14,7 +14,7 @@ from os.path import abspath, dirname # Read secret from file def _read_secret(secret_name, default=None): try: - f = open("/run/secrets/" + secret_name, encoding="utf-8") + f = open(f"/run/secrets/{secret_name}", encoding="utf-8") except OSError: return default else: @@ -81,8 +81,7 @@ REDIS = { environ.get("REDIS_CACHE_PASSWORD", environ.get("REDIS_PASSWORD", "")), ), "DATABASE": int(environ.get("REDIS_CACHE_DATABASE", 1)), - "SSL": environ.get("REDIS_CACHE_SSL", environ.get("REDIS_SSL", "False")).lower() - == "true", + "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"), diff --git a/env/netbox.env b/env/netbox.env index ee71174..8950d9f 100644 --- a/env/netbox.env +++ b/env/netbox.env @@ -14,7 +14,7 @@ REDIS_DATABASE=0 REDIS_HOST=redis REDIS_INSECURE_SKIP_TLS_VERIFY=false REDIS_PASSWORD=H733Kdjndks81 -SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj +SECRET_KEY=r8OwDznj!!dciP9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNjaa SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 SUPERUSER_EMAIL=admin@example.com SUPERUSER_NAME=admin diff --git a/netbox_acls/__init__.py b/netbox_acls/__init__.py index fa5e548..4898b59 100644 --- a/netbox_acls/__init__.py +++ b/netbox_acls/__init__.py @@ -17,8 +17,8 @@ class NetBoxACLsConfig(PluginConfig): version = __version__ description = "Manage simple ACLs in NetBox" base_url = "access-lists" - min_version = "3.4.0" - max_version = "3.4.99" + min_version = "3.5.0" + max_version = "3.5.99" config = NetBoxACLsConfig diff --git a/netbox_acls/api/serializers.py b/netbox_acls/api/serializers.py index 2dc4d04..241134e 100644 --- a/netbox_acls/api/serializers.py +++ b/netbox_acls/api/serializers.py @@ -4,8 +4,7 @@ while Django itself handles the database abstraction. """ from django.contrib.contenttypes.models import ContentType -from django.core.exceptions import ObjectDoesNotExist -from drf_yasg.utils import swagger_serializer_method +from drf_spectacular.utils import extend_schema_field from ipam.api.serializers import NestedPrefixSerializer from netbox.api.fields import ContentTypeField 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. 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. -error_message_action_remark_source_prefix_set = ( - "Action is set to remark, Source Prefix CANNOT be set." -) +error_message_action_remark_source_prefix_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. -error_message_remark_without_action_remark = ( - "CANNOT set remark unless action is set to remark." -) +error_message_remark_without_action_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. error_message_acl_type = "Provided parent Access List is not of right type." @@ -81,7 +76,7 @@ class AccessListSerializer(NetBoxModelSerializer): "rule_count", ) - @swagger_serializer_method(serializer_or_field=serializers.DictField) + @extend_schema_field(serializers.DictField()) def get_assigned_object(self, obj): serializer = get_serializer_for_model( obj.assigned_object, @@ -99,11 +94,7 @@ class AccessListSerializer(NetBoxModelSerializer): error_message = {} # Check if Access List has no existing rules before change the Access List's type. - if ( - self.instance - and self.instance.type != data.get("type") - and self.instance.rule_count > 0 - ): + if self.instance and self.instance.type != data.get("type") and self.instance.rule_count > 0: error_message["type"] = [ "This ACL has ACL rules associated, CANNOT change ACL type.", ] @@ -149,7 +140,7 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer): "last_updated", ) - @swagger_serializer_method(serializer_or_field=serializers.DictField) + @extend_schema_field(serializers.DictField()) def get_assigned_object(self, obj): serializer = get_serializer_for_model( obj.assigned_object, @@ -168,24 +159,14 @@ class ACLInterfaceAssignmentSerializer(NetBoxModelSerializer): acl_host = data["access_list"].assigned_object if data["assigned_object_type"].model == "interface": - interface_host = ( - data["assigned_object_type"] - .get_object_for_this_type(id=data["assigned_object_id"]) - .device - ) + interface_host = data["assigned_object_type"].get_object_for_this_type(id=data["assigned_object_id"]).device elif data["assigned_object_type"].model == "vminterface": - interface_host = ( - data["assigned_object_type"] - .get_object_for_this_type(id=data["assigned_object_id"]) - .virtual_machine - ) + interface_host = data["assigned_object_type"].get_object_for_this_type(id=data["assigned_object_id"]).virtual_machine else: interface_host = None # Check that the associated interface's parent host has the selected ACL defined. if acl_host != interface_host: - error_acl_not_assigned_to_host = ( - "Access List not present on the selected interface's host." - ) + error_acl_not_assigned_to_host = "Access List not present on the selected interface's host." error_message["access_list"] = [error_acl_not_assigned_to_host] error_message["assigned_object_id"] = [error_acl_not_assigned_to_host] diff --git a/netbox_acls/constants.py b/netbox_acls/constants.py index 8d73006..468845c 100644 --- a/netbox_acls/constants.py +++ b/netbox_acls/constants.py @@ -10,6 +10,5 @@ ACL_HOST_ASSIGNMENT_MODELS = Q( ) ACL_INTERFACE_ASSIGNMENT_MODELS = Q( - Q(app_label="dcim", model="interface") - | Q(app_label="virtualization", model="vminterface"), + Q(app_label="dcim", model="interface") | Q(app_label="virtualization", model="vminterface"), ) diff --git a/netbox_acls/forms/bulk_edit.py b/netbox_acls/forms/bulk_edit.py index 81b4dd8..7ac0ab6 100644 --- a/netbox_acls/forms/bulk_edit.py +++ b/netbox_acls/forms/bulk_edit.py @@ -7,11 +7,11 @@ Draft for a possible BulkEditForm, but may not be worth wile. # from django.core.exceptions import ValidationError # from django.utils.safestring import mark_safe # from netbox.forms import NetBoxModelBulkEditForm -# from utilities.forms import ( +# from utilities.forms.utils import add_blank_choice +# from utilities.forms.fields import ( # ChoiceField, # DynamicModelChoiceField, # StaticSelect, -# add_blank_choice, # ) # from virtualization.models import VirtualMachine diff --git a/netbox_acls/forms/filtersets.py b/netbox_acls/forms/filtersets.py index f227e3f..37f20df 100644 --- a/netbox_acls/forms/filtersets.py +++ b/netbox_acls/forms/filtersets.py @@ -6,13 +6,13 @@ from dcim.models import Device, Interface, Region, Site, SiteGroup, VirtualChass from django import forms from ipam.models import Prefix from netbox.forms import NetBoxModelFilterSetForm -from utilities.forms import ( +from utilities.forms.fields import ( ChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField, - add_blank_choice, ) +from utilities.forms.utils import add_blank_choice from virtualization.models import VirtualMachine, VMInterface from ..choices import ( diff --git a/netbox_acls/forms/models.py b/netbox_acls/forms/models.py index 8d04d3a..0c95ef5 100644 --- a/netbox_acls/forms/models.py +++ b/netbox_acls/forms/models.py @@ -8,7 +8,7 @@ from django.contrib.contenttypes.models import ContentType from django.utils.safestring import mark_safe from ipam.models import Prefix from netbox.forms import NetBoxModelForm -from utilities.forms import CommentField, DynamicModelChoiceField +from utilities.forms.fields import CommentField, DynamicModelChoiceField from virtualization.models import ( Cluster, 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 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 -help_text_acl_rule_index = ( - "Determines the order of the rule in the ACL processing. AKA Sequence Number." -) +help_text_acl_rule_index = "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. 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. -error_message_action_remark_source_prefix_set = ( - "Action is set to remark, Source Prefix CANNOT be set." -) +error_message_action_remark_source_prefix_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. -error_message_remark_without_action_remark = ( - "CANNOT set remark unless action is set to remark." -) +error_message_remark_without_action_remark = "CANNOT set remark unless action is set to remark." class AccessListForm(NetBoxModelForm): @@ -149,7 +143,6 @@ class AccessListForm(NetBoxModelForm): } def __init__(self, *args, **kwargs): - # Initialize helper selectors instance = kwargs.get("instance") initial = kwargs.get("initial", {}).copy() @@ -168,9 +161,7 @@ class AccessListForm(NetBoxModelForm): if instance.assigned_object.cluster: initial["cluster"] = instance.assigned_object.cluster if instance.assigned_object.cluster.group: - initial[ - "cluster_group" - ] = instance.assigned_object.cluster.group + initial["cluster_group"] = instance.assigned_object.cluster.group if 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") # 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( - "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. if not device and not virtual_chassis and not virtual_machine: @@ -230,28 +217,20 @@ class AccessListForm(NetBoxModelForm): ).exists() # Check if duplicate entry. - 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." - ) + 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_message |= { host_type: [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. - if ( - acl_type == ACLTypeChoices.TYPE_EXTENDED - and self.instance.aclstandardrules.exists() - ) or ( - acl_type == ACLTypeChoices.TYPE_STANDARD - and self.instance.aclextendedrules.exists() - ): - error_message["type"] = [ - "This ACL has ACL rules associated, CANNOT change ACL type.", - ] + # Check if Access List has no existing rules before change the Access List's type. + if self.instance.pk and ( + (acl_type == ACLTypeChoices.TYPE_EXTENDED and self.instance.aclstandardrules.exists()) + or (acl_type == ACLTypeChoices.TYPE_STANDARD and self.instance.aclextendedrules.exists()) + ): + error_message["type"] = [ + "This ACL has ACL rules associated, CANNOT change ACL type.", + ] if error_message: raise forms.ValidationError(error_message) @@ -261,9 +240,7 @@ class AccessListForm(NetBoxModelForm): def save(self, *args, **kwargs): # Set assigned object self.instance.assigned_object = ( - self.cleaned_data.get("device") - or self.cleaned_data.get("virtual_chassis") - or self.cleaned_data.get("virtual_machine") + self.cleaned_data.get("device") or self.cleaned_data.get("virtual_chassis") or self.cleaned_data.get("virtual_machine") ) return super().save(*args, **kwargs) @@ -324,7 +301,6 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm): comments = CommentField() def __init__(self, *args, **kwargs): - # Initialize helper selectors instance = kwargs.get("instance") initial = kwargs.get("initial", {}).copy() @@ -376,15 +352,15 @@ class ACLInterfaceAssignmentForm(NetBoxModelForm): # Check if both interface and vminterface are set. 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 |= { "interface": [error_too_many_interfaces], "vminterface": [error_too_many_interfaces], } elif not (interface or vminterface): - error_no_interface = ( - "An Access List assignment but specify an Interface or VM Interface." - ) + error_no_interface = "An Access List assignment but specify an Interface or VM Interface." error_message |= { "interface": [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. if access_list_host != host: - error_acl_not_assigned_to_host = ( - "Access List not present on selected host." - ) + error_acl_not_assigned_to_host = "Access List not present on selected host." error_message |= { "access_list": [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, direction=direction, ).exists(): - error_interface_already_assigned = ( - "Interfaces can only have 1 Access List assigned in each direction." - ) + error_interface_already_assigned = "Interfaces can only have 1 Access List assigned in each direction." error_message |= { "direction": [error_interface_already_assigned], assigned_object_type: [error_interface_already_assigned], diff --git a/netbox_acls/graphql/schema.py b/netbox_acls/graphql/schema.py index cdaa8a2..0dc2dd2 100644 --- a/netbox_acls/graphql/schema.py +++ b/netbox_acls/graphql/schema.py @@ -3,6 +3,7 @@ from netbox.graphql.fields import ObjectField, ObjectListField from .types import * + class Query(ObjectType): """ Defines the queries available to this plugin via the graphql api. diff --git a/netbox_acls/graphql/types.py b/netbox_acls/graphql/types.py index 9e05942..f43774c 100644 --- a/netbox_acls/graphql/types.py +++ b/netbox_acls/graphql/types.py @@ -72,4 +72,3 @@ class ACLStandardRuleType(NetBoxObjectType): model = models.ACLStandardRule fields = "__all__" filterset_class = filtersets.ACLStandardRuleFilterSet - diff --git a/netbox_acls/migrations/0001_initial.py b/netbox_acls/migrations/0001_initial.py index 9a530ce..e41a675 100644 --- a/netbox_acls/migrations/0001_initial.py +++ b/netbox_acls/migrations/0001_initial.py @@ -282,7 +282,9 @@ class Migration(migrations.Migration): }, ), # migrations.AddConstraint( - # model_name='accesslist', - # constraint=models.UniqueConstraint(fields=('assigned_object_type', 'assigned_object_id'), name='accesslist_assigned_object'), + # model_name="accesslist", + # constraint=models.UniqueConstraint( + # fields=("assigned_object_type", "assigned_object_id"), name="accesslist_assigned_object" + # ), # ), ] diff --git a/netbox_acls/migrations/0002_alter_accesslist_options_and_more.py b/netbox_acls/migrations/0002_alter_accesslist_options_and_more.py index ea5ab86..902248f 100644 --- a/netbox_acls/migrations/0002_alter_accesslist_options_and_more.py +++ b/netbox_acls/migrations/0002_alter_accesslist_options_and_more.py @@ -6,7 +6,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("netbox_acls", "0001_initial"), diff --git a/netbox_acls/migrations/0003_netbox_acls.py b/netbox_acls/migrations/0003_netbox_acls.py index 562e0f3..0af5aef 100644 --- a/netbox_acls/migrations/0003_netbox_acls.py +++ b/netbox_acls/migrations/0003_netbox_acls.py @@ -5,7 +5,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("netbox_acls", "0002_alter_accesslist_options_and_more"), ] diff --git a/netbox_acls/tests/test_api.py b/netbox_acls/tests/test_api.py index 77559e6..207f1dd 100644 --- a/netbox_acls/tests/test_api.py +++ b/netbox_acls/tests/test_api.py @@ -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.urls import reverse from rest_framework import status diff --git a/netbox_acls/urls.py b/netbox_acls/urls.py index 5c70579..d7624a1 100644 --- a/netbox_acls/urls.py +++ b/netbox_acls/urls.py @@ -47,7 +47,11 @@ urlpatterns = ( views.ACLInterfaceAssignmentEditView.as_view(), 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( "interface-assignments/delete/", views.ACLInterfaceAssignmentBulkDeleteView.as_view(), diff --git a/netbox_acls/version.py b/netbox_acls/version.py index bc86c94..67bc602 100644 --- a/netbox_acls/version.py +++ b/netbox_acls/version.py @@ -1 +1 @@ -__version__ = "1.2.2" +__version__ = "1.3.0" diff --git a/netbox_acls/views.py b/netbox_acls/views.py index d986945..9f1fa81 100644 --- a/netbox_acls/views.py +++ b/netbox_acls/views.py @@ -50,7 +50,8 @@ class AccessListView(generic.ObjectView): 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: @@ -227,8 +228,7 @@ class ACLInterfaceAssignmentEditView(generic.ObjectEditView): """ return { - "access_list": request.GET.get("access_list") - or request.POST.get("access_list"), + "access_list": request.GET.get("access_list") or request.POST.get("access_list"), "direction": request.GET.get("direction") or request.POST.get("direction"), } @@ -360,8 +360,7 @@ class ACLStandardRuleEditView(generic.ObjectEditView): """ return { - "access_list": request.GET.get("access_list") - or request.POST.get("access_list"), + "access_list": request.GET.get("access_list") or request.POST.get("access_list"), } @@ -443,8 +442,7 @@ class ACLExtendedRuleEditView(generic.ObjectEditView): """ return { - "access_list": request.GET.get("access_list") - or request.POST.get("access_list"), + "access_list": request.GET.get("access_list") or request.POST.get("access_list"), } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ab061a4 --- /dev/null +++ b/pyproject.toml @@ -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 diff --git a/setup.py b/setup.py index 5059faa..d3b23d4 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ from setuptools import find_packages, setup script_dir = os.path.abspath(os.path.dirname(__file__)) 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): @@ -43,7 +43,18 @@ setup( include_package_data=True, zip_safe=False, classifiers=[ - "Framework :: Django", + "Development Status :: 5 - Production/Stable", + "Natural Language :: English", + "Programming Language :: Python", "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", ], ) From 67669450c0b757961d1395b7925fe65f45a31034 Mon Sep 17 00:00:00 2001 From: Ryan Merolle Date: Fri, 23 Jun 2023 14:48:01 -0400 Subject: [PATCH 12/12] Update setup.py Closes #141 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index d3b23d4..c8faa97 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,7 @@ setup( classifiers=[ "Development Status :: 5 - Production/Stable", "Natural Language :: English", + "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only",
Details
Remark {{ object.get_remark_display|placeholder }}