From 9d9b8a51d07bdd0e13f5ac7def8865f2f7a93bce Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Fri, 23 Jun 2023 15:42:50 -0400 Subject: [PATCH 01/19] update pypi metadata --- setup.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c8faa97..030dece 100644 --- a/setup.py +++ b/setup.py @@ -38,9 +38,12 @@ def get_version(relative_path): long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/ryanmerolle/netbox-acls", + license="Apache 2.0", install_requires=[], + python_requires=">=3.10", packages=find_packages(), include_package_data=True, + keywords=["netbox", "netbox-plugin"], zip_safe=False, classifiers=[ "Development Status :: 5 - Production/Stable", @@ -49,7 +52,6 @@ def get_version(relative_path): "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", @@ -58,4 +60,8 @@ def get_version(relative_path): "Topic :: System :: Networking", "Topic :: Internet", ], + project_urls={ + "Issues": "https://github.com/ryanmerolle/netbox-acls/issues", + "Source": "https://github.com/ryanmerolle/netbox-acls", + }, ) From b11fe40d020ccd760da8c290da281690de904245 Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Fri, 23 Jun 2023 21:04:39 +0000 Subject: [PATCH 02/19] fix alphanumeric_plus RegexValidator --- netbox_acls/migrations/0004_netbox_acls.py | 25 ++++++++++++++++++++++ netbox_acls/models/access_lists.py | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 netbox_acls/migrations/0004_netbox_acls.py diff --git a/netbox_acls/migrations/0004_netbox_acls.py b/netbox_acls/migrations/0004_netbox_acls.py new file mode 100644 index 0000000..a09db41 --- /dev/null +++ b/netbox_acls/migrations/0004_netbox_acls.py @@ -0,0 +1,25 @@ +# Generated by Django 4.1.9 on 2023-06-23 21:02 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("netbox_acls", "0003_netbox_acls"), + ] + + operations = [ + migrations.AlterField( + model_name="accesslist", + name="name", + field=models.CharField( + max_length=500, + validators=[ + django.core.validators.RegexValidator( + "^[a-zA-Z0-9-_]+$", "Only alphanumeric, hyphens, and underscores characters are allowed." + ) + ], + ), + ), + ] diff --git a/netbox_acls/models/access_lists.py b/netbox_acls/models/access_lists.py index 1feb635..e65f765 100644 --- a/netbox_acls/models/access_lists.py +++ b/netbox_acls/models/access_lists.py @@ -21,7 +21,7 @@ alphanumeric_plus = RegexValidator( - r"^[0-9a-zA-Z,-,_]*$", + r"^[a-zA-Z0-9-_]+$", "Only alphanumeric, hyphens, and underscores characters are allowed.", ) From 2ee95574a996a1b75d0eefa47d77f1d7a150bffa Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Fri, 23 Jun 2023 21:13:34 +0000 Subject: [PATCH 03/19] cleanup devcontainer --- .devcontainer/devcontainer.json | 206 +++++++++++++++----------------- 1 file changed, 96 insertions(+), 110 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6453f36..8b36b28 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,121 +2,107 @@ // https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/python-3-postgres // Update the VARIANT arg in docker-compose.yml to pick a Python version { - "name": "NetBox Plugin Development", - "dockerComposeFile": [ - "docker-compose.yml", - "docker-compose.override.yml" - ], - "service": "netbox", - //"workspaceMount": "source=${localWorkspaceFolder},target=/opt/netbox/netbox/netbox-acls,type=bind,consistency=cached", - "workspaceFolder": "/opt/netbox/netbox/netbox-acls", + "name": "NetBox Plugin Development", + "dockerComposeFile": ["docker-compose.yml", "docker-compose.override.yml"], + "service": "netbox", + //"workspaceMount": "source=${localWorkspaceFolder},target=/opt/netbox/netbox/netbox-acls,type=bind,consistency=cached", + "workspaceFolder": "/opt/netbox/netbox/netbox-acls", - "overrideCommand":false, + "overrideCommand": false, - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // Set *default* container specific settings.json values on container create. - "settings": { - "editor.experimental.stickyScroll.enabled": true, - //"[python]": { - // "editor.codeActionsOnSave": { - // "source.organizeImports": true - // } - //}, - "isort.args": [ - "--profile=black" - ], - "isort.path": "/opt/netbox/venv/bin/isort", - "python.analysis.typeCheckingMode": "strict", - python.Jedi - "python.analysis.extraPaths": [ - "/opt/netbox/netbox" - ], - "python.autoComplete.extraPaths": [ - "/opt/netbox/netbox" - ], - "python.defaultInterpreterPath": "/opt/netbox/venv/bin/python3", - "python.formatting.autopep8Path": "/opt/netbox/venv/bin/autopep8", - "python.formatting.blackPath": "/opt/netbox/venv/bin/black", - "python.formatting.provider": "black", - "python.formatting.yapfPath": "/opt/netbox/venv/bin/yapf", - "python.linting.banditPath": "/opt/netbox/venv/bin/bandit", - "python.linting.enabled": true, - "python.linting.flake8Path": "/opt/netbox/venv/bin/flake8", - "python.linting.flake8Args": [ - "--max-line-length=160", - "--ignore=E203" - ], - "python.linting.mypyPath": "//opt/netbox/venv/bin/mypy", - "python.linting.pycodestylePath": "/opt/netbox/venv/bin/pycodestyle", - "python.linting.pydocstylePath": "/opt/netbox/venv/bin/pydocstyle", - "python.linting.pylintArgs": [ - "--load-plugins", - "pylint_django", - "--errors-only", - "--load-plugins=pylint_django", - "--django-settings-module=/opt/netbox/netbox/netbox/netbox.settings", - "--enable=W0602,W0611,W0612,W0613,W0614" - ], - "python.linting.pylintEnabled": true, - "python.linting.pylintPath": "/opt/netbox/venv/bin/pylint", - "python.linting.lintOnSave": true, - "python.pythonPath": "/opt/netbox/venv/bin/python3", - "python.terminal.activateEnvironment": true, - "python.venvPath": "/opt/netbox/", - "files.exclude": { - "**/node_modules": true, - "build": true, - "dist": true, - "*egg*": true - } - }, + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "editor.experimental.stickyScroll.enabled": true, + //"[python]": { + // "editor.codeActionsOnSave": { + // "source.organizeImports": true + // } + //}, + "isort.args": ["--profile=black"], + "isort.path": ["/opt/netbox/venv/bin/isort"], + "python.analysis.typeCheckingMode": "strict", + "python.analysis.extraPaths": ["/opt/netbox/netbox"], + "python.autoComplete.extraPaths": ["/opt/netbox/netbox"], + "python.defaultInterpreterPath": "/opt/netbox/venv/bin/python3", + "python.formatting.autopep8Path": "/opt/netbox/venv/bin/autopep8", + "python.formatting.blackPath": "/opt/netbox/venv/bin/black", + "python.formatting.provider": "black", + "python.formatting.yapfPath": "/opt/netbox/venv/bin/yapf", + "python.linting.banditPath": "/opt/netbox/venv/bin/bandit", + "python.linting.enabled": true, + "python.linting.flake8Path": "/opt/netbox/venv/bin/flake8", + "python.linting.flake8Args": ["--max-line-length=160", "--ignore=E203"], + "python.linting.mypyPath": "//opt/netbox/venv/bin/mypy", + "python.linting.pycodestylePath": "/opt/netbox/venv/bin/pycodestyle", + "python.linting.pydocstylePath": "/opt/netbox/venv/bin/pydocstyle", + "python.linting.pylintArgs": [ + "--load-plugins", + "pylint_django", + "--errors-only", + "--load-plugins=pylint_django", + "--django-settings-module=/opt/netbox/netbox/netbox/netbox.settings", + "--enable=W0602,W0611,W0612,W0613,W0614" + ], + "python.linting.pylintEnabled": true, + "python.linting.pylintPath": "/opt/netbox/venv/bin/pylint", + "python.linting.lintOnSave": true, + "python.pythonPath": "/opt/netbox/venv/bin/python3", + "python.terminal.activateEnvironment": true, + "python.venvPath": "/opt/netbox/", + "files.exclude": { + "**/node_modules": true, + "build": true, + "dist": true, + "*egg*": true + } + }, - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "DavidAnson.vscode-markdownlint", - "GitHub.codespaces", - "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", - "wholroyd.jinja", - "yzhang.markdown-all-in-one" - ] - } - }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "DavidAnson.vscode-markdownlint", + "GitHub.codespaces", + "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", + "wholroyd.jinja", + "yzhang.markdown-all-in-one" + ] + } + }, - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // This can be used to network with other containers or the host. - // "forwardPorts": [5000, 5432], + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // This can be used to network with other containers or the host. + // "forwardPorts": [5000, 5432], - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "pip install --user -r requirements-dev.txt", + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip install --user -r requirements-dev.txt", - //"postAttachCommand": "source /opt/netbox/venv/bin/activate", - - // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" + //"postAttachCommand": "source /opt/netbox/venv/bin/activate", + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" } From 38952e10f01cf413a48370e3bc342b149c3b2c78 Mon Sep 17 00:00:00 2001 From: ryanmerolle Date: Fri, 23 Jun 2023 21:18:04 +0000 Subject: [PATCH 04/19] housekeeping --- .devcontainer/requirements-dev.txt | 1 + .gitignore | 15 +++++++++++++++ Makefile | 11 +++++++++-- test.sh | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.devcontainer/requirements-dev.txt b/.devcontainer/requirements-dev.txt index 80fc9bf..5b4885e 100644 --- a/.devcontainer/requirements-dev.txt +++ b/.devcontainer/requirements-dev.txt @@ -2,6 +2,7 @@ autoflake autopep8 bandit black +coverage flake8 isort mypy diff --git a/.gitignore b/.gitignore index 2f771e8..52547c9 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,18 @@ cython_debug/ .vscode/ # JetBrains .idea/ + +# Temporary files +*.tmp +tmp/ + +# coverage +coverage/ +htmlcov/ +.coverage +.coverage.* +coverage.xml +*.cover + +# ruff +.ruff_cache/ diff --git a/Makefile b/Makefile index fb3ba16..259948c 100644 --- a/Makefile +++ b/Makefile @@ -75,8 +75,15 @@ 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} + ${NETBOX_MANAGE_PATH}/manage.py makemigrations ${PLUGIN_NAME} --check + coverage run --source "netbox_acls" ${NETBOX_MANAGE_PATH}/manage.py test ${PLUGIN_NAME} -v 2 + +.PHONY: coverage_report +coverage_report: + coverage report + +.PHONY: test_coverage +test_coverage: test coverage_report #relpatch: # $(eval GSTATUS := $(shell git status --porcelain)) diff --git a/test.sh b/test.sh index 5fc2e06..7f16723 100755 --- a/test.sh +++ b/test.sh @@ -17,7 +17,7 @@ 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 + $doco run --rm netbox python manage.py test netbox_acls -v 2 } test_cleanup() { From ef83787a8c20c04d9325ca45ef0a438ed816bf0a Mon Sep 17 00:00:00 2001 From: Ryan Merolle Date: Sat, 24 Jun 2023 11:31:36 -0400 Subject: [PATCH 05/19] Update accesslist.html --- netbox_acls/templates/netbox_acls/accesslist.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox_acls/templates/netbox_acls/accesslist.html b/netbox_acls/templates/netbox_acls/accesslist.html index bd4f003..db7e73c 100644 --- a/netbox_acls/templates/netbox_acls/accesslist.html +++ b/netbox_acls/templates/netbox_acls/accesslist.html @@ -23,7 +23,7 @@
Access List
Access List Type - {{ object.get_type_display }} + {% badge object.get_type_display bg_color=object.get_type_color %} Default Action From 60c0d8906d4a1f9f8765745b93282c6b87d8a18a Mon Sep 17 00:00:00 2001 From: Ryan Merolle Date: Sat, 24 Jun 2023 11:40:28 -0400 Subject: [PATCH 06/19] Update accesslist.html --- netbox_acls/templates/netbox_acls/accesslist.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox_acls/templates/netbox_acls/accesslist.html b/netbox_acls/templates/netbox_acls/accesslist.html index db7e73c..b37e2a0 100644 --- a/netbox_acls/templates/netbox_acls/accesslist.html +++ b/netbox_acls/templates/netbox_acls/accesslist.html @@ -23,7 +23,7 @@
Access List
Access List Type - {% badge object.get_type_display bg_color=object.get_type_color %} + {% badge object.get_type_display bg_color=object.get_type_color %} Default Action From 89b040436b0ce91f4c0c1561860e099c609124dc Mon Sep 17 00:00:00 2001 From: Ryan Merolle Date: Sun, 9 Jul 2023 23:33:29 -0400 Subject: [PATCH 07/19] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ab061a4..0e7a918 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ multi_line_output = 3 max-line-length = 140 [tool.pyright] -include = ["netbox_secrets"] +include = ["netbox_acls"] exclude = [ "**/node_modules", "**/__pycache__", From 484eeb2df2af810c8273729239c28e9426e4e267 Mon Sep 17 00:00:00 2001 From: Ryan Merolle Date: Sat, 14 Oct 2023 00:43:33 -0400 Subject: [PATCH 08/19] Update dependabot.yml --- .github/dependabot.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 56cca7f..f954b70 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,9 +1,16 @@ --- +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + version: 2 updates: - - package-ecosystem: pip + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" directory: "/" schedule: - interval: daily - time: "04:00" - open-pull-requests-limit: 10 + interval: "weekly" From a99cc4aafacdca53e49e4fdbf2534c7d10a21a0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 04:43:50 +0000 Subject: [PATCH 09/19] Bump actions/dependency-review-action from 2 to 3 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 2 to 3. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index f8ba70e..9cddb6d 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -18,4 +18,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v3 - name: 'Dependency Review' - uses: actions/dependency-review-action@v2 + uses: actions/dependency-review-action@v3 From 534a2e7fd651a04a5429a74ee428e53256e27967 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 04:43:54 +0000 Subject: [PATCH 10/19] Bump pypa/gh-action-pypi-publish from 1.4.2 to 1.8.10 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.4.2 to 1.8.10. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/27b31702a0e7fc50959f5ad993c78deac1bdfc29...b7f401de30cb6434a1e19f805ff006643653240e) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/python-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 04f8db0..be46aab 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -34,7 +34,7 @@ jobs: - name: Build package run: python -m build - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + uses: pypa/gh-action-pypi-publish@b7f401de30cb6434a1e19f805ff006643653240e with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} From a9221226ffca5b81c63f4e651654b2145bb671e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 04:43:57 +0000 Subject: [PATCH 11/19] Bump github/super-linter from 4 to 5 Bumps [github/super-linter](https://github.com/github/super-linter) from 4 to 5. - [Changelog](https://github.com/github/super-linter/blob/main/docs/release-process.md) - [Commits](https://github.com/github/super-linter/compare/v4...v5) --- updated-dependencies: - dependency-name: github/super-linter dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4adfb50..1e0d4d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: fetch-depth: 0 - name: Lint Code Base - uses: github/super-linter/slim@v4 + uses: github/super-linter/slim@v5 env: DEFAULT_BRANCH: dev GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 281641f36fff2ccfb47552fba695c086b293b93c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 04:44:05 +0000 Subject: [PATCH 12/19] Bump actions/setup-python from 3 to 4 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3 to 4. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/python-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 04f8db0..d735b7b 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies From 908ec867486208d7d589b99fc1332579bfec5609 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Oct 2023 04:47:51 +0000 Subject: [PATCH 13/19] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/python-publish.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4adfb50..c331b6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Full git history is needed to get a proper list of changed files within `super-linter` fetch-depth: 0 @@ -40,7 +40,7 @@ jobs: steps: - id: git-checkout name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - id: docker-test name: Test the image diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 94e8b63..4a766f3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 9cddb6d..f63346c 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -16,6 +16,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 'Dependency Review' uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index a1b0157..70f421e 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: From 6c804162ea714122801e94aadc6e824d83f07800 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 03:58:52 +0000 Subject: [PATCH 14/19] Bump actions/setup-python from 4 to 5 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/python-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 70f421e..c4e3239 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install dependencies From 2873875dc600cd719719e10e4174eab7756fd5ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 03:06:24 +0000 Subject: [PATCH 15/19] Bump github/codeql-action from 2 to 3 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/v2...v3) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 4a766f3..354dfc5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -43,7 +43,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -57,7 +57,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -70,4 +70,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From a685e96bf0993cc62140612cfd240f4453808ce6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 03:54:25 +0000 Subject: [PATCH 16/19] Bump actions/dependency-review-action from 3 to 4 Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 3 to 4. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/dependency-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index f63346c..9a20f45 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -18,4 +18,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v4 - name: 'Dependency Review' - uses: actions/dependency-review-action@v3 + uses: actions/dependency-review-action@v4 From 2528e92d5cb4d6ef118c7cb50351b3e09f14b17d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 03:39:14 +0000 Subject: [PATCH 17/19] Bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.14 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.10 to 1.8.14. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/b7f401de30cb6434a1e19f805ff006643653240e...81e9d935c883d0b210363ab89cf05f3894778450) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/python-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 70f421e..c96a7bc 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -34,7 +34,7 @@ jobs: - name: Build package run: python -m build - name: Publish package - uses: pypa/gh-action-pypi-publish@b7f401de30cb6434a1e19f805ff006643653240e + uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} From f0b461616ba2e0d8cac7b17546b420f26edb44ba Mon Sep 17 00:00:00 2001 From: Ryan Merolle Date: Wed, 27 Mar 2024 00:09:32 -0400 Subject: [PATCH 18/19] netbox 3.6 support (#167) netbox 3.6 support thanks to @abhi1693 & @kbelokon --- .devcontainer/Dockerfile-plugin_dev | 11 +- .devcontainer/configuration/configuration.py | 363 ++++++++++--------- .devcontainer/devcontainer.json | 2 +- .devcontainer/docker-compose.yml | 8 +- .devcontainer/env/netbox.env | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 4 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 14 +- Dockerfile | 4 +- README.md | 7 +- docker-compose.yml | 6 +- env/netbox.env | 2 +- netbox_acls/__init__.py | 4 +- netbox_acls/forms/filtersets.py | 13 +- netbox_acls/forms/models.py | 182 ++++------ netbox_acls/tables.py | 6 +- netbox_acls/version.py | 2 +- test.sh | 13 +- 19 files changed, 315 insertions(+), 332 deletions(-) diff --git a/.devcontainer/Dockerfile-plugin_dev b/.devcontainer/Dockerfile-plugin_dev index 633f053..fa0ba06 100644 --- a/.devcontainer/Dockerfile-plugin_dev +++ b/.devcontainer/Dockerfile-plugin_dev @@ -1,15 +1,15 @@ -ARG NETBOX_VARIANT=v3.5 +ARG NETBOX_VARIANT=v3.6 FROM netboxcommunity/netbox:${NETBOX_VARIANT} -ARG NETBOX_INITIALIZERS_VARIANT=3.5.* +ARG NETBOX_INITIALIZERS_VARIANT=3.6.* ARG DEBIAN_FRONTEND=noninteractive # Install APT packages # hadolint ignore=DL3008 RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y install --no-install-recommends curl git make openssh-client python3.10-dev sudo wget zsh \ + && apt-get -y install --no-install-recommends curl git make openssh-client python3.11-dev sudo wget zsh \ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* # Install development & ide dependencies @@ -17,12 +17,11 @@ COPY requirements-dev.txt /tmp/pip-tmp/ RUN /opt/netbox/venv/bin/python3 -m pip install --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements-dev.txt \ && rm -rf /tmp/* -ARG USERNAME=vscode +ARG USERNAME=ubuntu ARG USER_UID=1000 ARG USER_GID=$USER_UID -RUN useradd -l -md /home/vscode -s /usr/bin/zsh -u $USER_UID $USERNAME \ - && usermod -aG sudo $USERNAME \ +RUN usermod -aG sudo $USERNAME \ && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ && mkdir /opt/netbox/netbox/netbox-acls \ && chown $USERNAME:$USERNAME /opt/netbox /etc/netbox /opt/unit -R diff --git a/.devcontainer/configuration/configuration.py b/.devcontainer/configuration/configuration.py index 0a41530..5f80d94 100644 --- a/.devcontainer/configuration/configuration.py +++ b/.devcontainer/configuration/configuration.py @@ -1,19 +1,46 @@ -# Based on https://github.com/netbox-community/netbox-docker/blob/release/configuration/configuration.py +#### +## We recommend to not edit this file. +## Create separate files to overwrite the settings. +## See `extra.py` as an example. +#### + import re from os import environ from os.path import abspath, dirname, join +from typing import Any, Callable, Tuple + +# For reference see https://docs.netbox.dev/en/stable/configuration/ +# Based on https://github.com/netbox-community/netbox/blob/develop/netbox/netbox/configuration_example.py +### +# NetBox-Docker Helper functions +### # Read secret from file -def _read_secret(secret_name, default=None): +def _read_secret(secret_name: str, default: str | None = None) -> str | None: try: - f = open(f"/run/secrets/{secret_name}", encoding="utf-8") - except OSError: + f = open(f'/run/secrets/{secret_name}', 'r', encoding='utf-8') + except EnvironmentError: return default else: with f: return f.readline().strip() +# If the `map_fn` isn't defined, then the value that is read from the environment (or the default value if not found) is returned. +# If the `map_fn` is defined, then `map_fn` is invoked and the value (that was read from the environment or the default value if not found) +# is passed to it as a parameter. The value returned from `map_fn` is then the return value of this function. +# The `map_fn` is not invoked, if the value (that was read from the environment or the default value if not found) is None. +def _environ_get_and_map(variable_name: str, default: str | None = None, map_fn: Callable[[str], Any | None] = None) -> Any | None: + env_value = environ.get(variable_name, default) + + if env_value is None: + return env_value + + return env_value if not map_fn else map_fn(env_value) + +_AS_BOOL = lambda value : value.lower() == 'true' +_AS_INT = lambda value : int(value) +_AS_LIST = lambda value : list(filter(None, value.split(' '))) _BASE_DIR = dirname(dirname(abspath(__file__))) @@ -27,59 +54,49 @@ def _read_secret(secret_name, default=None): # 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(" ") +ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ') +# ensure that '*' or 'localhost' is always in ALLOWED_HOSTS (needed for health checks) +if '*' not in ALLOWED_HOSTS and 'localhost' not in ALLOWED_HOSTS: + ALLOWED_HOSTS.append('localhost') # PostgreSQL database configuration. See the Django documentation for a complete list of available parameters: # 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 + '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': _environ_get_and_map('DB_CONN_MAX_AGE', '300', _AS_INT), + # Max database connection age + 'DISABLE_SERVER_SIDE_CURSORS': _environ_get_and_map('DB_DISABLE_SERVER_SIDE_CURSORS', 'False', _AS_BOOL), + # 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", + 'tasks': { + 'HOST': environ.get('REDIS_HOST', 'localhost'), + 'PORT': _environ_get_and_map('REDIS_PORT', 6379, _AS_INT), + 'USERNAME': environ.get('REDIS_USERNAME', ''), + 'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')), + 'DATABASE': _environ_get_and_map('REDIS_DATABASE', 0, _AS_INT), + 'SSL': _environ_get_and_map('REDIS_SSL', 'False', _AS_BOOL), + 'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False', _AS_BOOL), }, - "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", + 'caching': { + 'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')), + 'PORT': _environ_get_and_map('REDIS_CACHE_PORT', environ.get('REDIS_PORT', '6379'), _AS_INT), + 'USERNAME': environ.get('REDIS_CACHE_USERNAME', environ.get('REDIS_USERNAME', '')), + 'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))), + 'DATABASE': _environ_get_and_map('REDIS_CACHE_DATABASE', '1', _AS_INT), + 'SSL': _environ_get_and_map('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False'), _AS_BOOL), + 'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False'), _AS_BOOL), }, } @@ -87,7 +104,7 @@ def _read_secret(secret_name, default=None): # 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", "")) +SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', '')) ######################### @@ -96,195 +113,203 @@ def _read_secret(secret_name, default=None): # # ######################### -# Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of -# application errors (assuming correct email settings are provided). -ADMINS = [ - # ['John Doe', 'jdoe@example.com'], -] - -# URL schemes that are allowed within links in NetBox -ALLOWED_URL_SCHEMES = ( - "file", - "ftp", - "ftps", - "http", - "https", - "irc", - "mailto", - "sftp", - "ssh", - "tel", - "telnet", - "tftp", - "vnc", - "xmpp", -) +# # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of +# # application errors (assuming correct email settings are provided). +# ADMINS = [ +# # ['John Doe', 'jdoe@example.com'], +# ] + +if 'ALLOWED_URL_SCHEMES' in environ: + ALLOWED_URL_SCHEMES = _environ_get_and_map('ALLOWED_URL_SCHEMES', None, _AS_LIST) # Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same # content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP. -BANNER_TOP = environ.get("BANNER_TOP", "") -BANNER_BOTTOM = environ.get("BANNER_BOTTOM", "") +if 'BANNER_TOP' in environ: + BANNER_TOP = environ.get('BANNER_TOP', None) +if 'BANNER_BOTTOM' in environ: + BANNER_BOTTOM = environ.get('BANNER_BOTTOM', None) # Text to include on the login page above the login form. HTML is allowed. -BANNER_LOGIN = environ.get("BANNER_LOGIN", "") - -# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set: -# BASE_PATH = 'netbox/' -BASE_PATH = environ.get("BASE_PATH", "") +if 'BANNER_LOGIN' in environ: + BANNER_LOGIN = environ.get('BANNER_LOGIN', None) # Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) -CHANGELOG_RETENTION = int(environ.get("CHANGELOG_RETENTION", 90)) +if 'CHANGELOG_RETENTION' in environ: + CHANGELOG_RETENTION = _environ_get_and_map('CHANGELOG_RETENTION', None, _AS_INT) + +# Maximum number of days to retain job results (scripts and reports). Set to 0 to retain job results in the database indefinitely. (Default: 90) +if 'JOB_RETENTION' in environ: + JOB_RETENTION = _environ_get_and_map('JOB_RETENTION', None, _AS_INT) +# JOBRESULT_RETENTION was renamed to JOB_RETENTION in the v3.5.0 release of NetBox. For backwards compatibility, map JOBRESULT_RETENTION to JOB_RETENTION +elif 'JOBRESULT_RETENTION' in environ: + JOB_RETENTION = _environ_get_and_map('JOBRESULT_RETENTION', None, _AS_INT) # API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be # allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or # CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers -CORS_ORIGIN_ALLOW_ALL = environ.get("CORS_ORIGIN_ALLOW_ALL", "False").lower() == "true" -CORS_ORIGIN_WHITELIST = list( - filter(None, environ.get("CORS_ORIGIN_WHITELIST", "https://localhost").split(" ")), -) -CORS_ORIGIN_REGEX_WHITELIST = [ - re.compile(r) - for r in list( - filter(None, environ.get("CORS_ORIGIN_REGEX_WHITELIST", "").split(" ")), - ) -] +CORS_ORIGIN_ALLOW_ALL = _environ_get_and_map('CORS_ORIGIN_ALLOW_ALL', 'False', _AS_BOOL) +CORS_ORIGIN_WHITELIST = _environ_get_and_map('CORS_ORIGIN_WHITELIST', 'https://localhost', _AS_LIST) +CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in _environ_get_and_map('CORS_ORIGIN_REGEX_WHITELIST', '', _AS_LIST)] # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal -# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging -# on a production system. -DEBUG = environ.get("DEBUG", "False").lower() == "true" +# sensitive information about your installation. Only enable debugging while performing testing. +# Never enable debugging on a production system. +DEBUG = _environ_get_and_map('DEBUG', 'False', _AS_BOOL) -# Set to True to enable DEVELOPER Mode. WARNING: ONLY netbox developers or plugin developers need this access. -DEVELOPER = environ.get("DEVELOPER_MODE", "False").lower() == "true" +# This parameter serves as a safeguard to prevent some potentially dangerous behavior, +# such as generating new database schema migrations. +# Set this to True only if you are actively developing the NetBox code base. +DEVELOPER = _environ_get_and_map('DEVELOPER', 'False', _AS_BOOL) # Email settings EMAIL = { - "SERVER": environ.get("EMAIL_SERVER", "localhost"), - "PORT": int(environ.get("EMAIL_PORT", 25)), - "USERNAME": environ.get("EMAIL_USERNAME", ""), - "PASSWORD": _read_secret("email_password", environ.get("EMAIL_PASSWORD", "")), - "USE_SSL": environ.get("EMAIL_USE_SSL", "False").lower() == "true", - "USE_TLS": environ.get("EMAIL_USE_TLS", "False").lower() == "true", - "SSL_CERTFILE": environ.get("EMAIL_SSL_CERTFILE", ""), - "SSL_KEYFILE": environ.get("EMAIL_SSL_KEYFILE", ""), - "TIMEOUT": int(environ.get("EMAIL_TIMEOUT", 10)), # seconds - "FROM_EMAIL": environ.get("EMAIL_FROM", ""), + 'SERVER': environ.get('EMAIL_SERVER', 'localhost'), + 'PORT': _environ_get_and_map('EMAIL_PORT', 25, _AS_INT), + 'USERNAME': environ.get('EMAIL_USERNAME', ''), + 'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')), + 'USE_SSL': _environ_get_and_map('EMAIL_USE_SSL', 'False', _AS_BOOL), + 'USE_TLS': _environ_get_and_map('EMAIL_USE_TLS', 'False', _AS_BOOL), + 'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''), + 'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''), + 'TIMEOUT': _environ_get_and_map('EMAIL_TIMEOUT', 10, _AS_INT), # seconds + 'FROM_EMAIL': environ.get('EMAIL_FROM', ''), } # Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table # (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True. -ENFORCE_GLOBAL_UNIQUE = environ.get("ENFORCE_GLOBAL_UNIQUE", "False").lower() == "true" +if 'ENFORCE_GLOBAL_UNIQUE' in environ: + ENFORCE_GLOBAL_UNIQUE = _environ_get_and_map('ENFORCE_GLOBAL_UNIQUE', None, _AS_BOOL) # Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and # by anonymous users. List models in the form `.`. Add '*' to this list to exempt all models. -EXEMPT_VIEW_PERMISSIONS = list( - filter(None, environ.get("EXEMPT_VIEW_PERMISSIONS", "").split(" ")), -) +EXEMPT_VIEW_PERMISSIONS = _environ_get_and_map('EXEMPT_VIEW_PERMISSIONS', '', _AS_LIST) + +# HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks). +# HTTP_PROXIES = { +# 'http': 'http://10.10.1.10:3128', +# 'https': 'http://10.10.1.10:1080', +# } + +# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing +# NetBox from an internal IP. +INTERNAL_IPS = _environ_get_and_map('INTERNAL_IPS', '127.0.0.1 ::1', _AS_LIST) # Enable GraphQL API. -GRAPHQL_ENABLED = environ.get("GRAPHQL_ENABLED", "True").lower() == "true" +if 'GRAPHQL_ENABLED' in environ: + GRAPHQL_ENABLED = _environ_get_and_map('GRAPHQL_ENABLED', None, _AS_BOOL) + +# # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: +# # https://docs.djangoproject.com/en/stable/topics/logging/ +# LOGGING = {} -# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: -# https://docs.djangoproject.com/en/stable/topics/logging/ -LOGGING = {} +# Automatically reset the lifetime of a valid session upon each authenticated request. Enables users to remain +# authenticated to NetBox indefinitely. +LOGIN_PERSISTENCE = _environ_get_and_map('LOGIN_PERSISTENCE', 'False', _AS_BOOL) # Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users # are permitted to access most data in NetBox (excluding secrets) but not make any changes. -LOGIN_REQUIRED = environ.get("LOGIN_REQUIRED", "False").lower() == "true" +LOGIN_REQUIRED = _environ_get_and_map('LOGIN_REQUIRED', 'False', _AS_BOOL) # The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to # re-authenticate. (Default: 1209600 [14 days]) -LOGIN_TIMEOUT = int(environ.get("LOGIN_TIMEOUT", 1209600)) +LOGIN_TIMEOUT = _environ_get_and_map('LOGIN_TIMEOUT', 1209600, _AS_INT) # Setting this to True will display a "maintenance mode" banner at the top of every page. -MAINTENANCE_MODE = environ.get("MAINTENANCE_MODE", "False").lower() == "true" +if 'MAINTENANCE_MODE' in environ: + MAINTENANCE_MODE = _environ_get_and_map('MAINTENANCE_MODE', None, _AS_BOOL) + +# Maps provider +if 'MAPS_URL' in environ: + MAPS_URL = environ.get('MAPS_URL', None) # An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g. # "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request # all objects by specifying "?limit=0". -MAX_PAGE_SIZE = int(environ.get("MAX_PAGE_SIZE", 1000)) +if 'MAX_PAGE_SIZE' in environ: + MAX_PAGE_SIZE = _environ_get_and_map('MAX_PAGE_SIZE', None, _AS_INT) # The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that # the default value of this setting is derived from the installed location. -MEDIA_ROOT = environ.get("MEDIA_ROOT", join(_BASE_DIR, "media")) +MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media')) # Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics' -METRICS_ENABLED = environ.get("METRICS_ENABLED", "False").lower() == "true" - -# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM. -NAPALM_USERNAME = environ.get("NAPALM_USERNAME", "") -NAPALM_PASSWORD = _read_secret("napalm_password", environ.get("NAPALM_PASSWORD", "")) - -# NAPALM timeout (in seconds). (Default: 30) -NAPALM_TIMEOUT = int(environ.get("NAPALM_TIMEOUT", 30)) - -# NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must -# be provided as a dictionary. -NAPALM_ARGS = {} +METRICS_ENABLED = _environ_get_and_map('METRICS_ENABLED', 'False', _AS_BOOL) # Determine how many objects to display per page within a list. (Default: 50) -PAGINATE_COUNT = int(environ.get("PAGINATE_COUNT", 50)) +if 'PAGINATE_COUNT' in environ: + PAGINATE_COUNT = _environ_get_and_map('PAGINATE_COUNT', None, _AS_INT) -# Enable installed plugins. Add the name of each plugin to the list. -PLUGINS = [] +# # Enable installed plugins. Add the name of each plugin to the list. +# PLUGINS = [] -# Plugins configuration settings. These settings are used by various plugins that the user may have installed. -# Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. -PLUGINS_CONFIG = {} +# # Plugins configuration settings. These settings are used by various plugins that the user may have installed. +# # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. +# PLUGINS_CONFIG = { +# } # When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to # prefer IPv4 instead. -PREFER_IPV4 = environ.get("PREFER_IPV4", "False").lower() == "true" +if 'PREFER_IPV4' in environ: + PREFER_IPV4 = _environ_get_and_map('PREFER_IPV4', None, _AS_BOOL) + +# The default value for the amperage field when creating new power feeds. +if 'POWERFEED_DEFAULT_AMPERAGE' in environ: + POWERFEED_DEFAULT_AMPERAGE = _environ_get_and_map('POWERFEED_DEFAULT_AMPERAGE', None, _AS_INT) + +# The default value (percentage) for the max_utilization field when creating new power feeds. +if 'POWERFEED_DEFAULT_MAX_UTILIZATION' in environ: + POWERFEED_DEFAULT_MAX_UTILIZATION = _environ_get_and_map('POWERFEED_DEFAULT_MAX_UTILIZATION', None, _AS_INT) + +# The default value for the voltage field when creating new power feeds. +if 'POWERFEED_DEFAULT_VOLTAGE' in environ: + POWERFEED_DEFAULT_VOLTAGE = _environ_get_and_map('POWERFEED_DEFAULT_VOLTAGE', None, _AS_INT) # Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1. -RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = int( - environ.get("RACK_ELEVATION_DEFAULT_UNIT_HEIGHT", 22), -) -RACK_ELEVATION_DEFAULT_UNIT_WIDTH = int( - environ.get("RACK_ELEVATION_DEFAULT_UNIT_WIDTH", 220), -) +if 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT' in environ: + RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', None, _AS_INT) +if 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH' in environ: + RACK_ELEVATION_DEFAULT_UNIT_WIDTH = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', None, _AS_INT) # Remote authentication support -REMOTE_AUTH_ENABLED = environ.get("REMOTE_AUTH_ENABLED", "False").lower() == "true" -REMOTE_AUTH_BACKEND = environ.get( - "REMOTE_AUTH_BACKEND", - "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_DEFAULT_GROUPS = list( - filter(None, environ.get("REMOTE_AUTH_DEFAULT_GROUPS", "").split(" ")), -) +REMOTE_AUTH_ENABLED = _environ_get_and_map('REMOTE_AUTH_ENABLED', 'False', _AS_BOOL) +REMOTE_AUTH_BACKEND = _environ_get_and_map('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend', _AS_LIST) +REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER') +REMOTE_AUTH_AUTO_CREATE_USER = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_USER', 'False', _AS_BOOL) +REMOTE_AUTH_DEFAULT_GROUPS = _environ_get_and_map('REMOTE_AUTH_DEFAULT_GROUPS', '', _AS_LIST) +# REMOTE_AUTH_DEFAULT_PERMISSIONS = {} # This repository is used to check whether there is a new release of NetBox available. Set to None to disable the # version check or use the URL below to check for release in the official NetBox repository. -# https://api.github.com/repos/netbox-community/netbox/releases -RELEASE_CHECK_URL = environ.get("RELEASE_CHECK_URL", None) - -# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of -# this setting is derived from the installed location. -REPORTS_ROOT = environ.get("REPORTS_ROOT", "/etc/netbox/reports") +RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None) +# RELEASE_CHECK_URL = 'https://api.github.com/repos/netbox-community/netbox/releases' # Maximum execution time for background tasks, in seconds. -RQ_DEFAULT_TIMEOUT = int(environ.get("RQ_DEFAULT_TIMEOUT", 300)) +RQ_DEFAULT_TIMEOUT = _environ_get_and_map('RQ_DEFAULT_TIMEOUT', 300, _AS_INT) + +# The name to use for the csrf token cookie. +CSRF_COOKIE_NAME = environ.get('CSRF_COOKIE_NAME', 'csrftoken') + +# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag. +# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like: +# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev +CSRF_TRUSTED_ORIGINS = _environ_get_and_map('CSRF_TRUSTED_ORIGINS', '', _AS_LIST) -# The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of -# this setting is derived from the installed location. -SCRIPTS_ROOT = environ.get("SCRIPTS_ROOT", "/etc/netbox/scripts") +# The name to use for the session cookie. +SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME', 'sessionid') # By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use # local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only # database access.) Note that the user as which NetBox runs must have read and write permissions to this path. -SESSION_FILE_PATH = environ.get("SESSIONS_ROOT", None) +SESSION_FILE_PATH = environ.get('SESSION_FILE_PATH', environ.get('SESSIONS_ROOT', None)) # Time zone (default: UTC) -TIME_ZONE = environ.get("TIME_ZONE", "UTC") +TIME_ZONE = environ.get('TIME_ZONE', 'UTC') # Date/time formatting. See the following link for supported formats: # https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date -DATE_FORMAT = environ.get("DATE_FORMAT", "N j, Y") -SHORT_DATE_FORMAT = environ.get("SHORT_DATE_FORMAT", "Y-m-d") -TIME_FORMAT = environ.get("TIME_FORMAT", "g:i a") -SHORT_TIME_FORMAT = environ.get("SHORT_TIME_FORMAT", "H:i:s") -DATETIME_FORMAT = environ.get("DATETIME_FORMAT", "N j, Y g:i a") -SHORT_DATETIME_FORMAT = environ.get("SHORT_DATETIME_FORMAT", "Y-m-d H:i") +DATE_FORMAT = environ.get('DATE_FORMAT', 'N j, Y') +SHORT_DATE_FORMAT = environ.get('SHORT_DATE_FORMAT', 'Y-m-d') +TIME_FORMAT = environ.get('TIME_FORMAT', 'g:i a') +SHORT_TIME_FORMAT = environ.get('SHORT_TIME_FORMAT', 'H:i:s') +DATETIME_FORMAT = environ.get('DATETIME_FORMAT', 'N j, Y g:i a') +SHORT_DATETIME_FORMAT = environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i') \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8b36b28..92d29e5 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -104,5 +104,5 @@ //"postAttachCommand": "source /opt/netbox/venv/bin/activate", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. - "remoteUser": "vscode" + "remoteUser": "ubuntu" } diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index b008240..cfd8f32 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -16,10 +16,10 @@ services: interval: 15s test: "curl -f http://localhost:8080/api/ || exit 1" volumes: - - ./configuration:/etc/netbox/config:z,ro - #- ./reports:/etc/netbox/reports:z,ro - #- ./scripts:/etc/netbox/scripts:z,ro - #- netbox-media-files:/opt/netbox/netbox/media:z + - ./configuration:/etc/netbox/config:ro + #- netbox-media-files:/opt/netbox/netbox/media:rw + #- netbox-reports-files:/opt/netbox/netbox/reports:rw + #- netbox-scripts-files:/opt/netbox/netbox/scripts:rw #netbox-worker: # <<: *netbox # depends_on: diff --git a/.devcontainer/env/netbox.env b/.devcontainer/env/netbox.env index 13b0f61..87b7d10 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_rClfWNjaa +SECRET_KEY='r(m)9nLGnz$(_q3N4z1k(EFsMCjjjzx08x9VhNVcfd%6RF#r!6DE@+V5Zk2X' SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 SUPERUSER_EMAIL=admin@example.com SUPERUSER_NAME=admin diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7008bae..abed693 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.3.0 + placeholder: v1.4.0 validations: required: true - type: input attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.5.4 + placeholder: v3.6.3 validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9f473b7..d9e09b4 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.5.4 + placeholder: v3.6.3 validations: required: true - type: dropdown diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d310908..bcdc166 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,4 +44,4 @@ jobs: - id: docker-test name: Test the image - run: ./test.sh snapshot + run: ./test.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4580d6c..6a502d2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ --- repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-docstring-first - id: check-merge-conflict @@ -21,24 +21,24 @@ repos: - "--profile=black" exclude: ^.devcontainer/ - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.9.1 hooks: - id: black language_version: python3 exclude: ^.devcontainer/ - repo: https://github.com/asottile/add-trailing-comma - rev: v2.5.1 + rev: v3.1.0 hooks: - id: add-trailing-comma args: - "--py36-plus" - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 exclude: ^.devcontainer/ - repo: https://github.com/asottile/pyupgrade - rev: v3.7.0 + rev: v3.15.0 hooks: - id: pyupgrade args: @@ -59,11 +59,11 @@ repos: # - id: htmlhint # args: [--config, .htmlhintrc] - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.35.0 + rev: v0.37.0 hooks: - id: markdownlint - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.272 + rev: v0.0.292 hooks: - id: ruff #- repo: local diff --git a/Dockerfile b/Dockerfile index 3b4a257..0c1b348 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG NETBOX_VARIANT=v3.5 +ARG NETBOX_VARIANT=v3.6 FROM netboxcommunity/netbox:${NETBOX_VARIANT} @@ -6,4 +6,4 @@ RUN mkdir -pv /plugins/netbox-acls COPY . /plugins/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 + cp -rf /plugins/netbox-acls/netbox_acls/ /opt/netbox/venv/lib/python3.11/site-packages/netbox_acls diff --git a/README.md b/README.md index 286a368..4a89d62 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,11 @@ Each Plugin Version listed below has been tested with its corresponding NetBox V | NetBox Version | Plugin Version | |:--------------:|:--------------:| -| 3.2 | 1.0.1 | -| 3.3 | 1.1.0 | -| 3.4 | 1.2.2 | +| 3.6 | 1.4.0 | | 3.5 | 1.3.0 | +| 3.4 | 1.2.2 | +| 3.3 | 1.1.0 | +| 3.2 | 1.0.1 | ## Installing diff --git a/docker-compose.yml b/docker-compose.yml index cc89ee5..2edb7b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,6 @@ services: build: dockerfile: Dockerfile context: . - args: - NETBOX_VARIANT: ${NETBOX_VARIANT} depends_on: - postgres - redis @@ -17,12 +15,12 @@ services: # postgres postgres: - image: postgres:14-alpine + image: postgres:15-alpine env_file: env/postgres.env # redis redis: - image: redis:6-alpine + image: redis:7-alpine command: - sh - -c # this is to evaluate the $REDIS_PASSWORD from the env diff --git a/env/netbox.env b/env/netbox.env index 8950d9f..9250029 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_rClfWNjaa +SECRET_KEY='r(m)9nLGnz$(_q3N4z1k(EFsMCjjjzx08x9VhNVcfd%6RF#r!6DE@+V5Zk2X' 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 4898b59..f82263a 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.5.0" - max_version = "3.5.99" + min_version = "3.6.0" + max_version = "3.6.99" config = NetBoxACLsConfig diff --git a/netbox_acls/forms/filtersets.py b/netbox_acls/forms/filtersets.py index 37f20df..8784725 100644 --- a/netbox_acls/forms/filtersets.py +++ b/netbox_acls/forms/filtersets.py @@ -7,7 +7,6 @@ from ipam.models import Prefix from netbox.forms import NetBoxModelFilterSetForm from utilities.forms.fields import ( - ChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, TagFilterField, @@ -74,11 +73,11 @@ class AccessListFilterForm(NetBoxModelFilterSetForm): queryset=VirtualChassis.objects.all(), required=False, ) - type = ChoiceField( + type = forms.ChoiceField( choices=add_blank_choice(ACLTypeChoices), required=False, ) - default_action = ChoiceField( + default_action = forms.ChoiceField( choices=add_blank_choice(ACLActionChoices), required=False, label="Default Action", @@ -158,7 +157,7 @@ class ACLInterfaceAssignmentFilterForm(NetBoxModelFilterSetForm): }, label="Access List", ) - direction = ChoiceField( + direction = forms.ChoiceField( choices=add_blank_choice(ACLAssignmentDirectionChoices), required=False, ) @@ -187,7 +186,7 @@ class ACLStandardRuleFilterForm(NetBoxModelFilterSetForm): required=False, label="Source Prefix", ) - action = ChoiceField( + action = forms.ChoiceField( choices=add_blank_choice(ACLRuleActionChoices), required=False, ) @@ -211,7 +210,7 @@ class ACLExtendedRuleFilterForm(NetBoxModelFilterSetForm): queryset=AccessList.objects.all(), required=False, ) - action = ChoiceField( + action = forms.ChoiceField( choices=add_blank_choice(ACLRuleActionChoices), required=False, ) @@ -225,7 +224,7 @@ class ACLExtendedRuleFilterForm(NetBoxModelFilterSetForm): required=False, label="Destination Prefix", ) - protocol = ChoiceField( + protocol = forms.ChoiceField( choices=add_blank_choice(ACLProtocolChoices), required=False, ) diff --git a/netbox_acls/forms/models.py b/netbox_acls/forms/models.py index 0c95ef5..dfba915 100644 --- a/netbox_acls/forms/models.py +++ b/netbox_acls/forms/models.py @@ -3,8 +3,8 @@ """ from dcim.models import Device, Interface, Region, Site, SiteGroup, VirtualChassis -from django import forms from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ValidationError from django.utils.safestring import mark_safe from ipam.models import Prefix from netbox.forms import NetBoxModelForm @@ -179,63 +179,49 @@ def clean(self): - Check if duplicate entry. (Because of GFK.) - Check if Access List has no existing rules before change the Access List's type. """ - cleaned_data = super().clean() - error_message = {} + super().clean() + if self.errors.get("name"): - return cleaned_data - name = cleaned_data.get("name") - acl_type = cleaned_data.get("type") - device = cleaned_data.get("device") - virtual_chassis = cleaned_data.get("virtual_chassis") - virtual_machine = cleaned_data.get("virtual_machine") + return + + name = self.cleaned_data.get("name") + acl_type = self.cleaned_data.get("type") + device = self.cleaned_data.get("device") + virtual_chassis = self.cleaned_data.get("virtual_chassis") + virtual_machine = self.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): - raise forms.ValidationError( - "Access Lists must be assigned to one host at a time. Either a device, virtual chassis or virtual machine." + raise ValidationError( + {"__all__": "Access Lists must be assigned to one host at a time. Either a device, virtual chassis or virtual machine."}, ) + # Check if no hosts selected. if not device and not virtual_chassis and not virtual_machine: - raise forms.ValidationError( - "Access Lists must be assigned to a device, virtual chassis or virtual machine.", - ) + raise ValidationError({"__all__": "Access Lists must be assigned to a device, virtual chassis or virtual machine."}) + existing_acls = None if device: host_type = "device" existing_acls = AccessList.objects.filter(name=name, device=device).exists() elif virtual_machine: host_type = "virtual_machine" - existing_acls = AccessList.objects.filter( - name=name, - virtual_machine=virtual_machine, - ).exists() - else: + existing_acls = AccessList.objects.filter(name=name, virtual_machine=virtual_machine).exists() + elif virtual_chassis: host_type = "virtual_chassis" - existing_acls = AccessList.objects.filter( - name=name, - virtual_chassis=virtual_chassis, - ).exists() + existing_acls = AccessList.objects.filter(name=name, virtual_chassis=virtual_chassis).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." - error_message |= { - host_type: [error_same_acl_name], - "name": [error_same_acl_name], - } + raise ValidationError({host_type: [error_same_acl_name], "name": [error_same_acl_name]}) + # Check if Access List has no existing rules before change the Access List's type. 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) - - return cleaned_data + raise ValidationError({"type": ["This ACL has ACL rules associated, CANNOT change ACL type."]}) def save(self, *args, **kwargs): # Set assigned object @@ -343,12 +329,13 @@ def clean(self): - Check for duplicate entry. (Because of GFK) - Check that the interface does not have an existing ACL applied in the direction already. """ - cleaned_data = super().clean() + super().clean() + error_message = {} - access_list = cleaned_data.get("access_list") - direction = cleaned_data.get("direction") - interface = cleaned_data.get("interface") - vminterface = cleaned_data.get("vminterface") + access_list = self.cleaned_data.get("access_list") + direction = self.cleaned_data.get("direction") + interface = self.cleaned_data.get("interface") + vminterface = self.cleaned_data.get("vminterface") # Check if both interface and vminterface are set. if interface and vminterface: @@ -366,22 +353,20 @@ def clean(self): "vminterface": [error_no_interface], } else: + # Define assigned_object, assigned_object_type, host_type, and host based on interface or vminterface if interface: assigned_object = interface assigned_object_type = "interface" host_type = "device" host = Interface.objects.get(pk=assigned_object.pk).device - assigned_object_id = Interface.objects.get(pk=assigned_object.pk).pk else: assigned_object = vminterface assigned_object_type = "vminterface" host_type = "virtual_machine" host = VMInterface.objects.get(pk=assigned_object.pk).virtual_machine - assigned_object_id = VMInterface.objects.get(pk=assigned_object.pk).pk - assigned_object_type_id = ContentType.objects.get_for_model( - assigned_object, - ).pk + assigned_object_id = assigned_object.pk + assigned_object_type_id = ContentType.objects.get_for_model(assigned_object).pk access_list_host = AccessList.objects.get(pk=access_list.pk).assigned_object # Check that an interface's parent device/virtual_machine is assigned to the Access List. @@ -392,20 +377,22 @@ def clean(self): assigned_object_type: [error_acl_not_assigned_to_host], host_type: [error_acl_not_assigned_to_host], } - # Check for duplicate entry. - if ACLInterfaceAssignment.objects.filter( + + # Check for duplicate entry and existing ACL in the direction. + existing_acl = ACLInterfaceAssignment.objects.filter( access_list=access_list, assigned_object_id=assigned_object_id, assigned_object_type=assigned_object_type_id, direction=direction, - ).exists(): + ) + if existing_acl.exists(): error_duplicate_entry = "An ACL with this name is already associated to this interface & direction." error_message |= { "access_list": [error_duplicate_entry], "direction": [error_duplicate_entry], assigned_object_type: [error_duplicate_entry], } - # Check that the interface does not have an existing ACL applied in the direction already. + if ACLInterfaceAssignment.objects.filter( assigned_object_id=assigned_object_id, assigned_object_type=assigned_object_type_id, @@ -418,8 +405,7 @@ def clean(self): } if error_message: - raise forms.ValidationError(error_message) - return cleaned_data + raise ValidationError(error_message) def save(self, *args, **kwargs): # Set assigned object @@ -484,27 +470,27 @@ def clean(self): - Check if action set to remark, but source_prefix set. - Check remark set, but action not set to remark. """ - cleaned_data = super().clean() + super().clean() + cleaned_data = self.cleaned_data error_message = {} - # No need to check for unique_together since there is no usage of GFK + action = cleaned_data.get("action") + remark = cleaned_data.get("remark") + source_prefix = cleaned_data.get("source_prefix") - if cleaned_data.get("action") == "remark": + if action == "remark": # Check if action set to remark, but no remark set. - if not cleaned_data.get("remark"): + if not remark: error_message["remark"] = [error_message_no_remark] # Check if action set to remark, but source_prefix set. - if cleaned_data.get("source_prefix"): - error_message["source_prefix"] = [ - error_message_action_remark_source_prefix_set, - ] + if source_prefix: + error_message["source_prefix"] = [error_message_action_remark_source_prefix_set] # Check remark set, but action not set to remark. - elif cleaned_data.get("remark"): + elif remark: error_message["remark"] = [error_message_remark_without_action_remark] if error_message: - raise forms.ValidationError(error_message) - return cleaned_data + raise ValidationError(error_message) class ACLExtendedRuleForm(NetBoxModelForm): @@ -583,53 +569,41 @@ class Meta: def clean(self): """ Validates form inputs before submitting: - - Check if action set to remark, but no remark set. - - Check if action set to remark, but source_prefix set. - - Check if action set to remark, but source_ports set. - - Check if action set to remark, but destination_prefix set. - - Check if action set to remark, but destination_ports set. - - Check if action set to remark, but destination_ports set. - - Check if action set to remark, but protocol set. - - Check remark set, but action not set to remark. + - Check if action set to remark, but no remark set. + - Check if action set to remark, but source_prefix set. + - Check if action set to remark, but source_ports set. + - Check if action set to remark, but destination_prefix set. + - Check if action set to remark, but destination_ports set. + - Check if action set to remark, but protocol set. + - Check remark set, but action not set to remark. """ - cleaned_data = super().clean() + super().clean() + cleaned_data = self.cleaned_data error_message = {} - # No need to check for unique_together since there is no usage of GFK + action = cleaned_data.get("action") + remark = cleaned_data.get("remark") + source_prefix = cleaned_data.get("source_prefix") + source_ports = cleaned_data.get("source_ports") + destination_prefix = cleaned_data.get("destination_prefix") + destination_ports = cleaned_data.get("destination_ports") + protocol = cleaned_data.get("protocol") - if cleaned_data.get("action") == "remark": - # Check if action set to remark, but no remark set. - if not cleaned_data.get("remark"): + if action == "remark": + if not remark: error_message["remark"] = [error_message_no_remark] - # Check if action set to remark, but source_prefix set. - if cleaned_data.get("source_prefix"): - error_message["source_prefix"] = [ - error_message_action_remark_source_prefix_set, - ] - # Check if action set to remark, but source_ports set. - if cleaned_data.get("source_ports"): - error_message["source_ports"] = [ - "Action is set to remark, Source Ports CANNOT be set.", - ] - # Check if action set to remark, but destination_prefix set. - if cleaned_data.get("destination_prefix"): - error_message["destination_prefix"] = [ - "Action is set to remark, Destination Prefix CANNOT be set.", - ] - # Check if action set to remark, but destination_ports set. - if cleaned_data.get("destination_ports"): - error_message["destination_ports"] = [ - "Action is set to remark, Destination Ports CANNOT be set.", - ] - # Check if action set to remark, but protocol set. - if cleaned_data.get("protocol"): - error_message["protocol"] = [ - "Action is set to remark, Protocol CANNOT be set.", - ] - # Check if action not set to remark, but remark set. - elif cleaned_data.get("remark"): + if source_prefix: + error_message["source_prefix"] = [error_message_action_remark_source_prefix_set] + if source_ports: + error_message["source_ports"] = ["Action is set to remark, Source Ports CANNOT be set."] + if destination_prefix: + error_message["destination_prefix"] = ["Action is set to remark, Destination Prefix CANNOT be set."] + if destination_ports: + error_message["destination_ports"] = ["Action is set to remark, Destination Ports CANNOT be set."] + if protocol: + error_message["protocol"] = ["Action is set to remark, Protocol CANNOT be set."] + elif remark: error_message["remark"] = [error_message_remark_without_action_remark] if error_message: - raise forms.ValidationError(error_message) - return cleaned_data + raise ValidationError(error_message) diff --git a/netbox_acls/tables.py b/netbox_acls/tables.py index 8552d78..b5a045f 100644 --- a/netbox_acls/tables.py +++ b/netbox_acls/tables.py @@ -64,7 +64,7 @@ class Meta(NetBoxTable.Meta): "rule_count", "default_action", "comments", - "actions", + "action", "tags", ) default_columns = ( @@ -147,7 +147,6 @@ class Meta(NetBoxTable.Meta): "access_list", "index", "action", - "actions", "remark", "tags", "description", @@ -157,7 +156,6 @@ class Meta(NetBoxTable.Meta): "access_list", "index", "action", - "actions", "remark", "source_prefix", "tags", @@ -189,7 +187,6 @@ class Meta(NetBoxTable.Meta): "access_list", "index", "action", - "actions", "remark", "tags", "description", @@ -203,7 +200,6 @@ class Meta(NetBoxTable.Meta): "access_list", "index", "action", - "actions", "remark", "tags", "source_prefix", diff --git a/netbox_acls/version.py b/netbox_acls/version.py index 67bc602..3e8d9f9 100644 --- a/netbox_acls/version.py +++ b/netbox_acls/version.py @@ -1 +1 @@ -__version__ = "1.3.0" +__version__ = "1.4.0" diff --git a/test.sh b/test.sh index 7f16723..f13c648 100755 --- a/test.sh +++ b/test.sh @@ -1,16 +1,9 @@ #!/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" @@ -27,12 +20,10 @@ test_cleanup() { docker image rm docker.io/library/netbox-acls-netbox || echo '' } -export NETBOX_VARIANT=${NETBOX_VARIANT} - -echo "🐳🐳🐳 Start testing '${NETBOX_VARIANT}'" +echo "🐳🐳🐳 Start testing" # Make sure the cleanup script is executed trap test_cleanup EXIT ERR test_netbox_unit_tests -echo "🐳🐳🐳 Done testing '${NETBOX_VARIANT}'" +echo "🐳🐳🐳 Done testing" From 01c35922830a5e80baefedb725391adb23837907 Mon Sep 17 00:00:00 2001 From: Ryan Merolle Date: Wed, 27 Mar 2024 00:46:36 -0400 Subject: [PATCH 19/19] Netbox 3.7 (#181) * 3.7 prep * bump image versions to match netbox-docker * add documentation on creating releases * fix typos in docs --- .devcontainer/Dockerfile-plugin_dev | 4 ++-- .devcontainer/docker-compose.yml | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 4 ++-- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- CONTRIBUTING.md | 7 +++++++ Dockerfile | 2 +- README.md | 3 ++- docker-compose.yml | 2 +- netbox_acls/__init__.py | 4 ++-- netbox_acls/version.py | 2 +- 10 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.devcontainer/Dockerfile-plugin_dev b/.devcontainer/Dockerfile-plugin_dev index fa0ba06..02b8620 100644 --- a/.devcontainer/Dockerfile-plugin_dev +++ b/.devcontainer/Dockerfile-plugin_dev @@ -1,8 +1,8 @@ -ARG NETBOX_VARIANT=v3.6 +ARG NETBOX_VARIANT=v3.7 FROM netboxcommunity/netbox:${NETBOX_VARIANT} -ARG NETBOX_INITIALIZERS_VARIANT=3.6.* +ARG NETBOX_INITIALIZERS_VARIANT=3.7.* ARG DEBIAN_FRONTEND=noninteractive diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index cfd8f32..2307ecf 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -49,7 +49,7 @@ services: # postgres postgres: - image: postgres:15-alpine + image: postgres:16-alpine env_file: env/postgres.env volumes: - netbox-postgres-data:/var/lib/postgresql/data diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index abed693..241716d 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.4.0 + placeholder: v1.5.0 validations: required: true - type: input attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v3.6.3 + placeholder: v3.7.4 validations: required: true - type: textarea diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index d9e09b4..a848352 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.6.3 + placeholder: v3.7.4 validations: required: true - type: dropdown diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cacec27..fd1d7c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,5 +97,12 @@ Development with this plugin leverges: * NetBox-Docker * Docker-Compose * Makefile for spin up of testing NetBox setup +* Dependabot for dependency version management + +### Cutting Releases + +1. Merge PR (squash) into `dev` branch +2. Merge `dev` into `release` branch +3. Create a release (pypi auto publishes) More Documentation to come. diff --git a/Dockerfile b/Dockerfile index 0c1b348..43a2c3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG NETBOX_VARIANT=v3.6 +ARG NETBOX_VARIANT=v3.7 FROM netboxcommunity/netbox:${NETBOX_VARIANT} diff --git a/README.md b/README.md index 4a89d62..8a72b6b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This plugin provides the following models: - Access Lists - Access List to Interface Assignment -- Access List Rules (abstract model bassis for other rules) +- Access List Rules (abstract model basis for other rules) - Access List Standard Rules - Access List Extended Rules @@ -38,6 +38,7 @@ Each Plugin Version listed below has been tested with its corresponding NetBox V | NetBox Version | Plugin Version | |:--------------:|:--------------:| +| 3.7 | 1.5.0 | | 3.6 | 1.4.0 | | 3.5 | 1.3.0 | | 3.4 | 1.2.2 | diff --git a/docker-compose.yml b/docker-compose.yml index 2edb7b0..5ea2ed2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,7 @@ services: # postgres postgres: - image: postgres:15-alpine + image: postgres:16-alpine env_file: env/postgres.env # redis diff --git a/netbox_acls/__init__.py b/netbox_acls/__init__.py index f82263a..584592f 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.6.0" - max_version = "3.6.99" + min_version = "3.7.0" + max_version = "3.7.99" config = NetBoxACLsConfig diff --git a/netbox_acls/version.py b/netbox_acls/version.py index 3e8d9f9..5b60188 100644 --- a/netbox_acls/version.py +++ b/netbox_acls/version.py @@ -1 +1 @@ -__version__ = "1.4.0" +__version__ = "1.5.0"