Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closes #93: Add support for NetBox 4.2 #94

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
strategy:
max-parallel: 10
matrix:
netbox_version: ["v3.7.8", "v4.0.7", "v4.1.1"]
netbox_version: ["v4.0.7", "v4.1.11", "v4.2.2"]
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![NetBox version](https://img.shields.io/badge/NetBox-3.7|4.0|4.1-blue.svg)](https://github.com/netbox-community/netbox)
[![NetBox version](https://img.shields.io/badge/NetBox-4.0|4.1|4.2-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)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
Expand Down Expand Up @@ -48,9 +48,10 @@ This is possible thanks to the scrapli_cfg. Read [Scrapli](https://github.com/sc
|----------------|------------------|
| 3.5 | =>0.1.0, <=2.5.0 |
| 3.6 | =>0.1.0, <=2.6.0 |
| 3.7 | =>0.1.0 |
| 3.7 | =>0.1.0, <=2.8.0 |
| 4.0 | =>2.6.0 |
| 4.1 | =>2.7.0 |
| 4.2 | =>2.9.0 |

<!--install-start-->
## Installing
Expand Down
Binary file modified docs/media/screenshots/compliance-diff.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/compliance-error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/compliance-list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/compliance-missing-extra.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/compliance-ok.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/compliance-patch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-approve-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-approved.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-collecting-diff-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-created.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-diffs-tab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-job-log.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-schedule-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-scheduled.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-unapprove-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/cr-unschedule-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/navbar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/platformsetting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/script-list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/script.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/substitute.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 3 additions & 9 deletions netbox_config_diff/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
from netbox.settings import VERSION

if VERSION.startswith("3."):
from extras.plugins import PluginConfig
else:
from netbox.plugins import PluginConfig

from netbox.plugins import PluginConfig

__author__ = "Artem Kotik"
__email__ = "[email protected]"
__version__ = "2.8.0"
__version__ = "2.9.0"


class ConfigDiffConfig(PluginConfig):
Expand All @@ -20,7 +14,7 @@ class ConfigDiffConfig(PluginConfig):
version = __version__
base_url = "config-diff"
required_settings = ["USERNAME", "PASSWORD"]
min_version = "3.7.0"
min_version = "4.0.0"
default_settings = {
"USER_SECRET_ROLE": "Username",
"PASSWORD_SECRET_ROLE": "Password",
Expand Down
54 changes: 14 additions & 40 deletions netbox_config_diff/api/serializers.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
from dcim.api.nested_serializers import NestedDeviceSerializer, NestedPlatformSerializer
from dcim.api.serializers import DeviceSerializer, PlatformSerializer
from dcim.models import Device
from netbox.api.fields import ChoiceField, SerializedPKRelatedField
from netbox.api.serializers import NetBoxModelSerializer
from netbox.settings import VERSION
from rest_framework import serializers
from rest_framework.serializers import ValidationError
from users.api.serializers import UserSerializer
from utilities.datetime import local_now

from netbox_config_diff.choices import ConfigComplianceStatusChoices, ConfigurationRequestStatusChoices
from netbox_config_diff.constants import ACCEPTABLE_DRIVERS
from netbox_config_diff.models import ConfigCompliance, ConfigurationRequest, PlatformSetting, Substitute

if VERSION.startswith("3."):
from users.api.nested_serializers import NestedUserSerializer
from utilities.utils import local_now
elif VERSION.startswith("4.0"):
from users.api.nested_serializers import NestedUserSerializer
from utilities.datetime import local_now
else:
from users.api.serializers_.nested import NestedUserSerializer
from utilities.datetime import local_now


# TODO: after droping support for NetBox 3.x, delete nested serializers and add brief_fields
class ConfigComplianceSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_config_diff-api:configcompliance-detail")
device = NestedDeviceSerializer()
device = DeviceSerializer(nested=True)
status = ChoiceField(choices=ConfigComplianceStatusChoices)

class Meta:
Expand All @@ -49,7 +39,7 @@ class Meta:

class PlatformSettingSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_config_diff-api:platformsetting-detail")
platform = NestedPlatformSerializer()
platform = PlatformSerializer(nested=True)

class Meta:
model = PlatformSetting
Expand All @@ -67,14 +57,7 @@ class Meta:
"created",
"last_updated",
)


class NestedPlatformSettingSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_config_diff-api:platformsetting-detail")

class Meta:
model = PlatformSetting
fields = ("id", "url", "display", "driver")
brief_fields = ("id", "url", "display", "driver")


class ConfigurationRequestSerializer(NetBoxModelSerializer):
Expand All @@ -83,13 +66,14 @@ class ConfigurationRequestSerializer(NetBoxModelSerializer):
)
devices = SerializedPKRelatedField(
queryset=Device.objects.all(),
serializer=NestedDeviceSerializer,
serializer=DeviceSerializer,
nested=True,
many=True,
)
status = ChoiceField(choices=ConfigurationRequestStatusChoices, read_only=True)
created_by = NestedUserSerializer(read_only=True)
approved_by = NestedUserSerializer(read_only=True)
scheduled_by = NestedUserSerializer(read_only=True)
created_by = UserSerializer(read_only=True, nested=True)
approved_by = UserSerializer(read_only=True, nested=True)
scheduled_by = UserSerializer(read_only=True, nested=True)

class Meta:
model = ConfigurationRequest
Expand All @@ -113,6 +97,7 @@ class Meta:
"last_updated",
)
read_only_fields = ["started", "scheduled", "completed"]
brief_fields = ("id", "url", "display", "status")

def validate(self, data):
if data.get("devices"):
Expand All @@ -136,18 +121,7 @@ def validate(self, data):


class ConfigurationRequestRWSerializer(ConfigurationRequestSerializer):
created_by = NestedUserSerializer()


class NestedConfigurationRequestSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name="plugins-api:netbox_config_diff-api:configurationrequest-detail"
)
status = ChoiceField(choices=ConfigurationRequestStatusChoices)

class Meta:
model = ConfigurationRequest
fields = ("id", "url", "display", "status")
created_by = UserSerializer(nested=True)


class ConfigurationRequestScheduleSerializer(serializers.Serializer):
Expand All @@ -161,7 +135,7 @@ def validate_schedule_at(self, value):

class SubstituteSerializer(NetBoxModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name="plugins-api:netbox_config_diff-api:substitute-detail")
platform_setting = NestedPlatformSettingSerializer()
platform_setting = PlatformSettingSerializer(nested=True)

class Meta:
model = Substitute
Expand Down
13 changes: 2 additions & 11 deletions netbox_config_diff/compliance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,18 @@
from core.models import DataFile, DataSource
from dcim.choices import DeviceStatusChoices
from dcim.models import Device, DeviceRole, Site
from django.conf import settings
from django.db.models import Q
from extras.scripts import MultiObjectVar, ObjectVar, TextVar
from jinja2.exceptions import TemplateError
from netbox.settings import VERSION
from netutils.config.compliance import diff_network_config
from utilities.exceptions import AbortScript
from utilities.jinja2 import render_jinja2

from netbox_config_diff.models import ConplianceDeviceDataClass

from .secrets import SecretsMixin
from .utils import PLATFORM_MAPPING, CustomChoiceVar, exclude_lines, get_remediation_commands, get_unified_diff

if VERSION.startswith("3."):
from utilities.utils import render_jinja2
else:
from utilities.jinja2 import render_jinja2


class ConfigDiffBase(SecretsMixin):
site = ObjectVar(
Expand Down Expand Up @@ -97,10 +91,7 @@ def validate_data(self, data: dict) -> Iterable[Device]:
if data["site"]:
filters["site"] = data["site"]
elif data["role"]:
if settings.VERSION.split(".", 1)[1].startswith("5"):
filters["device_role"] = data["role"]
else:
filters["role"] = data["role"]
filters["role"] = data["role"]
devices = Device.objects.filter(**filters).exclude(
Q(primary_ip4__isnull=True) & Q(primary_ip6__isnull=True),
)
Expand Down
6 changes: 1 addition & 5 deletions netbox_config_diff/compliance/secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
from typing import TYPE_CHECKING

from dcim.models import Device
from netbox.settings import VERSION

if TYPE_CHECKING:
from netbox_secrets.models import Secret

if VERSION.startswith("3."):
from extras.plugins import get_installed_plugins, get_plugin_config
else:
from netbox.plugins import get_installed_plugins, get_plugin_config
from netbox.plugins import get_installed_plugins, get_plugin_config


class SecretsMixin:
Expand Down
7 changes: 1 addition & 6 deletions netbox_config_diff/configurator/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from asgiref.sync import sync_to_async
from dcim.models import Device
from jinja2.exceptions import TemplateError
from netbox.settings import VERSION
from netutils.config.compliance import diff_network_config
from scrapli import AsyncScrapli
from scrapli_cfg.platform.base.async_platform import AsyncScrapliCfgPlatform
from scrapli_cfg.response import ScrapliCfgResponse
from utilities.request import NetBoxFakeRequest

from netbox_config_diff.compliance.secrets import SecretsMixin
from netbox_config_diff.compliance.utils import PLATFORM_MAPPING, get_remediation_commands, get_unified_diff
Expand All @@ -22,11 +22,6 @@

from .factory import AsyncScrapliCfg

if VERSION.startswith("3."):
from utilities.utils import NetBoxFakeRequest
else:
from utilities.request import NetBoxFakeRequest


class Configurator(SecretsMixin):
def __init__(self, devices: Iterable[Device], request: NetBoxFakeRequest) -> None:
Expand Down
12 changes: 0 additions & 12 deletions netbox_config_diff/forms/base.py

This file was deleted.

38 changes: 8 additions & 30 deletions netbox_config_diff/forms/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,23 @@
from django import forms
from django.contrib.auth import get_user_model
from netbox.forms import NetBoxModelBulkEditForm, NetBoxModelFilterSetForm, NetBoxModelForm
from netbox.settings import VERSION
from utilities.datetime import local_now
from utilities.forms.fields import (
DynamicModelChoiceField,
DynamicModelMultipleChoiceField,
TagFilterField,
)
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import DateTimePicker

from netbox_config_diff.choices import ConfigComplianceStatusChoices, ConfigurationRequestStatusChoices
from netbox_config_diff.constants import ACCEPTABLE_DRIVERS
from netbox_config_diff.models import ConfigCompliance, ConfigurationRequest, PlatformSetting, Substitute

from .base import CustomForm

if VERSION.startswith("3."):
from utilities.utils import local_now
else:
from utilities.datetime import local_now
from utilities.forms.rendering import FieldSet


class ConfigComplianceFilterForm(NetBoxModelFilterSetForm):
model = ConfigCompliance
if VERSION.startswith("3."):
fieldsets = ((None, ("q", "device_id", "status")),)
else:
fieldsets = (FieldSet("q", "device_id", "status"),)
fieldsets = (FieldSet("q", "device_id", "status"),)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
Expand Down Expand Up @@ -60,10 +50,7 @@ class Meta:

class PlatformSettingFilterForm(NetBoxModelFilterSetForm):
model = PlatformSetting
if VERSION.startswith("3."):
fieldsets = ((None, ("q", "platform_id", "tag")),)
else:
fieldsets = (FieldSet("q", "filter_id", "tag"),)
fieldsets = (FieldSet("q", "filter_id", "tag"),)
platform_id = DynamicModelMultipleChoiceField(
queryset=Platform.objects.all(),
required=False,
Expand Down Expand Up @@ -91,10 +78,7 @@ class PlatformSettingBulkEditForm(NetBoxModelBulkEditForm):
)

model = PlatformSetting
if VERSION.startswith("3."):
fieldsets = ((None, ("driver", "command", "description", "exclude_regex")),)
else:
fieldsets = (FieldSet("driver", "command", "description", "exclude_regex"),)
fieldsets = (FieldSet("driver", "command", "description", "exclude_regex"),)
nullable_fields = ("description", "exclude_regex")


Expand Down Expand Up @@ -145,10 +129,7 @@ def clean(self):

class ConfigurationRequestFilterForm(NetBoxModelFilterSetForm):
model = ConfigurationRequest
if VERSION.startswith("3."):
fieldsets = ((None, ("q", "created_by_id", "approved_by_id", "scheduled_by_id", "device_id", "status", "tag")),)
else:
fieldsets = (FieldSet("q", "created_by_id", "approved_by_id", "scheduled_by_id", "device_id", "status", "tag"),)
fieldsets = (FieldSet("q", "created_by_id", "approved_by_id", "scheduled_by_id", "device_id", "status", "tag"),)
created_by_id = DynamicModelMultipleChoiceField(
queryset=get_user_model().objects.all(),
required=False,
Expand Down Expand Up @@ -176,7 +157,7 @@ class ConfigurationRequestFilterForm(NetBoxModelFilterSetForm):
tag = TagFilterField(model)


class ConfigurationRequestScheduleForm(CustomForm):
class ConfigurationRequestScheduleForm(forms.ModelForm):
scheduled = forms.DateTimeField(
widget=DateTimePicker(),
label="Schedule at",
Expand Down Expand Up @@ -214,10 +195,7 @@ class Meta:
class SubstituteFilterForm(NetBoxModelFilterSetForm):
model = Substitute
fieldsets = ((None, ("q", "platform_setting_id", "tag")),)
if VERSION.startswith("3."):
fieldsets = ((None, ("q", "platform_setting_id", "tag")),)
else:
fieldsets = (FieldSet("q", "platform_setting_id", "tag"),)
fieldsets = (FieldSet("q", "platform_setting_id", "tag"),)
platform_setting_id = DynamicModelMultipleChoiceField(
queryset=PlatformSetting.objects.all(),
required=False,
Expand Down
7 changes: 1 addition & 6 deletions netbox_config_diff/graphql/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
from netbox.settings import VERSION

if VERSION.startswith("3."):
from .old.schema import schema # noqa
else:
from .new.schema import schema # noqa
from .schema import schema # noqa
Empty file.
Empty file.
Loading
Loading