Skip to content

Commit 855e92f

Browse files
authored
feat: plugin configuration further improvements (#34)
* fix: clear diode permissions Signed-off-by: Michal Fiedorowicz <[email protected]> * feat: allow configuration of diode usernames via PLUGINS_CONFIG Signed-off-by: Michal Fiedorowicz <[email protected]> * feat: update settings with diode users details Signed-off-by: Michal Fiedorowicz <[email protected]> * feat: update ingestion logs view Signed-off-by: Michal Fiedorowicz <[email protected]> * update settings copy for env vars Signed-off-by: Michal Fiedorowicz <[email protected]> * create users using usernames provided with plugin config Signed-off-by: Michal Fiedorowicz <[email protected]> * python fmt Signed-off-by: Michal Fiedorowicz <[email protected]> * update migration for creating setting using diode target from plugin config Signed-off-by: Michal Fiedorowicz <[email protected]> * use diode_target_override from plugin config or from database block modifying diode target via GUI if diode_target_override is present Signed-off-by: Michal Fiedorowicz <[email protected]> * migration to rename legacy users new usernames obtained from plugin config (either default ones or PLUGINS_CONFIG) Signed-off-by: Michal Fiedorowicz <[email protected]> * fix 0001_initial migration Signed-off-by: Michal Fiedorowicz <[email protected]> * tidy up 0002_setting migration Signed-off-by: Michal Fiedorowicz <[email protected]> * adjust unit tests to plugin config changes Signed-off-by: Michal Fiedorowicz <[email protected]> * tidy up forms Signed-off-by: Michal Fiedorowicz <[email protected]> * disable PLUGINS_CONFIG for docker by default Signed-off-by: Michal Fiedorowicz <[email protected]> * update README Signed-off-by: Michal Fiedorowicz <[email protected]> * handle missing diode users Signed-off-by: Michal Fiedorowicz <[email protected]> * unit tests for handling missing diode users Signed-off-by: Michal Fiedorowicz <[email protected]> * rename reconciled to changes in ingestion logs metrics gauges Signed-off-by: Michal Fiedorowicz <[email protected]> --------- Signed-off-by: Michal Fiedorowicz <[email protected]>
1 parent 9d26ec7 commit 855e92f

File tree

14 files changed

+414
-112
lines changed

14 files changed

+414
-112
lines changed

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,30 @@ PLUGINS = [
4040
]
4141
```
4242

43-
Also in your `configuration.py` file, add `netbox_diode_plugin`to the `PLUGINS_CONFIG` dictionary, e.g.:
43+
Also in your `configuration.py` file, in order to customise the plugin settings, add `netbox_diode_plugin`to the
44+
`PLUGINS_CONFIG` dictionary, e.g.:
4445

4546
```python
4647
PLUGINS_CONFIG = {
4748
"netbox_diode_plugin": {
48-
"diode_target": "grpc://localhost:8080/diode", # The Diode gRPC target for communication with Diode server, default: "grpc://localhost:8080/diode"
49-
"disallow_diode_target_override": True, # Disallow the Diode target to be overridden by the user, default: False
50-
}
49+
# Diode gRPC target for communication with Diode server
50+
"diode_target_override": "grpc://localhost:8080/diode",
51+
52+
# User allowed for Diode to NetBox communication
53+
"diode_to_netbox_username": "diode-to-netbox",
54+
55+
# User allowed for NetBox to Diode communication
56+
"netbox_to_diode_username": "netbox-to-diode",
57+
58+
# # User allowed for data ingestion
59+
"diode_username": "diode-ingestion",
60+
},
5161
}
5262
```
5363

64+
Note: Once you customise usernames with PLUGINS_CONFIG during first installation, you should not change or remove them
65+
later on. Doing so will cause the plugin to stop working properly.
66+
5467
Restart NetBox services to load the plugin:
5568

5669
```

docker/netbox/configuration/plugins.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,18 @@
66

77
PLUGINS = ["netbox_diode_plugin"]
88

