From d00fdf525b38cb93cba8afd58750b345d1803b71 Mon Sep 17 00:00:00 2001 From: Artem Kotik Date: Thu, 11 Apr 2024 21:44:13 +0800 Subject: [PATCH] Version 2.3.0 (#61) * Change URLs in Usage to docs * Update project description * Add video presentation * Closes #50: Add template field to ConfigDiffScript (#51) * Add name template for searching in DataSource * Add docs for templating files name in DataSource * Closes #47: Move plugin to separete menu item in navbar (#52) * Move plugin into separate menu item * Add tab for devices with their config compliance * Closes #53: Add netbox-rq to installation process docs * Changelog for 2.2.0 version * Handle junipers templates with set commands (#58) * Reverse columns in diff (#59) * Add python 3.12 and Netbox 3.7.5 in CI * Add warning about using set commands in Juniper * Version 2.3.0 --- .github/workflows/commit.yaml | 4 +- README.md | 2 +- docs/configuratiom-management.md | 6 ++ netbox_config_diff/__init__.py | 2 +- netbox_config_diff/compliance/utils.py | 2 +- netbox_config_diff/configurator/base.py | 2 +- netbox_config_diff/configurator/platforms.py | 73 +++++++++++++++++++- tests/test_compliance_utils.py | 2 +- 8 files changed, 83 insertions(+), 10 deletions(-) diff --git a/.github/workflows/commit.yaml b/.github/workflows/commit.yaml index 0effea7..23b3ca3 100644 --- a/.github/workflows/commit.yaml +++ b/.github/workflows/commit.yaml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 10 matrix: - python: ["3.10", "3.11"] + python: ["3.10", "3.11", "3.12"] steps: - name: Checkout uses: actions/checkout@v3 @@ -31,7 +31,7 @@ jobs: strategy: max-parallel: 10 matrix: - netbox_version: ["v3.5.9", "v3.6.9"] + netbox_version: ["v3.5.9", "v3.6.9", "v3.7.5"] steps: - name: Checkout uses: actions/checkout@v3 diff --git a/README.md b/README.md index 0d7795c..dc1ebe2 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ This is possible thanks to the scrapli_cfg. Read [Scrapli](https://github.com/sc | NetBox Version | Plugin Version | |----------------|----------------| -| 3.5, 3.6 | =>0.1.0 | +| 3.5, 3.6, 3.7 | =>0.1.0 | ## Installing diff --git a/docs/configuratiom-management.md b/docs/configuratiom-management.md index 52d9a42..a28413e 100644 --- a/docs/configuratiom-management.md +++ b/docs/configuratiom-management.md @@ -12,6 +12,12 @@ Supported platforms: Plugin using [scrapli-cfg](https://github.com/scrapli/scrapli_cfg) for this feature. +!!! warning + If you use Juniper and render config in set commands, please read next info. + Plugin uses `load override` command to load config to a device, set commands load with `load set`. + With `load set` commnad you can't replace all config, because this command uses `merge` action. + So, please, be careful when using set commands in rendering config and pushig it with plugin, it can have unexpected side effects. + ## Substitutes If you render not full configuration, it is acceptable to pull missing config sections from the actual configuration to render full configuration. diff --git a/netbox_config_diff/__init__.py b/netbox_config_diff/__init__.py index f687423..b4d2835 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.2.0" +__version__ = "2.3.0" class ConfigDiffConfig(PluginConfig): diff --git a/netbox_config_diff/compliance/utils.py b/netbox_config_diff/compliance/utils.py index c0aa4af..1da8fc7 100644 --- a/netbox_config_diff/compliance/utils.py +++ b/netbox_config_diff/compliance/utils.py @@ -30,8 +30,8 @@ def __init__(self, choices, *args, **kwargs): def get_unified_diff(rendered_config: str, actual_config: str, device: str) -> str: diff = unified_diff( - rendered_config.strip().splitlines(), actual_config.splitlines(), + rendered_config.strip().splitlines(), fromfiledate=device, tofiledate=device, lineterm="", diff --git a/netbox_config_diff/configurator/base.py b/netbox_config_diff/configurator/base.py index a6635a2..38bc94b 100644 --- a/netbox_config_diff/configurator/base.py +++ b/netbox_config_diff/configurator/base.py @@ -126,7 +126,7 @@ async def _collect_one_diff(self, device: ConfiguratorDeviceDataClass) -> None: ) device.rendered_config = rendered_config else: - actual_config = await conn.get_config() + actual_config = await conn.get_config(config_template=device.rendered_config) device.actual_config = conn.clean_config(actual_config.result) device.diff = get_unified_diff(device.rendered_config, device.actual_config, device.name) diff --git a/netbox_config_diff/configurator/platforms.py b/netbox_config_diff/configurator/platforms.py index d4ffe97..45eb5a2 100644 --- a/netbox_config_diff/configurator/platforms.py +++ b/netbox_config_diff/configurator/platforms.py @@ -1,5 +1,5 @@ import re -from typing import Pattern +from typing import Any, Pattern from scrapli_cfg.exceptions import TemplateError from scrapli_cfg.platform.core.arista_eos import AsyncScrapliCfgEOS @@ -94,13 +94,17 @@ async def render_substituted_config( """ self.logger.info("fetching configuration and replacing with provided substitutes") - source_config = await self.get_config(source=source) + source_config = await self.get_config(config_template=config_template, source=source) return source_config, self._render_substituted_config( config_template=config_template, substitutes=substitutes, source_config=source_config.result, ) + async def get_config(self, **kwargs) -> ScrapliCfgResponse: + kwargs.pop("config_template", None) + return await super().get_config(**kwargs) + class CustomAsyncScrapliCfgEOS(CustomScrapliCfg, AsyncScrapliCfgEOS): pass @@ -119,4 +123,67 @@ class CustomAsyncScrapliCfgNXOS(CustomScrapliCfg, AsyncScrapliCfgNXOS): class CustomAsyncScrapliCfgJunos(CustomScrapliCfg, AsyncScrapliCfgJunos): - pass + is_set_config = False + + async def get_config(self, config_template: str, source: str = "running") -> ScrapliCfgResponse: + response = self._pre_get_config(source=source) + + command = "show configuration" + if re.findall(r"^set\s+", config_template, flags=re.I | re.M): + self.is_set_config = True + command += " | display set" + + if self._in_configuration_session is True: + config_result = await self.conn.send_config(config=f"run {command}") + else: + config_result = await self.conn.send_command(command=command) + + return self._post_get_config( + response=response, + source=source, + scrapli_responses=[config_result], + result=config_result.result, + ) + + async def load_config(self, config: str, replace: bool = False, **kwargs: Any) -> ScrapliCfgResponse: + """ + Load configuration to a device + + Supported kwargs: + set: bool indicating config is a "set" style config (ignored if replace is True) + + Args: + config: string of the configuration to load + replace: replace the configuration or not, if false configuration will be loaded as a + merge operation + kwargs: additional kwargs that the implementing classes may need for their platform, + see above for junos supported kwargs + + Returns: + ScrapliCfgResponse: response object + + Raises: + N/A + + """ + response = self._pre_load_config(config=config) + + config = self._prepare_load_config(config=config, replace=replace) + + config_result = await self.conn.send_config(config=config, privilege_level="root_shell") + + if self.is_set_config is True: + load_config = f"load set {self.filesystem}{self.candidate_config_filename}" + else: + if self._replace is True: + load_config = f"load override {self.filesystem}{self.candidate_config_filename}" + else: + load_config = f"load merge {self.filesystem}{self.candidate_config_filename}" + + load_result = await self.conn.send_config(config=load_config) + self._in_configuration_session = True + + return self._post_load_config( + response=response, + scrapli_responses=[config_result, load_result], + ) diff --git a/tests/test_compliance_utils.py b/tests/test_compliance_utils.py index 4178a26..b69b285 100644 --- a/tests/test_compliance_utils.py +++ b/tests/test_compliance_utils.py @@ -56,4 +56,4 @@ def test_exclude_lines(regex: str, expected: str) -> None: ids=["diff", "no diff"], ) def test_get_unified_diff(render: str, actual: str, expected: str) -> None: - assert get_unified_diff(render, actual, "test-1") == expected + assert get_unified_diff(actual, render, "test-1") == expected