Skip to content

Commit 792f528

Browse files
authored
Version 2.4.0
1 parent d00fdf5 commit 792f528

File tree

18 files changed

+185
-87
lines changed

18 files changed

+185
-87
lines changed

.github/workflows/commit.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
strategy:
3232
max-parallel: 10
3333
matrix:
34-
netbox_version: ["v3.5.9", "v3.6.9", "v3.7.5"]
34+
netbox_version: ["v3.5.9", "v3.6.9", "v3.7.8"]
3535
steps:
3636
- name: Checkout
3737
uses: actions/checkout@v3

docs/colliecting-diffs.md

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,41 @@ After script is done you can find results in `Config Compliances` menu. Each dev
6363

6464
Also result is storing rendered and actual configurations from devices.
6565

66+
Compliance finished with error
67+
68+
![Screenshot of the compliance error](media/screenshots/compliance-error.png)
69+
70+
Render diff between configurations
71+
72+
![Screenshot of diff](media/screenshots/compliance-diff.png)
73+
74+
No diff
75+
76+
![Screenshot of the compliance ok](media/screenshots/compliance-ok.png)
77+
78+
### Patch commands
79+
80+
With [hier_config](https://github.com/netdevops/hier_config) library you are able to take a actual configuration of a network device, compare it to its rendered configuration,
81+
and build the remediation steps necessary to bring a device into spec with its intended configuration.
82+
83+
![Screenshot of the patch commands](media/screenshots/compliance-patch.png)
84+
85+
Supported platforms:
86+
87+
* Arista EOS (arista_eos)
88+
* Cisco IOS-XE (cisco_iosxe)
89+
* Cisco IOS-XR (cisco_iosxr)
90+
* Cisco NX-OS (cisco_nxos)
91+
92+
However, any NOS that utilizes a CLI syntax that is structured in a similar fasion to IOS should work mostly out of the box.
93+
94+
NOS's that utilize a `set` based CLI syntax has been added as experimental functionality:
95+
96+
* Juniper JunOS (juniper_junos)
97+
* VyOS (vyos_vyos)
98+
99+
### Missing/extra
100+
66101
With the help of [netutils](https://github.com/networktocode/netutils) library plugin stores missing and extra config lines.
67102

68103
![Screenshot of the missing/extra lines](media/screenshots/compliance-missing-extra.png)
@@ -81,15 +116,3 @@ Supported platforms for missing/extra lines:
81116
* Nokia SROS (nokia_sros)
82117
* PaloAlto PanOS (paloalto_panos)
83118
* Ruckus FastIron (ruckus_fastiron)
84-
85-
Compliance finished with error
86-
87-
![Screenshot of the compliance error](media/screenshots/compliance-error.png)
88-
89-
Render diff between configurations
90-
91-
![Screenshot of diff](media/screenshots/compliance-diff.png)
92-
93-
No diff
94-
95-
![Screenshot of the compliance ok](media/screenshots/compliance-ok.png)
25.9 KB
Loading

netbox_config_diff/api/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Meta:
2929
"diff",
3030
"rendered_config",
3131
"actual_config",
32+
"patch",
3233
"missing",
3334
"extra",
3435
"created",

netbox_config_diff/compliance/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from netbox_config_diff.models import ConplianceDeviceDataClass
1919

2020
from .secrets import SecretsMixin
21-
from .utils import PLATFORM_MAPPING, CustomChoiceVar, exclude_lines, get_unified_diff
21+
from .utils import PLATFORM_MAPPING, CustomChoiceVar, exclude_lines, get_remediation_commands, get_unified_diff
2222

2323

2424
class ConfigDiffBase(SecretsMixin):
@@ -204,3 +204,6 @@ def get_diff(self, devices: list[ConplianceDeviceDataClass]) -> None:
204204
device.extra = diff_network_config(
205205
cleaned_config, device.rendered_config, PLATFORM_MAPPING[device.platform]
206206
)
207+
device.patch = get_remediation_commands(
208+
device.name, device.platform, cleaned_config, device.rendered_config
209+
)

netbox_config_diff/compliance/utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from django.forms import ChoiceField
55
from extras.scripts import ScriptVariable
6+
from hier_config import Host
67

78
PLATFORM_MAPPING = {
89
"arista_eos": "arista_eos",
@@ -19,6 +20,15 @@
1920
"ruckus_fastiron": "ruckus_fastiron",
2021
}
2122

23+
REMEDIATION_MAPPING = {
24+
"arista_eos": "eos",
25+
"cisco_iosxe": "ios",
26+
"cisco_iosxr": "iosxr",
27+
"cisco_nxos": "nxos",
28+
"juniper_junos": "junos",
29+
"vyos_vyos": "vyos",
30+
}
31+
2232

2333
class CustomChoiceVar(ScriptVariable):
2434
form_field = ChoiceField
@@ -43,3 +53,10 @@ def exclude_lines(text: str, regexs: list) -> str:
4353
for item in regexs:
4454
text = re.sub(item, "", text, flags=re.I | re.M)
4555
return text.strip()
56+
57+
58+
def get_remediation_commands(name: str, platform: str, actual_config: str, rendered_config: str) -> str:
59+
host = Host(hostname=name, os=REMEDIATION_MAPPING.get(platform, "ios"))
60+
host.load_running_config(config_text=actual_config)
61+
host.load_generated_config(config_text=rendered_config)
62+
return host.remediation_config_filtered_text(include_tags={}, exclude_tags={})

netbox_config_diff/configurator/base.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from utilities.utils import NetBoxFakeRequest
1515

1616
from netbox_config_diff.compliance.secrets import SecretsMixin
17-
from netbox_config_diff.compliance.utils import PLATFORM_MAPPING, get_unified_diff
17+
from netbox_config_diff.compliance.utils import PLATFORM_MAPPING, get_remediation_commands, get_unified_diff
1818
from netbox_config_diff.configurator.exceptions import DeviceConfigurationError, DeviceValidationError
1919
from netbox_config_diff.configurator.utils import CustomLogger
2020
from netbox_config_diff.constants import ACCEPTABLE_DRIVERS
@@ -137,6 +137,9 @@ async def _collect_one_diff(self, device: ConfiguratorDeviceDataClass) -> None:
137137
device.extra = diff_network_config(
138138
device.actual_config, device.rendered_config, PLATFORM_MAPPING[device.platform]
139139
)
140+
device.patch = get_remediation_commands(
141+
device.name, device.platform, device.actual_config, device.rendered_config
142+
)
140143
self.logger.log_info(f"Got diff from {device.name}")
141144
except Exception:
142145
error = traceback.format_exc()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.db import migrations, models
2+
3+
4+
class Migration(migrations.Migration):
5+
6+
dependencies = [
7+
("netbox_config_diff", "0008_alter_configcompliance_device"),
8+
]
9+
10+
operations = [
11+
migrations.AddField(
12+
model_name="configcompliance",
13+
name="patch",
14+
field=models.TextField(blank=True),
15+
),
16+
]

netbox_config_diff/models/data_models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class BaseDeviceDataClass:
2323
diff: str = ""
2424
missing: str | None = None
2525
extra: str | None = None
26+
patch: str | None = None
2627
error: str = ""
2728
config_error: str | None = None
2829
auth_strict_key: bool = False
@@ -99,6 +100,7 @@ def to_db(self) -> dict:
99100
"actual_config": self.actual_config or "",
100101
"missing": self.missing or "",
101102
"extra": self.extra or "",
103+
"patch": self.patch or "",
102104
}
103105

104106
def send_to_db(self) -> None:

netbox_config_diff/models/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class ConfigCompliance(AbsoluteURLMixin, ChangeLoggingMixin, models.Model):
4949
extra = models.TextField(
5050
blank=True,
5151
)
52+
patch = models.TextField(
53+
blank=True,
54+
)
5255

5356
objects = RestrictedQuerySet.as_manager()
5457

netbox_config_diff/templates/netbox_config_diff/configcompliance/config.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
<div class="card">
99
<div class="card-header">
1010
<div class="float-end">
11+
{% copy_content config_field %}
1112
<a href="?export=True" class="btn btn-sm btn-primary" role="button">
1213
<i class="mdi mdi-download" aria-hidden="true"></i> Download
1314
</a>
1415
</div>
1516
<h5>{{ header }}</h5>
1617
</div>
1718
{% if config %}
18-
<pre class="card-body">{{ config }}</pre>
19+
<pre class="card-body" id="{{ config_field }}">{{ config }}</pre>
1920
{% else %}
2021
<div class="card-body text-muted">No configuration</div>
2122
{% endif %}

netbox_config_diff/templates/netbox_config_diff/configcompliance/missing_extra.html

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,10 @@
55
{% block content %}
66
<div class="row">
77
<div class="col col-md-6">
8-
<div class="card">
9-
<div class="card-header">
10-
<div class="float-end">
11-
<a href="?export_missing=True" class="btn btn-sm btn-primary" role="button">
12-
<i class="mdi mdi-download" aria-hidden="true"></i> Download
13-
</a>
14-
</div>
15-
<h5>Missing</h5>
16-
</div>
17-
{% if object.missing %}
18-
<pre class="card-body">{{ object.missing }}</pre>
19-
{% else %}
20-
<div class="card-body text-muted">No lines</div>
21-
{% endif %}
22-
</div>
8+
{% include 'netbox_config_diff/inc/commands_card.html' with data=object.missing header='Missing' pre_id='missing' %}
239
</div>
2410
<div class="col col-md-6">
25-
<div class="card">
26-
<div class="card-header">
27-
<div class="float-end">
28-
<a href="?export_extra=True" class="btn btn-sm btn-primary" role="button">
29-
<i class="mdi mdi-download" aria-hidden="true"></i> Download
30-
</a>
31-
</div>
32-
<h5>Extra</h5>
33-
</div>
34-
{% if object.extra %}
35-
<pre class="card-body">{{ object.extra }}</pre>
36-
{% else %}
37-
<div class="card-body text-muted">No lines</div>
38-
{% endif %}
39-
</div>
11+
{% include 'netbox_config_diff/inc/commands_card.html' with data=object.extra header='Extra' pre_id='extra' %}
4012
</div>
4113
</div>
4214
{% endblock %}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% extends "netbox_config_diff/configcompliance.html" %}
2+
3+
{% block title %}{{ object }} - Patch commands{% endblock %}
4+
5+
{% block content %}
6+
<div class="row">
7+
<div class="col col-md-6">
8+
{% include 'netbox_config_diff/inc/commands_card.html' with data=object.patch header='Patch commands' pre_id='patch' %}
9+
</div>
10+
</div>
11+
{% endblock %}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<div class="card">
2+
<div class="card-header">
3+
<div class="float-end">
4+
{% copy_content pre_id %}
5+
<a href="?export_{{ pre_id }}=True" class="btn btn-sm btn-primary" role="button">
6+
<i class="mdi mdi-download" aria-hidden="true"></i> Download
7+
</a>
8+
</div>
9+
<h5>{{ header }}</h5>
10+
</div>
11+
{% if data %}
12+
<pre class="card-body" id="{{ pre_id }}">{{ data }}</pre>
13+
{% else %}
14+
<div class="card-body text-muted">No commands</div>
15+
{% endif %}
16+
</div>

netbox_config_diff/views/base.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from django.http import HttpResponse
2+
from django.shortcuts import render
13
from django.urls import reverse
2-
from netbox.views.generic import ObjectDeleteView, ObjectEditView
4+
from netbox.views.generic import ObjectDeleteView, ObjectEditView, ObjectView
35

46

57
class BaseObjectDeleteView(ObjectDeleteView):
@@ -11,3 +13,40 @@ class BaseObjectEditView(ObjectEditView):
1113
@property
1214
def default_return_url(self) -> str:
1315
return f"plugins:netbox_config_diff:{self.queryset.model._meta.model_name}_list"
16+
17+
18+
class BaseExportView(ObjectView):
19+
def export_parts(self, name, lines, suffix):
20+
response = HttpResponse(lines, content_type="text")
21+
filename = f"{name}_{suffix}.txt"
22+
response["Content-Disposition"] = f'attachment; filename="{filename}"'
23+
return response
24+
25+
26+
class BaseConfigComplianceConfigView(BaseExportView):
27+
config_field = None
28+
template_header = None
29+
30+
def get(self, request, **kwargs):
31+
instance = self.get_object(**kwargs)
32+
context = self.get_extra_context(request, instance)
33+
34+
if request.GET.get("export"):
35+
return self.export_parts(instance.device.name, context["config"], self.config_field)
36+
37+
return render(
38+
request,
39+
self.get_template_name(),
40+
{
41+
"object": instance,
42+
"tab": self.tab,
43+
**context,
44+
},
45+
)
46+
47+
def get_extra_context(self, request, instance):
48+
return {
49+
"header": self.template_header,
50+
"config": getattr(instance, self.config_field),
51+
"config_field": self.config_field,
52+
}

0 commit comments

Comments
 (0)