diff --git a/docs/changelog.md b/docs/changelog.md index 14f44e4..ae4a828 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,18 @@ # Changelog +## 2.8.0 (2024-11-10) + +* [#76](https://github.com/miaow2/netbox-config-diff/issues/76) Add in NetBox 4.0 and higher some statistics for diff +* [#81](https://github.com/miaow2/netbox-config-diff/issues/81) Fix error in rendering config with several substitutes +* [#84](https://github.com/miaow2/netbox-config-diff/issues/84) Strip multiple empty lines in configs + +## 2.7.0 (2024-09-29) + +* [#79](https://github.com/miaow2/netbox-config-diff/issues/79) Add support for NetBox 4.1 + +This release drops support for NetBox 3.6. + ## 2.6.0 (2024-07-14) * [#62](https://github.com/miaow2/netbox-config-diff/issues/62) Add support for NetBox 4.0 diff --git a/docs/colliecting-diffs.md b/docs/colliecting-diffs.md index 565c0bf..e9a9bef 100644 --- a/docs/colliecting-diffs.md +++ b/docs/colliecting-diffs.md @@ -42,6 +42,9 @@ If you have configs in NetBox DataSource, you can define it, the script instead !!! note Only synced DataSources are acceptable +!!! note + Diff replaces sequences of 3 or more empty lines with one empty line + 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. diff --git a/netbox_config_diff/__init__.py b/netbox_config_diff/__init__.py index 2600791..eddf5f7 100644 --- a/netbox_config_diff/__init__.py +++ b/netbox_config_diff/__init__.py @@ -8,7 +8,7 @@ __author__ = "Artem Kotik" __email__ = "miaow2@yandex.ru" -__version__ = "2.7.0" +__version__ = "2.8.0" class ConfigDiffConfig(PluginConfig): diff --git a/netbox_config_diff/compliance/utils.py b/netbox_config_diff/compliance/utils.py index 7451a71..8c89f54 100644 --- a/netbox_config_diff/compliance/utils.py +++ b/netbox_config_diff/compliance/utils.py @@ -40,8 +40,8 @@ def __init__(self, choices, *args, **kwargs): def get_unified_diff(rendered_config: str, actual_config: str, device: str) -> str: diff = unified_diff( - actual_config.splitlines(), - rendered_config.strip().splitlines(), + re.sub("\n{3,}", "\n", actual_config).splitlines(), + re.sub("\n{3,}", "\n", rendered_config).strip().splitlines(), fromfiledate=device, tofiledate=device, lineterm="", @@ -60,3 +60,16 @@ def get_remediation_commands(name: str, platform: str, actual_config: str, rende host.load_running_config(config_text=actual_config) host.load_generated_config(config_text=rendered_config) return host.remediation_config_filtered_text(include_tags={}, exclude_tags={}) + + +def get_diff_statistics(diff: str) -> tuple[int, int]: + lines_added = 0 + lines_deleted = 0 + + for line in diff.splitlines(): + if line.startswith("+") and not line.startswith("+++"): + lines_added += 1 + elif line.startswith("-") and not line.startswith("---"): + lines_deleted += 1 + + return lines_added, lines_deleted diff --git a/netbox_config_diff/configurator/base.py b/netbox_config_diff/configurator/base.py index 0106ffe..5eda8ad 100644 --- a/netbox_config_diff/configurator/base.py +++ b/netbox_config_diff/configurator/base.py @@ -137,12 +137,15 @@ async def _collect_one_diff(self, device: ConfiguratorDeviceDataClass) -> None: device.diff = get_unified_diff(device.rendered_config, device.actual_config, device.name) self.logger.add_diff(device.name, diff=device.diff) - device.missing = diff_network_config( - device.rendered_config, device.actual_config, PLATFORM_MAPPING[device.platform] - ) - device.extra = diff_network_config( - device.actual_config, device.rendered_config, PLATFORM_MAPPING[device.platform] - ) + try: + device.missing = diff_network_config( + device.rendered_config, device.actual_config, PLATFORM_MAPPING[device.platform] + ) + device.extra = diff_network_config( + device.actual_config, device.rendered_config, PLATFORM_MAPPING[device.platform] + ) + except Exception as e: + self.logger.log_warning(f"Unable to get missing/extra commands for {device.name}: {e}") device.patch = get_remediation_commands( device.name, device.platform, device.actual_config, device.rendered_config ) diff --git a/netbox_config_diff/configurator/platforms.py b/netbox_config_diff/configurator/platforms.py index 45eb5a2..994faee 100644 --- a/netbox_config_diff/configurator/platforms.py +++ b/netbox_config_diff/configurator/platforms.py @@ -46,15 +46,15 @@ def _render_substituted_config( replace_sections = [(name, re.search(pattern=pattern, string=source_config)) for name, pattern in substitutes] - rendered_config = "" + rendered_config = config_template for name, replace_section in replace_sections: if not replace_section: - msg = f"substitution pattern {name} was unable to find a match in the target config" " source" + msg = f"substitution pattern {name} was unable to find a match in the target configsource" self.logger.critical(msg) raise TemplateError(msg) replace_group = replace_section.group() - rendered_config = config_template.replace(f"{{{{ {name} }}}}", replace_group) + rendered_config = rendered_config.replace(f"{{{{ {name} }}}}", replace_group) # remove any totally empty lines (from bad regex, or just device spitting out lines w/ # nothing on it diff --git a/netbox_config_diff/templates/netbox_config_diff/configcompliance/data.html b/netbox_config_diff/templates/netbox_config_diff/configcompliance/data.html index 3b576df..82ca341 100644 --- a/netbox_config_diff/templates/netbox_config_diff/configcompliance/data.html +++ b/netbox_config_diff/templates/netbox_config_diff/configcompliance/data.html @@ -35,7 +35,14 @@
Error
-
Diff
+

Diff + {% if version|first != "3" and statistics %} +
+ {{ statistics.0 }} line(s) missing, + {{ statistics.1 }} extra line(s) +
+ {% endif %} +

diff --git a/netbox_config_diff/views/compliance.py b/netbox_config_diff/views/compliance.py index aa88b0b..12aa43e 100644 --- a/netbox_config_diff/views/compliance.py +++ b/netbox_config_diff/views/compliance.py @@ -5,6 +5,7 @@ from netbox.views import generic from utilities.views import ViewTab, register_model_view +from netbox_config_diff.compliance.utils import get_diff_statistics from netbox_config_diff.filtersets import ConfigComplianceFilterSet, PlatformSettingFilterSet from netbox_config_diff.forms import ( ConfigComplianceFilterForm, @@ -25,10 +26,14 @@ class ConfigComplianceView(generic.ObjectView): template_name = "netbox_config_diff/configcompliance/data.html" def get_extra_context(self, request, instance): + statistics = None + if instance.diff: + statistics = get_diff_statistics(instance.diff) return { "instance": instance, "base_template": self.base_template, "version": VERSION, + "statistics": statistics, }