diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml index 4731536..0effea7 100644 --- a/.github/workflows/commit.yaml +++ b/.github/workflows/commit.yaml @@ -31,7 +31,7 @@ jobs: strategy: max-parallel: 10 matrix: - netbox_version: ["v3.5.9", "v3.6.4"] + netbox_version: ["v3.5.9", "v3.6.9"] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/docs/colliecting-diffs.md b/docs/colliecting-diffs.md index 54ddae6..01c6b15 100644 --- a/docs/colliecting-diffs.md +++ b/docs/colliecting-diffs.md @@ -42,6 +42,17 @@ If you have configs in NetBox DataSource, you can define it, the script instead !!! note Only synced DataSources are acceptable +If in your DataSource config names are different from the hostnames of the devices, you can specify config name with Jinja2 template in `Name template` field. + Reference device with `{{ object }}` variable. + +For example, config name is virtual chassis name plus `config` (`switchname-config`) and your devices names are `switchname1`, `switchname2` and etc. + +You can define Jinja2 template with logic to use virtual chassis name if device is in chassis, else use device name: + +``` +{% if object.virtual_chassis %}{{ object.virtual_chassis.name }}-config{% else %}{{ object.name }}{% endif %} +``` + ![Screenshot of the script](media/screenshots/script.png) ## Results diff --git a/docs/media/screenshots/script.png b/docs/media/screenshots/script.png index b0fe676..498aeb6 100644 Binary files a/docs/media/screenshots/script.png and b/docs/media/screenshots/script.png differ diff --git a/netbox_config_diff/compliance/base.py b/netbox_config_diff/compliance/base.py index 2e2c3f8..fb9702a 100644 --- a/netbox_config_diff/compliance/base.py +++ b/netbox_config_diff/compliance/base.py @@ -9,10 +9,11 @@ from dcim.models import Device, DeviceRole, Site from django.conf import settings from django.db.models import Q -from extras.scripts import MultiObjectVar, ObjectVar +from extras.scripts import MultiObjectVar, ObjectVar, TextVar from jinja2.exceptions import TemplateError from netutils.config.compliance import diff_network_config from utilities.exceptions import AbortScript +from utilities.utils import render_jinja2 from netbox_config_diff.models import ConplianceDeviceDataClass @@ -52,6 +53,11 @@ class ConfigDiffBase(SecretsMixin): }, description="Define synced DataSource, if you want compare configs stored in it wihout connecting to devices", ) + name_template = TextVar( + required=False, + description="Jinja2 template code for the device name in Data source. " + "Reference the object as {{ object }}.", + ) def run_script(self, data: dict) -> None: devices = self.validate_data(data) @@ -155,17 +161,26 @@ def get_devices_with_rendered_configs(self, devices: Iterable[Device]) -> Iterat auth_secondary=auth_secondary, rendered_config=rendered_config, error=error, + device=device, ) 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 self.data["name_template"]: + try: + device_name = render_jinja2(self.data["name_template"], {"object": device.device}).strip() + except Exception as e: + self.log_failure(f"Error in rendering data source name for {device.name}: {e}, using device name.") + device_name = device.name + else: + device_name = device.name + if df := DataFile.objects.filter(source=self.data["data_source"], path__icontains=device_name).first(): if config := df.data_as_string: device.actual_config = config else: device.error = f"Data in file {df} is broken, skiping device {device.name}" else: - device.error = f"Not found file in DataSource for device {device.name}" + device.error = f"Not found file in DataSource for name {device_name}" def get_actual_configs(self, devices: list[ConplianceDeviceDataClass]) -> None: if self.data["data_source"]: diff --git a/netbox_config_diff/models/data_models.py b/netbox_config_diff/models/data_models.py index 43162ce..1567bbc 100644 --- a/netbox_config_diff/models/data_models.py +++ b/netbox_config_diff/models/data_models.py @@ -1,6 +1,7 @@ import traceback from dataclasses import dataclass +from dcim.models import Device from scrapli import AsyncScrapli from netbox_config_diff.choices import ConfigComplianceStatusChoices @@ -113,10 +114,12 @@ def send_to_db(self) -> None: class ConplianceDeviceDataClass(BaseDeviceDataClass): command: str + device: Device | None = None - def __init__(self, command: str, **kwargs) -> None: + def __init__(self, command: str, device: Device, **kwargs) -> None: super().__init__(**kwargs) self.command = command + self.device = device async def get_actual_config(self) -> None: if self.error is not None: diff --git a/tests/conftest.py b/tests/conftest.py index 692d0a9..da9e32f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -90,6 +90,7 @@ def factory(**fields: Unpack["DeviceDataClassData"]) -> "DeviceDataClassData": "password": faker.password(), "auth_strict_key": False, "transport": "asyncssh", + "device": None, } return data | fields