9-
PLUGINS_CONFIG = {
10-
"netbox_diode_plugin": {
11-
"diode_target": "grpc://localhost:8080/diode", # The Diode gRPC target for communication with Diode server, default: "grpc://localhost:8080/diode"
12-
"disallow_diode_target_override": False, # Disallow the Diode target to be overridden by the user, default: False
13-
}
14-
}
9+
# PLUGINS_CONFIG = {
10+
# "netbox_diode_plugin": {
11+
# # Diode gRPC target for communication with Diode server
12+
# "diode_target_override": "grpc://localhost:8080/diode",
13+
#
14+
# # User allowed for Diode to NetBox communication
15+
# "diode_to_netbox_username": "diode-to-netbox",
16+
#
17+
# # User allowed for NetBox to Diode communication
18+
# "netbox_to_diode_username": "netbox-to-diode",
19+
#
20+
# # User allowed for data ingestion
21+
# "diode_username": "diode-ingestion",
22+
# },
23+
# }

netbox_diode_plugin/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@ class NetBoxDiodePluginConfig(PluginConfig):
1616
version = version_semver()
1717
base_url = "diode"
1818
min_version = "3.7.2"
19+
default_settings = {
20+
# Default Diode gRPC target for communication with Diode server
21+
"diode_target": "grpc://localhost:8080/diode",
22+
23+
# User allowed for Diode to NetBox communication
24+
"diode_to_netbox_username": "diode-to-netbox",
25+
26+
# User allowed for NetBox to Diode communication
27+
"netbox_to_diode_username": "netbox-to-diode",
28+
29+
# User allowed for data ingestion
30+
"diode_username": "diode-ingestion",
31+
}
1932

2033

2134
config = NetBoxDiodePluginConfig

netbox_diode_plugin/forms.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# !/usr/bin/env python
22
# Copyright 2024 NetBox Labs Inc
33
"""Diode NetBox Plugin - Forms."""
4-
from django.conf import settings as netbox_settings
54
from netbox.forms import NetBoxModelForm
5+
from netbox.plugins import get_plugin_config
66
from utilities.forms.rendering import FieldSet
77

88
from netbox_diode_plugin.models import Setting
@@ -29,12 +29,12 @@ def __init__(self, *args, **kwargs):
2929
"""Initialize the form."""
3030
super().__init__(*args, **kwargs)
3131

32-
disallow_diode_target_override = netbox_settings.PLUGINS_CONFIG.get(
33-
"netbox_diode_plugin", {}
34-
).get("disallow_diode_target_override", False)
32+
diode_target_override = get_plugin_config(
33+
"netbox_diode_plugin", "diode_target_override"
34+
)
3535

36-
if disallow_diode_target_override:
36+
if diode_target_override:
3737
self.fields["diode_target"].disabled = True
3838
self.fields["diode_target"].help_text = (
39-
"This field is not allowed to be overridden."
39+
"This field is not allowed to be modified."
4040
)

netbox_diode_plugin/migrations/0001_initial.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import os
66

77
from django.apps import apps as django_apps
8-
from django.conf import settings
8+
from django.conf import settings as netbox_settings
99
from django.contrib.contenttypes.management import create_contenttypes
1010
from django.db import migrations, models
1111
from users.models import Token as NetBoxToken
1212

13+
from netbox_diode_plugin.plugin_config import get_diode_usernames
14+
1315

1416
# Read secret from file
1517
def _read_secret(secret_name, default=None):
@@ -22,8 +24,10 @@ def _read_secret(secret_name, default=None):
2224
return f.readline().strip()
2325

2426

25-
def _create_user_with_token(apps, username, group, is_superuser: bool = False):
26-
User = apps.get_model(settings.AUTH_USER_MODEL)
27+
def _create_user_with_token(
28+
apps, user_category, username, group, is_superuser: bool = False
29+
):
30+
User = apps.get_model(netbox_settings.AUTH_USER_MODEL)
2731
"""Create a user with the given username and API key if it does not exist."""
2832
try:
2933
user = User.objects.get(username=username)
@@ -38,7 +42,7 @@ def _create_user_with_token(apps, username, group, is_superuser: bool = False):
3842
Token = apps.get_model("users", "Token")
3943

