Skip to content

Commit

Permalink
Version 2.2.0 (#55)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
miaow2 authored Feb 6, 2024
1 parent 7492978 commit fc45a92
Show file tree
Hide file tree
Showing 20 changed files with 234 additions and 130 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,19 @@ python manage.py collectstatic --noinput
Restart NetBox service:

```bash
systemctl restart netbox
systemctl restart netbox netbox-rq
```
<!--install-end-->
<!--usage-start-->
## Usage

Read this [doc](docs/colliecting-diffs.md) about collecting diffs, for configuration management read [this](docs/configuratiom-management.md)
Read this [doc](https://miaow2.github.io/netbox-config-diff/colliecting-diffs/) about collecting diffs, for configuration management read [this](https://miaow2.github.io/netbox-config-diff/configuratiom-management/)

## Video

My presention about plugin at October NetBox community call (19.10.2023).

[![October NetBox community call](https://img.youtube.com/vi/B4uhtYh278o/0.jpg)](https://youtu.be/B4uhtYh278o?t=425)
<!--usage-end-->

## Screenshots
Expand Down
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 2.2.0 (2024-02-06)

* [#47](https://github.com/miaow2/netbox-config-diff/issues/47) Move plugin to separete menu item in navbar and add tab for devices with compliance result
* [#50](https://github.com/miaow2/netbox-config-diff/issues/50) Add template field for device name in DataSource to ConfigDiffScript
* [#53](https://github.com/miaow2/netbox-config-diff/issues/53) Add netbox-rq to installation process docs

## 2.1.0 (2023-10-26)

* [#35](https://github.com/miaow2/netbox-config-diff/issues/35) Add ability to define password for accessing priviliged exec mode
Expand Down
13 changes: 12 additions & 1 deletion docs/colliecting-diffs.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# Usage

Under `Plugins` navbar menu you can find plugin
In navbar serach for `Config Diff Plugin` menu

![Screenshot of navbar](media/screenshots/navbar.png)

Expand Down Expand Up @@ -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
Expand Down
Binary file modified docs/media/screenshots/navbar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/media/screenshots/script.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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.1.0"
__version__ = "2.2.0"


class ConfigDiffConfig(PluginConfig):
Expand Down
21 changes: 18 additions & 3 deletions netbox_config_diff/compliance/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 <code>{{ object }}</code>.",
)

def run_script(self, data: dict) -> None:
devices = self.validate_data(data)
Expand Down Expand Up @@ -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"]:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("dcim", "0181_rename_device_role_device_role"),
("netbox_config_diff", "0007_configurationrequest"),
]

operations = [
migrations.AlterField(
model_name="configcompliance",
name="device",
field=models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="config_compliance",
to="dcim.device",
),
),
]
5 changes: 4 additions & 1 deletion netbox_config_diff/models/data_models.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion netbox_config_diff/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ConfigCompliance(AbsoluteURLMixin, ChangeLoggingMixin, models.Model):
device = models.OneToOneField(
to="dcim.Device",
on_delete=models.CASCADE,
related_name="config_compliamce",
related_name="config_compliance",
)
status = models.CharField(
max_length=50,
Expand Down
51 changes: 26 additions & 25 deletions netbox_config_diff/navigation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from extras.plugins import PluginMenuButton, PluginMenuItem
from extras.plugins import PluginMenu, PluginMenuButton, PluginMenuItem
from utilities.choices import ButtonColorChoices


Expand All @@ -12,35 +12,36 @@ def get_add_button(model: str) -> PluginMenuButton:
)


menu_items = (
PluginMenuItem(
link="plugins:netbox_config_diff:platformsetting_list",
link_text="Platform Settings",
buttons=[get_add_button("platformsetting")],
permissions=["netbox_config_diff.view_platformsetting"],
),
PluginMenuItem(
link="plugins:netbox_config_diff:configcompliance_list",
link_text="Config Compliances",
buttons=[],
permissions=["netbox_config_diff.view_configcompliance"],
),
PluginMenuItem(
link="plugins:netbox_config_diff:configurationrequest_list",
link_text="Configuration Requests",
buttons=[get_add_button("configurationrequest")],
permissions=["netbox_config_diff.view_configurationrequest"],
),
def get_menu_item(model: str, verbose_name: str, add_button: bool = True) -> PluginMenuItem:
return PluginMenuItem(
link=f"plugins:netbox_config_diff:{model}_list",
link_text=verbose_name,
buttons=[get_add_button(model)] if add_button else [],
permissions=[f"netbox_config_diff.view_{model}"],
)


compliance_items = (
get_menu_item("platformsetting", "Platform Settings"),
get_menu_item("configcompliance", "Config Compliances", add_button=False),
)

config_items = (
get_menu_item("configurationrequest", "Configuration Requests"),
get_menu_item("substitute", "Substitutes"),
PluginMenuItem(
link="plugins:netbox_config_diff:configurationrequest_job_list",
link_text="Jobs",
buttons=[],
permissions=["core.view_job"],
),
PluginMenuItem(
link="plugins:netbox_config_diff:substitute_list",
link_text="Substitutes",
buttons=[get_add_button("substitute")],
permissions=["netbox_config_diff.view_substitute"],
)

menu = PluginMenu(
label="Config Diff Plugin",
groups=(
("Compliance", compliance_items),
("Config Management", config_items),
),
icon_class="mdi mdi-vector-difference",
)
Original file line number Diff line number Diff line change
@@ -1,81 +1,13 @@
{% extends "netbox_config_diff/configcompliance/base.html" %}
{% load static %}
{% extends "generic/object.html" %}
{% load buttons %}
{% load perms %}

{% block content %}
<div class="row mb-3">
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">{{ object|meta:"verbose_name"|bettertitle }}</h5>
<div class="card-body">
<table class="table table-hover attr-table">
<tr>
<th scope="row">Device</th>
<td>{{ object.device|linkify }}</td>
</tr>
<tr>
<th scope="row">Status</th>
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
</tr>
</table>
</div>
</div>
{% block controls %}
<div class="controls">
<div class="control-group">
{% if request.user|can_delete:object %}
{% delete_button object %}
{% endif %}
</div>
{% if object.error %}
<div class="col col-md-6">
<div class="card">
<h5 class="card-header">Error</h5>
<div class="card-body">
<pre class="block">{{ object.error }}</pre>
</div>
</div>
</div>
{% endif %}
</div>
{% if object.diff %}
<div class="row mb-3">
<div class="col col-md-12">
<div class="card">
<h5 class="card-header">Diff</h5>
<div class="card-body" id="diffElement"></div>
</div>
</div>
</div>
{% endif %}
{% endblock content %}

{% block javascript %}
<script type="text/javascript" src="{% static 'netbox_config_diff/diff2html-ui.min.js' %}"></script>
<script type="text/javascript">
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
const colorMode = localStorage.getItem("netbox-color-mode");
if (colorMode === 'dark') {
link.href = `{% static 'netbox_config_diff/diff2html.dark.min.css' %}`
} else {
link.href = `{% static 'netbox_config_diff/diff2html.min.css' %}`
};
document.head.appendChild(link);
document.addEventListener('DOMContentLoaded', () => {
var configuration = {
drawFileList: false,
fileListToggle: false,
fileListStartVisible: false,
fileContentToggle: false,
matching: 'lines',
outputFormat: 'side-by-side',
synchronisedScroll: true,
highlight: true,
renderNothingWhenEmpty: false,
stickyFileHeaders: false,
drawFileList: false,
};
const jsonDiff = `{{ object.diff|safe }}`;
var targetElement = document.getElementById('diffElement');
var diff2htmlUi = new Diff2HtmlUI(targetElement, jsonDiff, configuration);
diff2htmlUi.draw();
document.querySelector(".d2h-file-header").remove();
diff2htmlUi.highlightCode();
});
</script>
{% endblock javascript %}
{% endblock controls %}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% extends "netbox_config_diff/configcompliance/base.html" %}
{% extends "netbox_config_diff/configcompliance.html" %}

{% block title %}{{ object }} - {{ header }}{% endblock %}

Expand Down
Loading

0 comments on commit fc45a92

Please sign in to comment.