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

Version 2.3.0 #61

Merged
merged 14 commits into from
Apr 11, 2024
4 changes: 2 additions & 2 deletions .github/workflows/commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

<!--install-start-->
## Installing
Expand Down
6 changes: 6 additions & 0 deletions docs/configuratiom-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion netbox_config_diff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

__author__ = "Artem Kotik"
__email__ = "[email protected]"
__version__ = "2.2.0"
__version__ = "2.3.0"


class ConfigDiffConfig(PluginConfig):
Expand Down
2 changes: 1 addition & 1 deletion netbox_config_diff/compliance/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="",
Expand Down
2 changes: 1 addition & 1 deletion netbox_config_diff/configurator/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
73 changes: 70 additions & 3 deletions netbox_config_diff/configurator/platforms.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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],
)
2 changes: 1 addition & 1 deletion tests/test_compliance_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading