Skip to content

Commit 4f698f3

Browse files
authored
Merge pull request #43 from netboxlabs/develop
🚚 release
2 parents 8714b86 + 3b19041 commit 4f698f3

File tree

16 files changed

+660
-70
lines changed

16 files changed

+660
-70
lines changed

Diff for: .github/workflows/manifest-modified.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: NetBox plugin manifest modified
2+
3+
on:
4+
push:
5+
branches: [ develop ]
6+
paths:
7+
- netbox-plugin.yaml
8+
9+
concurrency:
10+
group: ${{ github.workflow }}
11+
cancel-in-progress: false
12+
13+
jobs:
14+
manifest-modified:
15+
uses: netboxlabs/public-workflows/.github/workflows/reusable-plugin-manifest-modified.yml@release

Diff for: README.md

+17-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ Also in your `configuration.py` file, in order to customise the plugin settings,
4646
```python
4747
PLUGINS_CONFIG = {
4848
"netbox_diode_plugin": {
49+
# Auto-provision users for Diode plugin
50+
"auto_provision_users": False,
51+
4952
# Diode gRPC target for communication with Diode server
5053
"diode_target_override": "grpc://localhost:8080/diode",
5154

@@ -64,6 +67,11 @@ PLUGINS_CONFIG = {
6467
Note: Once you customise usernames with PLUGINS_CONFIG during first installation, you should not change or remove them
6568
later on. Doing so will cause the plugin to stop working properly.
6669

70+
`auto_provision_users` is a boolean flag (default: `False`) that determines whether the plugin should automatically
71+
create the users during
72+
migration. If set to `False`, you will need to provision Diode users with their API keys manually via the plugin's setup
73+
page in the NetBox UI.
74+
6775
Restart NetBox services to load the plugin:
6876

6977
```
@@ -93,7 +101,15 @@ export NETBOX_TO_DIODE_API_KEY=$(head -c20 </dev/urandom|xxd -p); env | grep NET
93101
export DIODE_API_KEY=$(head -c20 </dev/urandom|xxd -p); env | grep DIODE_API_KEY
94102
```
95103

96-
**Note:** store these API key strings in a safe place as they will be needed later to configure the Diode server
104+
**Note:** store these API key strings in a safe place as they will be needed later to configure the Diode server.
105+
106+
If you don't set these environment variables, the plugin will generate random API keys for you either during the
107+
migration process (with `auto_provision_users` set to `True`) or when you manually create the users in the plugin's
108+
setup page in the NetBox UI.
109+
110+
It's important to note that environment variables with API keys should be populated in the Diode server's
111+
environment variables (see [docs](https://github.com/netboxlabs/diode/tree/develop/diode-server#running-the-diode-server))
112+
as well to ensure proper communication between the Diode SDK, Diode server and the NetBox plugin.
97113

98114
Run migrations to create all necessary resources:
99115

Diff for: docker/netbox/configuration/plugins.py

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
# PLUGINS_CONFIG = {
1010
# "netbox_diode_plugin": {
11+
# # Auto-provision users for Diode plugin
12+
# "auto_provision_users": True,
13+
#
1114
# # Diode gRPC target for communication with Diode server
1215
# "diode_target_override": "grpc://localhost:8080/diode",
1316
#

Diff for: docker/netbox/plugins_test.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88

99
PLUGINS_CONFIG = {
1010
"netbox_diode_plugin": {
11-
"enable_ingestion_logs": True,
11+
"auto_provision_users": True,
1212
}
1313
}

Diff for: netbox-plugin.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: 0.1
2+
package_name: netboxlabs-diode-netbox-plugin
3+
compatibility:
4+
- release: 0.5.1
5+
netbox_min: 4.1.0
6+
netbox_max: 4.1.3
7+
- release: 0.5.0
8+
netbox_min: 4.1.0
9+
netbox_max: 4.1.3
10+
- release: 0.3.0
11+
netbox_min: 4.0.8
12+
netbox_max: 4.0.9
13+
- release: 0.2.1
14+
netbox_min: 3.7.2
15+
netbox_max: 4.0.8

Diff for: netbox_diode_plugin/__init__.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ class NetBoxDiodePluginConfig(PluginConfig):
1717
base_url = "diode"
1818
min_version = "3.7.2"
1919
default_settings = {
20+
# Auto-provision users for Diode plugin
21+
"auto_provision_users": False,
2022
# Default Diode gRPC target for communication with Diode server
2123
"diode_target": "grpc://localhost:8080/diode",
22-
2324
# User allowed for Diode to NetBox communication
2425
"diode_to_netbox_username": "diode-to-netbox",
25-
2626
# User allowed for NetBox to Diode communication
2727
"netbox_to_diode_username": "netbox-to-diode",
28-
2928
# User allowed for data ingestion
3029
"diode_username": "diode-ingestion",
3130
}

Diff for: netbox_diode_plugin/forms.py

+56-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
# !/usr/bin/env python
22
# Copyright 2024 NetBox Labs Inc
33
"""Diode NetBox Plugin - Forms."""
4+
from django import forms
5+
from django.core.validators import MinLengthValidator
6+
from django.utils.translation import gettext_lazy as _
47
from netbox.forms import NetBoxModelForm
58
from netbox.plugins import get_plugin_config
9+
from users.models import Token as NetBoxToken
610
from utilities.forms.rendering import FieldSet
711

812
from netbox_diode_plugin.models import Setting
913

10-
__all__ = ("SettingsForm",)
14+
__all__ = (
15+
"SettingsForm",
16+
"SetupForm",
17+
)
1118

1219

1320
class SettingsForm(NetBoxModelForm):
@@ -38,3 +45,51 @@ def __init__(self, *args, **kwargs):
3845
self.fields["diode_target"].help_text = (
3946
"This field is not allowed to be modified."
4047
)
48+
49+
50+
class SetupForm(forms.Form):
51+
"""Setup form."""
52+
53+
def __init__(self, users, *args, **kwargs):
54+
"""Initialize the form."""
55+
super().__init__(*args, **kwargs)
56+
57+
for user_type, user_properties in users.items():
58+
field_name = f"{user_type}_api_key"
59+
username_or_type = user_properties.get("username") or user_type
60+
label = f"{username_or_type}"
61+
62+
disabled = user_properties.get("predefined_api_key") is not None or user_properties.get("api_key") is not None
63+
help_text = _(
64+
f"Key must be at least 40 characters in length.<br />Map to environment variable "
65+
f'{user_properties["api_key_env_var_name"]} in Diode service'
66+
f'{" and Diode SDK" if user_type == "diode" else ""}'
67+
)
68+
69+
initial_value = user_properties.get("api_key") or user_properties.get(
70+
"predefined_api_key"
71+
)
72+
73+
if (
74+
user_properties.get("predefined_api_key") is None
75+
and user_properties.get("api_key") is None
76+
):
77+
initial_value = NetBoxToken.generate_key()
78+
79+
self.fields[field_name] = forms.CharField(
80+
required=True,
81+
max_length=40,
82+
validators=[MinLengthValidator(40)],
83+
label=label,
84+
disabled=disabled,
85+
initial=initial_value,
86+
help_text=help_text,
87+
widget=forms.TextInput(
88+
attrs={
89+
"data-clipboard": "true",
90+
"placeholder": _(
91+
f"Enter a valid API key for {username_or_type} user"
92+
),
93+
}
94+
),
95+
)

Diff for: netbox_diode_plugin/migrations/0001_initial.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.conf import settings as netbox_settings
99
from django.contrib.contenttypes.management import create_contenttypes
1010
from django.db import migrations, models
11+
from netbox.plugins import get_plugin_config
1112
from users.models import Token as NetBoxToken
1213

1314
from netbox_diode_plugin.plugin_config import get_diode_usernames
@@ -24,7 +25,7 @@ def _read_secret(secret_name, default=None):
2425
return f.readline().strip()
2526

2627

27-
def _create_user_with_token(apps, user_category, username, group):
28+
def _create_user_with_token(apps, user_type, username, group):
2829
User = apps.get_model(netbox_settings.AUTH_USER_MODEL)
2930
"""Create a user with the given username and API key if it does not exist."""
3031
try:
@@ -37,7 +38,7 @@ def _create_user_with_token(apps, user_category, username, group):
3738
Token = apps.get_model("users", "Token")
3839

3940
if not Token.objects.filter(user=user).exists():
40-
key = f"{user_category.upper()}_API_KEY"
41+
key = f"{user_type.upper()}_API_KEY"
4142
api_key = _read_secret(key.lower(), os.getenv(key))
4243
if api_key is None:
4344
api_key = NetBoxToken.generate_key()
@@ -66,11 +67,18 @@ def configure_plugin(apps, schema_editor):
6667
)
6768
permission.object_types.set([diode_plugin_object_type.id])
6869

70+
auto_provision_users = get_plugin_config(
71+
"netbox_diode_plugin", "auto_provision_users"
72+
)
73+
74+
if not auto_provision_users:
75+
return
76+
6977
diode_to_netbox_user_id = None
7078

71-
for user_category, username in get_diode_usernames().items():
72-
user = _create_user_with_token(apps, user_category, username, group)
73-
if user_category == "diode_to_netbox":
79+
for user_type, username in get_diode_usernames().items():
80+
user = _create_user_with_token(apps, user_type, username, group)
81+
if user_type == "diode_to_netbox":
7482
diode_to_netbox_user_id = user.id
7583

7684
permission.users.set([diode_to_netbox_user_id])

Diff for: netbox_diode_plugin/migrations/0004_rename_legacy_users.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,21 @@
99

1010
def rename_legacy_users(apps, schema_editor):
1111
"""Rename legacy users."""
12-
legacy_usernames_to_user_category_map = {
12+
legacy_usernames_to_user_type_map = {
1313
"DIODE_TO_NETBOX": "diode_to_netbox",
1414
"NETBOX_TO_DIODE": "netbox_to_diode",
1515
"DIODE": "diode",
1616
}
1717

1818
User = apps.get_model("users", "User")
1919
users = User.objects.filter(
20-
username__in=legacy_usernames_to_user_category_map.keys(),
20+
username__in=legacy_usernames_to_user_type_map.keys(),
2121
groups__name="diode",
2222
)
2323

2424
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)
25+
user_type = legacy_usernames_to_user_type_map.get(user.username)
26+
user.username = get_diode_usernames().get(user_type)
2727
user.save()
2828

2929

Diff for: netbox_diode_plugin/plugin_config.py

+25-10
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,35 @@
44

55
from netbox.plugins import get_plugin_config
66

7-
__all__ = ("get_diode_usernames", "get_diode_username_for_user_category")
7+
__all__ = (
8+
"get_diode_user_types",
9+
"get_diode_usernames",
10+
"get_diode_username_for_user_type",
11+
)
12+
13+
14+
def get_diode_user_types():
15+
"""Returns a list of diode user types."""
16+
return "diode_to_netbox", "netbox_to_diode", "diode"
17+
18+
19+
def get_diode_user_types_with_labels():
20+
"""Returns a list of diode user types with labels."""
21+
return (
22+
("diode_to_netbox", "Diode to NetBox"),
23+
("netbox_to_diode", "NetBox to Diode"),
24+
("diode", "Diode"),
25+
)
826

927

1028
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")
29+
"""Returns a dictionary of diode user types and their configured usernames."""
1330
return {
14-
user_category: get_plugin_config(
15-
"netbox_diode_plugin", f"{user_category}_username"
16-
)
17-
for user_category in diode_user_categories
31+
user_type: get_plugin_config("netbox_diode_plugin", f"{user_type}_username")
32+
for user_type in get_diode_user_types()
1833
}
1934

2035

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")
36+
def get_diode_username_for_user_type(user_type):
37+
"""Returns a diode username for a given user type."""
38+
return get_plugin_config("netbox_diode_plugin", f"{user_type}_username")

Diff for: netbox_diode_plugin/templates/diode/setup.html

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{% extends 'generic/_base.html' %}
2+
{% load i18n %}
3+
4+
{% block title %}{% trans "Setup" %}{% endblock %}
5+
6+
{% block content %}
7+
8+
<div class="alert alert-warning mt-3" role="alert">
9+
<h4 class="alert-heading">{% trans "Important" %}</h4>
10+
<p>{% trans "Diode is not currently configured and requires a set of users and API keys to be created before using Diode. If fields in this form are read-only, they have been pre-populated based on application settings that cannot be overwritten." %}</p>
11+
<p>{% trans "Click the \"Create\" button to create the required Diode users and API keys and proceed." %}</p>
12+
</div>
13+
14+
<div class="tab-pane show active" id="setup-form" role="tabpanel" aria-labelledby="setup-tab">
15+
<form action="" method="post" enctype="multipart/form-data" class="object-edit mt-5">
16+
{% csrf_token %}
17+
18+
<div class="row">
19+
<h2 class="col-9 offset-3">{% trans "Diode users and API Keys" %}</h2>
20+
</div>
21+
22+
<div id="form_fields" hx-disinherit="hx-select hx-swap">
23+
{% block form %}
24+
{% include 'htmx/form.html' %}
25+
{% endblock form %}
26+
</div>
27+
28+
<div class="text-end my-3">
29+
{% block buttons %}
30+
<button type="submit" name="_update" class="btn btn-primary">
31+
{% trans "Create" %}
32+
</button>
33+
{% endblock buttons %}
34+
</div>
35+
</form>
36+
</div>
37+
{% endblock content %}
38+

0 commit comments

Comments
 (0)