4044
if not Token.objects.filter(user=user).exists():
41-
key = f"{username}_API_KEY"
45+
key = f"{user_category.upper()}_API_KEY"
4246
api_key = _read_secret(key.lower(), os.getenv(key))
4347
if api_key is None:
4448
api_key = NetBoxToken.generate_key()
@@ -49,18 +53,18 @@ def _create_user_with_token(apps, username, group, is_superuser: bool = False):
4953

5054
def configure_plugin(apps, schema_editor):
5155
"""Configure the plugin."""
52-
diode_to_netbox_username = "DIODE_TO_NETBOX"
53-
netbox_to_diode_username = "NETBOX_TO_DIODE"
54-
diode_username = "DIODE"
55-
5656
Group = apps.get_model("users", "Group")
5757
group, _ = Group.objects.get_or_create(name="diode")
5858

59-
diode_to_netbox_user = _create_user_with_token(
60-
apps, diode_to_netbox_username, group
61-
)
62-
_ = _create_user_with_token(apps, netbox_to_diode_username, group, True)
63-
_ = _create_user_with_token(apps, diode_username, group)
59+
diode_to_netbox_user_id = None
60+
61+
for user_category, username in get_diode_usernames().items():
62+
is_superuser = user_category in ("netbox_to_diode",)
63+
user = _create_user_with_token(
64+
apps, user_category, username, group, is_superuser
65+
)
66+
if user_category == "diode_to_netbox":
67+
diode_to_netbox_user_id = user.id
6468

6569
app_config = django_apps.get_app_config("netbox_diode_plugin")
6670

@@ -78,8 +82,7 @@ def configure_plugin(apps, schema_editor):
7882
actions=["add", "view"],
7983
)
8084

81-
permission.groups.set([group.id])
82-
permission.users.set([diode_to_netbox_user.id])
85+
permission.users.set([diode_to_netbox_user_id])
8386
permission.object_types.set([diode_plugin_object_type.id])
8487

8588

netbox_diode_plugin/migrations/0002_setting.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33
"""Diode Netbox Plugin - Database migrations."""
44

55
import utilities.json
6-
from django.conf import settings as netbox_settings
76
from django.db import migrations, models
7+
from netbox.plugins import get_plugin_config
88

99

1010
def create_settings_entity(apps, schema_editor):
1111
"""Create a Setting entity."""
1212
Setting = apps.get_model("netbox_diode_plugin", "Setting")
1313

14-
diode_target = netbox_settings.PLUGINS_CONFIG.get(
15-
"netbox_diode_plugin", {}
16-
).get("diode_target", "grpc://localhost:8080/diode")
14+
default_diode_target = get_plugin_config("netbox_diode_plugin", "diode_target")
15+
diode_target = get_plugin_config(
16+
"netbox_diode_plugin", "diode_target_override", default_diode_target
17+
)
1718

