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.2.0 #55

Merged
merged 7 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading