From 2d090a46d3d2cf57d9390a38081e145328bfa750 Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Tue, 24 Oct 2023 18:13:20 +0400 Subject: [PATCH 1/8] Refactor models --- netbox_config_diff/compliance/base.py | 17 +++++++------- netbox_config_diff/configurator/base.py | 22 ++++++++++-------- netbox_config_diff/models/__init__.py | 11 +++++++++ netbox_config_diff/models/base.py | 10 ++++++++ .../models.py => models/data_models.py} | 20 +++++++++++----- netbox_config_diff/{ => models}/models.py | 23 +++++-------------- 6 files changed, 63 insertions(+), 40 deletions(-) create mode 100644 netbox_config_diff/models/__init__.py create mode 100644 netbox_config_diff/models/base.py rename netbox_config_diff/{compliance/models.py => models/data_models.py} (93%) rename netbox_config_diff/{ => models}/models.py (90%) diff --git a/netbox_config_diff/compliance/base.py b/netbox_config_diff/compliance/base.py index 0d60752..24ef968 100644 --- a/netbox_config_diff/compliance/base.py +++ b/netbox_config_diff/compliance/base.py @@ -14,7 +14,8 @@ from netutils.config.compliance import diff_network_config from utilities.exceptions import AbortScript -from .models import DeviceDataClass +from netbox_config_diff.models import ConplianceDeviceDataClass + from .secrets import SecretsMixin from .utils import PLATFORM_MAPPING, CustomChoiceVar, exclude_lines, get_unified_diff @@ -106,12 +107,12 @@ def validate_data(self, data: dict) -> Iterable[Device]: self.log_info(f"Working with device(s): {', '.join(d.name for d in devices)}") return devices - def update_in_db(self, devices: list[DeviceDataClass]) -> None: + def update_in_db(self, devices: list[ConplianceDeviceDataClass]) -> None: for device in devices: self.log_results(device) device.send_to_db() - def log_results(self, device: DeviceDataClass) -> None: + def log_results(self, device: ConplianceDeviceDataClass) -> None: if device.error: self.log_failure(f"{device.name} errored") elif device.diff: @@ -119,7 +120,7 @@ def log_results(self, device: DeviceDataClass) -> None: else: self.log_success(f"{device.name} no diff") - def get_devices_with_rendered_configs(self, devices: Iterable[Device]) -> Iterator[DeviceDataClass]: + def get_devices_with_rendered_configs(self, devices: Iterable[Device]) -> Iterator[ConplianceDeviceDataClass]: self.check_netbox_secrets() self.substitutes = {} for device in devices: @@ -142,7 +143,7 @@ def get_devices_with_rendered_configs(self, devices: Iterable[Device]) -> Iterat if substitutes := device.platform.platform_setting.substitutes.all(): self.substitutes[platform] = [s.regexp for s in substitutes] - yield DeviceDataClass( + yield ConplianceDeviceDataClass( pk=device.pk, name=device.name, mgmt_ip=str(device.primary_ip.address.ip), @@ -155,7 +156,7 @@ def get_devices_with_rendered_configs(self, devices: Iterable[Device]) -> Iterat error=error, ) - def get_config_from_datasource(self, devices: list[DeviceDataClass]) -> None: + def get_config_from_datasource(self, devices: list[ConplianceDeviceDataClass]) -> None: for device in devices: if df := DataFile.objects.filter(source=self.data["data_source"], path__icontains=device.name).first(): if config := df.data_as_string: @@ -165,14 +166,14 @@ def get_config_from_datasource(self, devices: list[DeviceDataClass]) -> None: else: device.error = f"Not found file in DataSource for device {device.name}" - def get_actual_configs(self, devices: list[DeviceDataClass]) -> None: + def get_actual_configs(self, devices: list[ConplianceDeviceDataClass]) -> None: if self.data["data_source"]: self.get_config_from_datasource(devices) else: loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather(*(d.get_actual_config() for d in devices))) - def get_diff(self, devices: list[DeviceDataClass]) -> None: + def get_diff(self, devices: list[ConplianceDeviceDataClass]) -> None: for device in devices: if device.error is not None: continue diff --git a/netbox_config_diff/configurator/base.py b/netbox_config_diff/configurator/base.py index 3f3913e..48bc6eb 100644 --- a/netbox_config_diff/configurator/base.py +++ b/netbox_config_diff/configurator/base.py @@ -13,12 +13,12 @@ from scrapli_cfg.response import ScrapliCfgResponse from utilities.utils import NetBoxFakeRequest -from netbox_config_diff.compliance.models import DeviceDataClass from netbox_config_diff.compliance.secrets import SecretsMixin from netbox_config_diff.compliance.utils import PLATFORM_MAPPING, get_unified_diff from netbox_config_diff.configurator.exceptions import DeviceConfigurationError, DeviceValidationError from netbox_config_diff.configurator.utils import CustomLogger from netbox_config_diff.constants import ACCEPTABLE_DRIVERS +from netbox_config_diff.models import ConfiguratorDeviceDataClass from .factory import AsyncScrapliCfg @@ -27,9 +27,9 @@ class Configurator(SecretsMixin): def __init__(self, devices: Iterable[Device], request: NetBoxFakeRequest) -> None: self.devices = devices self.request = request - self.unprocessed_devices: set[DeviceDataClass] = set() - self.processed_devices: set[DeviceDataClass] = set() - self.failed_devices: set[DeviceDataClass] = set() + self.unprocessed_devices: set[ConfiguratorDeviceDataClass] = set() + self.processed_devices: set[ConfiguratorDeviceDataClass] = set() + self.failed_devices: set[ConfiguratorDeviceDataClass] = set() self.substitutes: dict[str, list] = {} self.logger = CustomLogger() self.connections: dict[str, AsyncScrapliCfgPlatform] = {} @@ -59,7 +59,7 @@ def validate_devices(self) -> None: error = "Define config template for device" self.logger.log_failure(error) - d = DeviceDataClass( + d = ConfiguratorDeviceDataClass( pk=device.pk, name=device.name, mgmt_ip=str(device.primary_ip.address.ip), @@ -115,7 +115,7 @@ async def _collect_diffs(self) -> None: await asyncio.gather(*(self._collect_one_diff(d) for d in self.unprocessed_devices)) await self.update_diffs() - async def _collect_one_diff(self, device: DeviceDataClass) -> None: + async def _collect_one_diff(self, device: ConfiguratorDeviceDataClass) -> None: self.logger.log_info(f"Collecting diff on {device.name}") try: conn = self.connections[device.name] @@ -161,7 +161,7 @@ async def _push_configs(self) -> None: devices=", ".join(f"{d.name}: {d.config_error}" for d in self.failed_devices), ) - async def _push_one_config(self, device: DeviceDataClass) -> None: + async def _push_one_config(self, device: ConfiguratorDeviceDataClass) -> None: self.logger.log_info(f"Push config to {device.name}") try: conn = self.connections[device.name] @@ -184,7 +184,11 @@ async def _push_one_config(self, device: DeviceDataClass) -> None: self.failed_devices.add(device) async def abort_config( - self, operation: str, conn: AsyncScrapliCfgPlatform, response: ScrapliCfgResponse, device: DeviceDataClass + self, + operation: str, + conn: AsyncScrapliCfgPlatform, + response: ScrapliCfgResponse, + device: ConfiguratorDeviceDataClass, ) -> None: self.logger.log_failure(f"Failed to {operation} config on {device.name}: {response.result}") device.config_error = response.result @@ -197,7 +201,7 @@ async def rollback(self) -> None: self.logger.log_info(f"Rollback config: {', '.join(d.name for d in self.processed_devices)}") await asyncio.gather(*(self._rollback_one(d) for d in self.processed_devices)) - async def _rollback_one(self, device: DeviceDataClass) -> None: + async def _rollback_one(self, device: ConfiguratorDeviceDataClass) -> None: conn = self.connections[device.name] await conn.load_config(config=device.actual_config, replace=True) await conn.commit_config() diff --git a/netbox_config_diff/models/__init__.py b/netbox_config_diff/models/__init__.py new file mode 100644 index 0000000..1ddddd2 --- /dev/null +++ b/netbox_config_diff/models/__init__.py @@ -0,0 +1,11 @@ +from .data_models import ConfiguratorDeviceDataClass, ConplianceDeviceDataClass +from .models import ConfigCompliance, ConfigurationRequest, PlatformSetting, Substitute + +__all__ = ( + "ConfigCompliance", + "ConfigurationRequest", + "ConfiguratorDeviceDataClass", + "ConplianceDeviceDataClass", + "PlatformSetting", + "Substitute", +) diff --git a/netbox_config_diff/models/base.py b/netbox_config_diff/models/base.py new file mode 100644 index 0000000..ddb682e --- /dev/null +++ b/netbox_config_diff/models/base.py @@ -0,0 +1,10 @@ +from django.db import models +from django.urls import reverse + + +class AbsoluteURLMixin(models.Model): + class Meta: + abstract = True + + def get_absolute_url(self): + return reverse(f"plugins:netbox_config_diff:{self._meta.model_name}", args=[self.pk]) diff --git a/netbox_config_diff/compliance/models.py b/netbox_config_diff/models/data_models.py similarity index 93% rename from netbox_config_diff/compliance/models.py rename to netbox_config_diff/models/data_models.py index 1ddaa26..b726a31 100644 --- a/netbox_config_diff/compliance/models.py +++ b/netbox_config_diff/models/data_models.py @@ -4,18 +4,18 @@ from scrapli import AsyncScrapli from netbox_config_diff.choices import ConfigComplianceStatusChoices -from netbox_config_diff.models import ConfigCompliance + +from .models import ConfigCompliance @dataclass -class DeviceDataClass: +class BaseDeviceDataClass: pk: int name: str mgmt_ip: str platform: str username: str password: str - command: str | None = None exclude_regex: str | None = None rendered_config: str | None = None actual_config: str | None = None @@ -25,14 +25,12 @@ class DeviceDataClass: error: str = "" config_error: str | None = None auth_strict_key: bool = False + auth_secondary: str | None = None transport: str = "asyncssh" def __str__(self) -> str: return self.name - def __hash__(self) -> int: - return hash(self.name) - def to_scrapli(self) -> dict: return { "host": self.mgmt_ip, @@ -40,6 +38,7 @@ def to_scrapli(self) -> dict: "auth_password": self.password, "platform": self.platform, "auth_strict_key": self.auth_strict_key, + "auth_secondary": self.auth_secondary, "transport": self.transport, "transport_options": { "asyncssh": { @@ -111,6 +110,10 @@ def send_to_db(self) -> None: except ConfigCompliance.DoesNotExist: ConfigCompliance.objects.create(**self.to_db()) + +class ConplianceDeviceDataClass(BaseDeviceDataClass): + command: str + async def get_actual_config(self) -> None: if self.error is not None: return @@ -123,3 +126,8 @@ async def get_actual_config(self) -> None: self.actual_config = result.result except Exception: self.error = traceback.format_exc() + + +class ConfiguratorDeviceDataClass(BaseDeviceDataClass): + def __hash__(self) -> int: + return hash(self.name) diff --git a/netbox_config_diff/models.py b/netbox_config_diff/models/models.py similarity index 90% rename from netbox_config_diff/models.py rename to netbox_config_diff/models/models.py index 0e4c4e9..46228af 100644 --- a/netbox_config_diff/models.py +++ b/netbox_config_diff/models/models.py @@ -5,7 +5,6 @@ from django.conf import settings from django.core.validators import RegexValidator from django.db import models -from django.urls import reverse from django.utils import timezone from django.utils.module_loading import import_string from django.utils.translation import gettext as _ @@ -18,8 +17,10 @@ from netbox_config_diff.choices import ConfigComplianceStatusChoices, ConfigurationRequestStatusChoices +from .base import AbsoluteURLMixin -class ConfigCompliance(ChangeLoggingMixin, models.Model): + +class ConfigCompliance(AbsoluteURLMixin, ChangeLoggingMixin, models.Model): device = models.OneToOneField( to="dcim.Device", on_delete=models.CASCADE, @@ -57,9 +58,6 @@ class Meta: def __str__(self) -> str: return self.device.name - def get_absolute_url(self): - return reverse("plugins:netbox_config_diff:configcompliance", args=[self.pk]) - def get_status_color(self) -> str: return ConfigComplianceStatusChoices.colors.get(self.status) @@ -72,7 +70,7 @@ def update(self, commit: bool = False, **kwargs) -> None: self.save() -class PlatformSetting(NetBoxModel): +class PlatformSetting(AbsoluteURLMixin, NetBoxModel): description = models.CharField( max_length=200, blank=True, @@ -107,11 +105,8 @@ class Meta: def __str__(self) -> str: return f"{self.platform} {self.driver}" - def get_absolute_url(self): - return reverse("plugins:netbox_config_diff:platformsetting", args=[self.pk]) - -class ConfigurationRequest(JobsMixin, PrimaryModel): +class ConfigurationRequest(AbsoluteURLMixin, JobsMixin, PrimaryModel): created_by = models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, @@ -161,9 +156,6 @@ class Meta: def __str__(self) -> str: return f"CR #{self.pk}" - def get_absolute_url(self): - return reverse("plugins:netbox_config_diff:configurationrequest", args=[self.pk]) - def get_status_color(self) -> str: return ConfigurationRequestStatusChoices.colors.get(self.status) @@ -210,7 +202,7 @@ def terminate(self, job: Job, status: str = ConfigurationRequestStatusChoices.CO self.save() -class Substitute(NetBoxModel): +class Substitute(AbsoluteURLMixin, NetBoxModel): platform_setting = models.ForeignKey( to="netbox_config_diff.PlatformSetting", on_delete=models.CASCADE, @@ -247,6 +239,3 @@ class Meta: def __str__(self) -> str: return self.name - - def get_absolute_url(self): - return reverse("plugins:netbox_config_diff:substitute", args=[self.pk]) From 6104ebd6bc6cdece7aab8489babd6710ea9b139a Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Tue, 24 Oct 2023 19:07:15 +0400 Subject: [PATCH 2/8] Fix tests --- tests/conftest.py | 6 +++--- tests/test_compliance.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6c44751..692d0a9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,7 +6,7 @@ from typing_extensions import Unpack from netbox_config_diff.compliance.base import ConfigDiffBase -from netbox_config_diff.compliance.models import DeviceDataClass +from netbox_config_diff.models import ConplianceDeviceDataClass from tests.factories import DataSourceFactory @@ -97,6 +97,6 @@ def factory(**fields: Unpack["DeviceDataClassData"]) -> "DeviceDataClassData": @pytest.fixture() -def devicedataclass_data(devicedataclass_factory: "DeviceDataClassDataFactory") -> DeviceDataClass: +def devicedataclass_data(devicedataclass_factory: "DeviceDataClassDataFactory") -> ConplianceDeviceDataClass: data = devicedataclass_factory() - return DeviceDataClass(**data) + return ConplianceDeviceDataClass(**data) diff --git a/tests/test_compliance.py b/tests/test_compliance.py index e111ab1..5dc2dc1 100644 --- a/tests/test_compliance.py +++ b/tests/test_compliance.py @@ -4,8 +4,7 @@ from dcim.models import Device from utilities.exceptions import AbortScript -from netbox_config_diff.compliance.models import DeviceDataClass -from netbox_config_diff.models import ConfigCompliance +from netbox_config_diff.models import ConfigCompliance, ConplianceDeviceDataClass from tests.factories import ConfigComplianceFactory, DeviceFactory, PlatformSettingFactory if TYPE_CHECKING: @@ -144,7 +143,7 @@ def test_devicedataclass_to_db( devicedataclass_factory: "DeviceDataClassDataFactory", diff: str, error: str, status: str ) -> None: data = devicedataclass_factory(**{"diff": diff, "error": error}) - d = DeviceDataClass(**data) + d = ConplianceDeviceDataClass(**data) assert d.to_db() == { "device_id": d.pk, From 383656ac458b085de7fa1f715ddcac8e0abef053 Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Tue, 24 Oct 2023 19:26:04 +0400 Subject: [PATCH 3/8] Fix tests --- netbox_config_diff/models/data_models.py | 4 ++++ tests/test_compliance.py | 1 + 2 files changed, 5 insertions(+) diff --git a/netbox_config_diff/models/data_models.py b/netbox_config_diff/models/data_models.py index b726a31..43162ce 100644 --- a/netbox_config_diff/models/data_models.py +++ b/netbox_config_diff/models/data_models.py @@ -114,6 +114,10 @@ def send_to_db(self) -> None: class ConplianceDeviceDataClass(BaseDeviceDataClass): command: str + def __init__(self, command: str, **kwargs) -> None: + super().__init__(**kwargs) + self.command = command + async def get_actual_config(self) -> None: if self.error is not None: return diff --git a/tests/test_compliance.py b/tests/test_compliance.py index 5dc2dc1..04f2ff8 100644 --- a/tests/test_compliance.py +++ b/tests/test_compliance.py @@ -90,6 +90,7 @@ def test_devicedataclass_to_scrapli(devicedataclass_data: "DeviceDataClassData") "auth_password": devicedataclass_data.password, "platform": devicedataclass_data.platform, "auth_strict_key": devicedataclass_data.auth_strict_key, + "auth_secondary": devicedataclass_data.auth_secondary, "transport": devicedataclass_data.transport, "transport_options": { "asyncssh": { From 86fb7bf95a1441d25deb37b5e02fb1bca889ba50 Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Wed, 25 Oct 2023 16:34:38 +0400 Subject: [PATCH 4/8] Add return_url for views --- netbox_config_diff/views/base.py | 13 +++++++++++++ netbox_config_diff/views/compliance.py | 8 +++++--- netbox_config_diff/views/configuration.py | 12 +++++++----- 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 netbox_config_diff/views/base.py diff --git a/netbox_config_diff/views/base.py b/netbox_config_diff/views/base.py new file mode 100644 index 0000000..43cc8de --- /dev/null +++ b/netbox_config_diff/views/base.py @@ -0,0 +1,13 @@ +from django.urls import reverse +from netbox.views.generic import ObjectDeleteView, ObjectEditView + + +class BaseObjectDeleteView(ObjectDeleteView): + def get_return_url(self, request, obj=None): + return reverse(f"plugins:netbox_config_diff:{self.queryset.model._meta.model_name}_list") + + +class BaseObjectEditView(ObjectEditView): + @property + def default_return_url(self) -> str: + return f"plugins:netbox_config_diff:{self.queryset.model._meta.model_name}_list" diff --git a/netbox_config_diff/views/compliance.py b/netbox_config_diff/views/compliance.py index f6cfd40..fbe5c95 100644 --- a/netbox_config_diff/views/compliance.py +++ b/netbox_config_diff/views/compliance.py @@ -14,6 +14,8 @@ from netbox_config_diff.models import ConfigCompliance, PlatformSetting from netbox_config_diff.tables import ConfigComplianceTable, PlatformSettingTable +from .base import BaseObjectDeleteView, BaseObjectEditView + class BaseConfigComplianceConfigView(generic.ObjectView): config_field = None @@ -119,7 +121,7 @@ class ConfigComplianceListView(generic.ObjectListView): @register_model_view(ConfigCompliance, "delete") -class ConfigComplianceDeleteView(generic.ObjectDeleteView): +class ConfigComplianceDeleteView(BaseObjectDeleteView): queryset = ConfigCompliance.objects.all() @@ -142,13 +144,13 @@ class PlatformSettingListView(generic.ObjectListView): @register_model_view(PlatformSetting, "edit") -class PlatformSettingEditView(generic.ObjectEditView): +class PlatformSettingEditView(BaseObjectEditView): queryset = PlatformSetting.objects.all() form = PlatformSettingForm @register_model_view(PlatformSetting, "delete") -class PlatformSettingDeleteView(generic.ObjectDeleteView): +class PlatformSettingDeleteView(BaseObjectDeleteView): queryset = PlatformSetting.objects.all() diff --git a/netbox_config_diff/views/configuration.py b/netbox_config_diff/views/configuration.py index ce8ba4f..f4beefb 100644 --- a/netbox_config_diff/views/configuration.py +++ b/netbox_config_diff/views/configuration.py @@ -30,6 +30,8 @@ from netbox_config_diff.models import ConfigurationRequest, Substitute from netbox_config_diff.tables import ConfigurationRequestTable, SubstituteTable +from .base import BaseObjectDeleteView, BaseObjectEditView + @register_model_view(ConfigurationRequest) class ConfigurationRequestView(generic.ObjectView): @@ -55,7 +57,7 @@ class ConfigurationRequestListView(generic.ObjectListView): @register_model_view(ConfigurationRequest, "edit") -class ConfigurationRequestEditView(generic.ObjectEditView): +class ConfigurationRequestEditView(BaseObjectEditView): queryset = ConfigurationRequest.objects.all() form = ConfigurationRequestForm @@ -86,7 +88,7 @@ def get(self, request, *args, **kwargs): @register_model_view(ConfigurationRequest, "delete") -class ConfigurationRequestDeleteView(generic.ObjectDeleteView): +class ConfigurationRequestDeleteView(BaseObjectDeleteView): queryset = ConfigurationRequest.objects.all() @@ -173,7 +175,7 @@ def post(self, request, pk): @register_model_view(ConfigurationRequest, "schedule") -class ConfigurationRequestScheduleView(generic.ObjectEditView): +class ConfigurationRequestScheduleView(BaseObjectEditView): queryset = ConfigurationRequest.objects.all() form = ConfigurationRequestScheduleForm @@ -320,11 +322,11 @@ class SubstituteListView(generic.ObjectListView): @register_model_view(Substitute, "edit") -class SubstituteEditView(generic.ObjectEditView): +class SubstituteEditView(BaseObjectEditView): queryset = Substitute.objects.all() form = SubstituteForm @register_model_view(Substitute, "delete") -class SubstituteDeleteView(generic.ObjectDeleteView): +class SubstituteDeleteView(BaseObjectDeleteView): queryset = Substitute.objects.all() From 17447ce7992470a410c8f6ec9e6a175d85397a68 Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Wed, 25 Oct 2023 16:51:49 +0400 Subject: [PATCH 5/8] Migrate to ruff format --- .github/workflows/commit.yaml | 4 ++-- .pre-commit-config.yaml | 9 +++------ README.md | 1 - pyproject.toml | 15 ++++----------- requirements/dev.txt | 3 +-- 5 files changed, 10 insertions(+), 22 deletions(-) diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml index 0f0c142..244dc79 100644 --- a/.github/workflows/commit.yaml +++ b/.github/workflows/commit.yaml @@ -21,8 +21,8 @@ jobs: python -m pip install --upgrade pip python -m pip install --upgrade setuptools wheel python -m pip install -r requirements/dev.txt - - name: Run black - run: black --check --diff --color . + - name: Run ruff format + run: ruff format - name: Run ruff run: ruff . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abcf5ca..68ed49a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,13 +5,10 @@ repos: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 23.1.0 - hooks: - - id: black - args: [--check, --diff, --color, .] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.280 + rev: v0.1.2 hooks: - id: ruff args: [netbox_config_diff] + - id: ruff-format + exclude: migrations diff --git a/README.md b/README.md index e26e271..6caa8d0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ [![NetBox version](https://img.shields.io/badge/NetBox-3.5|3.6-blue.svg)](https://github.com/netbox-community/netbox) [![Supported Versions](https://img.shields.io/pypi/pyversions/netbox-config-diff.svg)](https://pypi.org/project/netbox-config-diff/) [![PyPI version](https://badge.fury.io/py/netbox-config-diff.svg)](https://badge.fury.io/py/netbox-config-diff) -[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CI](https://github.com/miaow2/netbox-config-diff/actions/workflows/commit.yaml/badge.svg?branch=develop)](https://github.com/miaow2/netbox-config-diff/actions) diff --git a/pyproject.toml b/pyproject.toml index 24b96ea..969c550 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,17 +51,6 @@ dependencies = {file = ["requirements/base.txt"]} optional-dependencies.dev = { file = ["requirements/dev.txt"] } optional-dependencies.test = { file = ["requirements/test.txt"] } -[tool.black] -line-length = 120 -skip-string-normalization = true -exclude = ''' -( - /( - migrations - )/ -) -''' - [tool.ruff] exclude = ["migrations", "__pycache__"] select = ["C", "E", "F", "I"] @@ -69,6 +58,10 @@ ignore = ["C901"] line-length = 120 target-version = "py310" +[tool.ruff.format] +line-length = 120 +exclude =['migrations'] + [tool.pytest.ini_options] addopts = "-p no:warnings -vv --no-migrations" DJANGO_SETTINGS_MODULE = "netbox.settings" diff --git a/requirements/dev.txt b/requirements/dev.txt index 844be90..e06651c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,2 +1 @@ -black==23.10.0 -ruff==0.1.0 +ruff==0.1.2 From 1e46ca7c023081ec240a84872846e48b93f3711c Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Wed, 25 Oct 2023 16:53:17 +0400 Subject: [PATCH 6/8] Remove tool.ruff.format in pyproject --- pyproject.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 969c550..0ae9cf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,10 +58,6 @@ ignore = ["C901"] line-length = 120 target-version = "py310" -[tool.ruff.format] -line-length = 120 -exclude =['migrations'] - [tool.pytest.ini_options] addopts = "-p no:warnings -vv --no-migrations" DJANGO_SETTINGS_MODULE = "netbox.settings" From 04297ca24efc2d7b964e6012619e5844dfcdc4be Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Wed, 25 Oct 2023 17:10:26 +0400 Subject: [PATCH 7/8] Fix ruff format --- .github/workflows/commit.yaml | 2 +- netbox_config_diff/compliance/secrets.py | 39 +++++++++++++++--------- netbox_config_diff/configurator/utils.py | 2 +- netbox_config_diff/models/models.py | 11 +++---- netbox_config_diff/navigation.py | 15 --------- 5 files changed, 31 insertions(+), 38 deletions(-) diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml index 244dc79..4731536 100644 --- a/.github/workflows/commit.yaml +++ b/.github/workflows/commit.yaml @@ -22,7 +22,7 @@ jobs: python -m pip install --upgrade setuptools wheel python -m pip install -r requirements/dev.txt - name: Run ruff format - run: ruff format + run: ruff format . - name: Run ruff run: ruff . diff --git a/netbox_config_diff/compliance/secrets.py b/netbox_config_diff/compliance/secrets.py index 998311c..862adae 100644 --- a/netbox_config_diff/compliance/secrets.py +++ b/netbox_config_diff/compliance/secrets.py @@ -15,9 +15,9 @@ class SecretsMixin: def get_session_key(self) -> None: if "netbox_secrets_sessionid" in self.request.COOKIES: - self.session_key = base64.b64decode(self.request.COOKIES['netbox_secrets_sessionid']) + self.session_key = base64.b64decode(self.request.COOKIES["netbox_secrets_sessionid"]) elif "HTTP_X_SESSION_KEY" in self.request.META: - self.session_key = base64.b64decode(self.request.META['HTTP_X_SESSION_KEY']) + self.session_key = base64.b64decode(self.request.META["HTTP_X_SESSION_KEY"]) else: self.session_key = None @@ -45,24 +45,33 @@ def get_secret(self, secret: "Secret") -> str | None: return None return secret.plaintext - def get_credentials(self, device: Device) -> tuple[str, str]: - if self.netbox_secrets_installed: - if secret := device.secrets.filter(role__name=self.user_role).first(): - if value := self.get_secret(secret): - username = value - if secret := device.secrets.filter(role__name=self.password_role).first(): - if value := self.get_secret(secret): - password = value - return username, password + def get_credentials(self, device: Device) -> tuple[str, str, str]: + if not self.netbox_secrets_installed: + return self.username, self.password, self.auth_secondary - return self.username, self.password + if secret := device.secrets.filter(role__name=self.user_role).first(): + username = value if (value := self.get_secret(secret)) else self.username + else: + username = self.username + if secret := device.secrets.filter(role__name=self.password_role).first(): + password = value if (value := self.get_secret(secret)) else self.password + else: + password = self.password + if secret := device.secrets.filter(role__name=self.auth_secondary_role).first(): + auth_secondary = value if (value := self.get_secret(secret)) else self.auth_secondary + else: + auth_secondary = self.auth_secondary + + return username, password, auth_secondary def check_netbox_secrets(self) -> None: if "netbox_secrets" in get_installed_plugins(): self.get_master_key() self.user_role = get_plugin_config("netbox_config_diff", "USER_SECRET_ROLE") self.password_role = get_plugin_config("netbox_config_diff", "PASSWORD_SECRET_ROLE") + self.auth_secondary_role = get_plugin_config("netbox_config_diff", "SECOND_AUTH_SECRET_ROLE") self.netbox_secrets_installed = True - else: - self.username = get_plugin_config("netbox_config_diff", "USERNAME") - self.password = get_plugin_config("netbox_config_diff", "PASSWORD") + + self.username = get_plugin_config("netbox_config_diff", "USERNAME") + self.password = get_plugin_config("netbox_config_diff", "PASSWORD") + self.auth_secondary = get_plugin_config("netbox_config_diff", "AUTH_SECONDARY") diff --git a/netbox_config_diff/configurator/utils.py b/netbox_config_diff/configurator/utils.py index c556f32..cf751cb 100644 --- a/netbox_config_diff/configurator/utils.py +++ b/netbox_config_diff/configurator/utils.py @@ -15,7 +15,7 @@ def _log(self, message: str, log_level: str | None = None) -> None: raise Exception(f"Unknown logging level: {log_level}") if log_level is None: log_level = LogLevelChoices.LOG_DEFAULT - self.log_data.append((timezone.now().strftime('%Y-%m-%d %H:%M:%S'), log_level, message)) + self.log_data.append((timezone.now().strftime("%Y-%m-%d %H:%M:%S"), log_level, message)) def log(self, message: str) -> None: self._log(message, log_level=LogLevelChoices.LOG_DEFAULT) diff --git a/netbox_config_diff/models/models.py b/netbox_config_diff/models/models.py index 46228af..4bb0dfd 100644 --- a/netbox_config_diff/models/models.py +++ b/netbox_config_diff/models/models.py @@ -110,21 +110,21 @@ class ConfigurationRequest(AbsoluteURLMixin, JobsMixin, PrimaryModel): created_by = models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, - related_name='+', + related_name="+", blank=True, null=True, ) approved_by = models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, - related_name='+', + related_name="+", blank=True, null=True, ) scheduled_by = models.ForeignKey( to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, - related_name='+', + related_name="+", blank=True, null=True, ) @@ -213,12 +213,12 @@ class Substitute(AbsoluteURLMixin, NetBoxModel): unique=True, validators=( RegexValidator( - regex=r'^[a-z0-9_]+$', + regex=r"^[a-z0-9_]+$", message=_("Only alphanumeric characters and underscores are allowed."), flags=re.IGNORECASE, ), RegexValidator( - regex=r'__', + regex=r"__", message=_("Double underscores are not permitted in names."), flags=re.IGNORECASE, inverse_match=True, @@ -226,7 +226,6 @@ class Substitute(AbsoluteURLMixin, NetBoxModel): ), ) description = models.CharField( - verbose_name=_('description'), max_length=200, blank=True, ) diff --git a/netbox_config_diff/navigation.py b/netbox_config_diff/navigation.py index 0e08eb1..ddada5d 100644 --- a/netbox_config_diff/navigation.py +++ b/netbox_config_diff/navigation.py @@ -1,6 +1,3 @@ -from django import forms -from extras.dashboard.utils import register_widget -from extras.dashboard.widgets import DashboardWidget, WidgetConfigForm from extras.plugins import PluginMenuButton, PluginMenuItem from utilities.choices import ButtonColorChoices @@ -47,15 +44,3 @@ def get_add_button(model: str) -> PluginMenuButton: permissions=["netbox_config_diff.view_substitute"], ), ) - - -@register_widget -class ReminderWidget(DashboardWidget): - default_title = 'Reminder' - description = 'Add a virtual sticky note' - - class ConfigForm(WidgetConfigForm): - content = forms.CharField(widget=forms.Textarea()) - - def render(self, request): - return self.config.get('content') From 715283a78749d5ab86721240dd527157dfeec979 Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Thu, 26 Oct 2023 10:54:48 +0400 Subject: [PATCH 8/8] Closes #35: Add ability to define password for accessing priviliged exec mode --- README.md | 9 +++++++++ docs/changelog.md | 12 ++++++++++++ docs/media/screenshots/cr-completed.png | Bin 0 -> 48412 bytes docs/media/screenshots/cr-created.png | Bin 0 -> 42240 bytes docs/secrets.md | 4 +++- netbox_config_diff/__init__.py | 3 ++- netbox_config_diff/compliance/base.py | 3 ++- netbox_config_diff/configurator/base.py | 3 ++- 8 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 docs/media/screenshots/cr-completed.png create mode 100644 docs/media/screenshots/cr-created.png diff --git a/README.md b/README.md index 6caa8d0..2955f8b 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ PLUGINS_CONFIG = { "netbox_config_diff": { "USERNAME": "foo", "PASSWORD": "bar", + "AUTH_SECONDARY": "foobar", # define here password for accessing Privileged EXEC mode, this variable is optional }, } ``` @@ -119,6 +120,14 @@ No diff ![Screenshot of the compliance ok](docs/media/screenshots/compliance-ok.png) +Configuration request + +![Screenshot of the CR](docs/media/screenshots/cr-created.png) + +Completed Configuration request + +![Screenshot of the completed CR](docs/media/screenshots/cr-completed.png) + ## Credits Based on the NetBox plugin tutorial: diff --git a/docs/changelog.md b/docs/changelog.md index 4204b52..1766866 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,17 @@ # Changelog +## 2.1.0 (2023-10-26) + +* [#35](https://github.com/miaow2/netbox-config-diff/issues/35) Add ability to define password for accessing priviliged exec mode +* [#37](https://github.com/miaow2/netbox-config-diff/issues/37) Add `DeviceRole` field to `CollectDiffScript` +* [#38](https://github.com/miaow2/netbox-config-diff/issues/38) Remove config template filter for devices filed in forms +* [#39](https://github.com/miaow2/netbox-config-diff/issues/39) Add `Status` field to `CollectDiffScript` +* [#43](https://github.com/miaow2/netbox-config-diff/issues/43) `ConfigDiffScript` does not create empty changelog entries + +## 2.0.1 (2023-10-22) + +* [#33](https://github.com/miaow2/netbox-config-diff/issues/33) Fix failing migrations on fresh database install + ## 2.0.0 (2023-10-18) * [#25](https://github.com/miaow2/netbox-config-diff/issues/25) Add configuration management diff --git a/docs/media/screenshots/cr-completed.png b/docs/media/screenshots/cr-completed.png new file mode 100644 index 0000000000000000000000000000000000000000..368a75c7905caa5005d4b21d3f178dcd3bb33570 GIT binary patch literal 48412 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‘MB%Z(W)Xsh-8K=t%nLz1D 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
H_J4jkfW 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 DoU01f literal 0 HcmV?d00001 diff --git a/docs/media/screenshots/cr-created.png b/docs/media/screenshots/cr-created.png new file mode 100644 index 0000000000000000000000000000000000000000..5733189b25c41c753ed8aa6142420231f0c558fd GIT binary patch literal 42240 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/secrets.md b/docs/secrets.md index e15ebc6..978f0f7 100644 --- a/docs/secrets.md +++ b/docs/secrets.md @@ -4,7 +4,8 @@ You can store credentials for devices authentification in NetBox secrets [plugin Read NetBox secrets docs for more info. -In plugin variables define secrets roles for username (`USER_SECRET_ROLE`) and password (`PASSWORD_SECRET_ROLE`). +In plugin variables define secrets roles for username (`USER_SECRET_ROLE`), password (`PASSWORD_SECRET_ROLE`) and + password (`SECOND_AUTH_SECRET_ROLE`) for Privileged EXEC mode. Default values for this variables are: @@ -13,6 +14,7 @@ PLUGINS_CONFIG = { "netbox_config_diff": { "USER_SECRET_ROLE": "Username", "PASSWORD_SECRET_ROLE": "Password", + "SECOND_AUTH_SECRET_ROLE": "Second Auth", }, } ``` diff --git a/netbox_config_diff/__init__.py b/netbox_config_diff/__init__.py index 22a4b4b..3cd5c9e 100644 --- a/netbox_config_diff/__init__.py +++ b/netbox_config_diff/__init__.py @@ -2,7 +2,7 @@ __author__ = "Artem Kotik" __email__ = "miaow2@yandex.ru" -__version__ = "2.0.1" +__version__ = "2.1.0" class ConfigDiffConfig(PluginConfig): @@ -18,6 +18,7 @@ class ConfigDiffConfig(PluginConfig): default_settings = { "USER_SECRET_ROLE": "Username", "PASSWORD_SECRET_ROLE": "Password", + "SECOND_AUTH_SECRET_ROLE": "Second Auth", } diff --git a/netbox_config_diff/compliance/base.py b/netbox_config_diff/compliance/base.py index 24ef968..2e2c3f8 100644 --- a/netbox_config_diff/compliance/base.py +++ b/netbox_config_diff/compliance/base.py @@ -124,7 +124,7 @@ def get_devices_with_rendered_configs(self, devices: Iterable[Device]) -> Iterat self.check_netbox_secrets() self.substitutes = {} for device in devices: - username, password = self.get_credentials(device) + username, password, auth_secondary = self.get_credentials(device) rendered_config = None error = None context_data = device.get_config_context() @@ -152,6 +152,7 @@ def get_devices_with_rendered_configs(self, devices: Iterable[Device]) -> Iterat exclude_regex=device.platform.platform_setting.exclude_regex, username=username, password=password, + auth_secondary=auth_secondary, rendered_config=rendered_config, error=error, ) diff --git a/netbox_config_diff/configurator/base.py b/netbox_config_diff/configurator/base.py index 48bc6eb..a6635a2 100644 --- a/netbox_config_diff/configurator/base.py +++ b/netbox_config_diff/configurator/base.py @@ -37,7 +37,7 @@ def __init__(self, devices: Iterable[Device], request: NetBoxFakeRequest) -> Non def validate_devices(self) -> None: self.check_netbox_secrets() for device in self.devices: - username, password = self.get_credentials(device) + username, password, auth_secondary = self.get_credentials(device) if device.platform.platform_setting is None: self.logger.log_warning(f"Skipping {device}, add PlatformSetting for {device.platform} platform") elif device.platform.platform_setting.driver not in ACCEPTABLE_DRIVERS: @@ -66,6 +66,7 @@ def validate_devices(self) -> None: platform=device.platform.platform_setting.driver, username=username, password=password, + auth_secondary=auth_secondary, rendered_config=rendered_config, error=error, )