1819
Setting.objects.create(diode_target=diode_target)
1920

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env python
2+
# Copyright 2024 NetBox Labs Inc
3+
"""Diode Netbox Plugin - Database migrations."""
4+
5+
from django.db import migrations
6+
7+
8+
def clear_diode_group_permissions(apps, schema_editor):
9+
"""Clear Diode group permissions."""
10+
ObjectPermission = apps.get_model("users", "ObjectPermission")
11+
permission = ObjectPermission.objects.get(name="Diode")
12+
permission.groups.clear()
13+
14+
15+
class Migration(migrations.Migration):
16+
"""0003_clear_permissions migration."""
17+
18+
dependencies = [
19+
("netbox_diode_plugin", "0001_initial"),
20+
("netbox_diode_plugin", "0002_setting"),
21+
]
22+
23+
operations = [
24+
migrations.RunPython(
25+
code=clear_diode_group_permissions, reverse_code=migrations.RunPython.noop
26+
),
27+
]
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env python
2+
# Copyright 2024 NetBox Labs Inc
3+
"""Diode Netbox Plugin - Database migrations."""
4+
5+
from django.db import migrations
6+
7+
from netbox_diode_plugin.plugin_config import get_diode_usernames
8+
9+
10+
def rename_legacy_users(apps, schema_editor):
11+
"""Rename legacy users."""
12+
legacy_usernames_to_user_category_map = {
13+
"DIODE_TO_NETBOX": "diode_to_netbox",
14+
"NETBOX_TO_DIODE": "netbox_to_diode",
15+
"DIODE": "diode",
16+
}
17+
18+
User = apps.get_model("users", "User")
19+
users = User.objects.filter(
20+
username__in=legacy_usernames_to_user_category_map.keys(),
21+
groups__name="diode",
22+
)
23+
24+
for user in users:
25+
user_category = legacy_usernames_to_user_category_map.get(user.username)
26+
user.username = get_diode_usernames().get(user_category)
27+
user.save()
28+
29+
30+
class Migration(migrations.Migration):
31+
"""0004_rename_legacy_users migration."""
32+
33+
dependencies = [
34+
("netbox_diode_plugin", "0001_initial"),
35+
("netbox_diode_plugin", "0002_setting"),
36+
("netbox_diode_plugin", "0003_clear_permissions"),
37+
]
38+
39+
operations = [
40+
migrations.RunPython(
41+
code=rename_legacy_users, reverse_code=migrations.RunPython.noop
42+
),
43+
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# !/usr/bin/env python
2+
# Copyright 2024 NetBox Labs Inc
3+
"""Diode NetBox Plugin - Plugin Settings."""
4+
5+
from netbox.plugins import get_plugin_config
6+
7+
__all__ = ("get_diode_usernames", "get_diode_username_for_user_category")
8+
9+
10+
def get_diode_usernames():
11+
"""Returns a dictionary of diode user categories and their configured usernames."""
12+
diode_user_categories = ("diode_to_netbox", "netbox_to_diode", "diode")
13+
return {
14+
user_category: get_plugin_config(
15+
"netbox_diode_plugin", f"{user_category}_username"
16+
)
17+
for user_category in diode_user_categories
18+
}
19+
20+
21+
def get_diode_username_for_user_category(user_category):
22+
"""Returns a diode username for a given user category."""
23+
return get_plugin_config("netbox_diode_plugin", f"{user_category}_username")

netbox_diode_plugin/templates/diode/ingestion_logs.html

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
{% block title %}{% trans "Ingestion Logs" %}{% endblock %}
88

99
{% block content %}
10-
{% if error %}
10+
11+
{% if netbox_to_diode_user_error %}
12+
<div class="alert alert-danger mt-3" role="alert">
13+
<h4 class="alert-heading">{% trans "Error" %}</h4>
14+
<p>{{ netbox_to_diode_user_error }}</p>
15+
</div>
16+
{% elif ingestion_logs_error %}
1117
<div class="alert alert-danger mt-3" role="alert">
1218
<h4 class="alert-heading">{% trans "Error" %}</h4>
13-
<p>{{ error.status_code }}: {{ error.details }}</p>
19+
<p>{{ ingestion_logs_error.status_code }}: {{ ingestion_logs_error.details }}</p>
1420
</div>
1521
{% else %}
1622
<div>
@@ -24,7 +30,7 @@ <h4 class="alert-heading">{% trans "Error" %}</h4>
2430
</div>
2531
<div class="w-auto p-0">
2632
<canvas id="ingestions-reconciled"></canvas>
27-
<div class="text-center metric-label">Reconciled</div>
33+
<div class="text-center metric-label">Changes</div>
2834
</div>
2935
<div class="w-auto p-0">
3036
<canvas id="ingestions-failed"></canvas>

0 commit comments

Comments
 (0)