netbox-plugin-prometheus-sd
netbox-plugin-prometheus-sd copied to clipboard
[Feature] - Add iregex filter for devices model and return custom_fields as labels
Hi, below is the Netbox API return from /api/dcim/devices/?name=router-01.example.com and I would like to know if you could implement in your API a filter on the model or model slug (using the model__iregex) that will return all devices matching this model as well as an option to return all custom_fields as labels, please.
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": 8,
"url": "https://netbox.example.com/api/dcim/devices/8/",
"display": "router-01.example.com",
"name": "router-01.example.com",
"device_type": {
"id": 61,
"url": "https://netbox.example.com/api/dcim/device-types/61/",
"display": "Dlink xSeries",
"manufacturer": {
"id": 12,
"url": "https://netbox.example.com/api/dcim/manufacturers/12/",
"display": "DLINK",
"name": "DLINK",
"slug": "dlink"
},
"model": "Dlink xSeries",
"slug": "dlink-xseries"
},
"device_role": {
"id": 5,
"url": "https://netbox.example.com/api/dcim/device-roles/5/",
"display": "ROUTER",
"name": "ROUTER",
"slug": "ROUTER"
},
"tenant": {
"id": 1,
"url": "https://netbox.example.com/api/tenancy/tenants/1/",
"display": "HOME",
"name": "HOME",
"slug": "home"
},
"platform": null,
"serial": "01234567890",
"asset_tag": null,
"site": {
"id": 1,
"url": "https://netbox.example.com/api/dcim/sites/1/",
"display": "Malakoff",
"name": "Paris",
"slug": "paris"
},
"location": null,
"rack": {
"id": 8,
"url": "https://netbox.example.com/api/dcim/racks/8/",
"display": "RACK 11",
"name": "RACK 11"
},
"position": 6,
"face": {
"value": "front",
"label": "Front"
},
"parent_device": null,
"status": {
"value": "active",
"label": "Active"
},
"airflow": {
"value": "side-to-rear",
"label": "Side to rear"
},
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"cluster": null,
"virtual_chassis": null,
"vc_position": null,
"vc_priority": null,
"comments": "",
"local_context_data": null,
"tags": [],
"custom_fields": {
"IP": "10.1.1.10",
"snmp_communaute": "read",
"snmp_communaute_alternate": null,
"snmp_version": "2c",
"snmp_port": "161",
"ping_frequence": "60",
"snmp_frequence": "60",
"client": null,
"exploitant": "Operator",
"partenaire": null
},
"config_context": {},
"created": "2022-07-12T12:21:22.461170Z",
"last_updated": "2022-07-12T12:23:18.688284Z"
}
]
}
Thank you in advance.
Here is what I tried :
What works, but hard-coded
class DeviceViewSet(NetBoxModelViewSet): # pylint: disable=too-many-ancestors
queryset = Device.objects.prefetch_related(
"device_type__manufacturer",
"device_role",
"tenant",
"platform",
"site",
"location",
"rack",
"parent_bay",
"virtual_chassis__master",
"primary_ip4__nat_outside",
"primary_ip6__nat_outside",
"tags",
).filter(
Q(device_type__model__iregex=r'^Dlink.*$')
)
filterset_class = DeviceFilterSet
serializer_class = PrometheusDeviceSerializer
pagination_class = None
What does not work, calling the url api/plugins/prometheus-sd/devices?model=Dlink
class DeviceViewSet(NetBoxModelViewSet): # pylint: disable=too-many-ancestors
queryset = Device.objects.prefetch_related(
"device_type__manufacturer",
"device_role",
"tenant",
"platform",
"site",
"location",
"rack",
"parent_bay",
"virtual_chassis__master",
"primary_ip4__nat_outside",
"primary_ip6__nat_outside",
"tags",
)
def get_queryset(self):
queryset = super().get_queryset()
device_type__model = self.request.query_params.get("model", None)
if device_type__model is not None:
queryset = queryset.filter(
Q(device_type__model__iregex=r'^{}.*$'.format(device_type__model))
)
return queryset
filterset_class = DeviceFilterSet
serializer_class = PrometheusDeviceSerializer
pagination_class = None
HTTP 400 Bad Request
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"model": [
"Select a valid choice. Dlink is not one of the available choices."
]
}
EDIT:
I think I found the solution, the problem comes from the line :
device_type__model = self.request.query_params.get("model", None)
I renamed the param model to device_model and it works :
# netbox_prometheus_sd/api/views.py
from django.db.models import Q
class DeviceViewSet(NetBoxModelViewSet): # pylint: disable=too-many-ancestors
queryset = Device.objects.prefetch_related(
"device_type__manufacturer",
"device_role",
"tenant",
"platform",
"site",
"location",
"rack",
"parent_bay",
"virtual_chassis__master",
"primary_ip4__nat_outside",
"primary_ip6__nat_outside",
"tags",
)
def get_queryset(self):
queryset = super().get_queryset()
request = self.get_serializer_context()['request']
device_model = request.query_params.get("deviceModel", None)
if device_model is not None:
queryset = queryset.filter(
Q(device_type__model__iregex=device_model)
)
return queryset
filterset_class = DeviceFilterSet
serializer_class = PrometheusDeviceSerializer
pagination_class = None
I'm having trouble displaying the custom_fields, I have the sensation that the object is not present.
Here is what I tried but without success :
# netbox_prometheus_sd/api/serializers.py
if hasattr(obj, "custom_fields") and obj.custom_fields is not None and len(obj.custom_fields.all()):
labels["custom_fields"] = ",".join(
[
f"{cf.name}={cf.value}"
for cf in obj.custom_fields.all()
if cf.value is not None
]
)
EDIT:
By persevering, I found the solution that I share below:
# netbox_prometheus_sd/api/serializers.py
if hasattr(obj, "custom_field_data") and obj.custom_field_data is not None:
for key, value in obj.custom_field_data.items():
if value is not None:
labels["custom_field_" + key.lower()] = value
@FlxPeters are you interested by a PR ?
Custom fields have been merged to main branch. Please provide feedback if the solution meets your requirements.
Hey! any ETA when the custom_fields will hit PyPI? Thank you for maintaining this plugin, Felix :)
I just released a pre-release on PyPi: https://pypi.org/project/netbox-plugin-prometheus-sd/0.6.0rc1/ Please provide feedback if this fits your needs or if we have to adjust the labels.
Awesome, thank you :) I'll tinker around with it for a couple days and report back!
I just wanted to make a issue/PR to include the custom fields and checked-out the pre-release which you mentioned above. Seems to work at-least for my use-case.
Works great for me. So far I did not encounter any limitations
I did a quick test with the git repo and it worked pretty good. Thanks I'd been wanting to do this for a while. I have noticed that the netbox plugin version and the pypi app version don't match. 0.5.0 from PyPi seems to display as 0.4 in netbox plugins. took me a while to work out what was going on.
It gets a bit awkward where custom fields have data type "multiselect". They are currently returned as a not-quite JSON list:
"__meta_netbox_custom_field_snmp_module": "['if_mib', 'ubiquiti_unifi']",
("not-quite JSON" because it uses single quotes rather than double quotes).
This is rather inconvenient when it comes to the new multi-module support in SNMP exporter (v0.24.0+), which requires a plain comma-separated list like /snmp?target=X.X.X.X&module=if_mib,ubiquiti_unifi
AFAICT there's no global search-replace in Prometheus relabelling, so this was the best I could come up with:
- source_labels: [__meta_netbox_custom_field_snmp_module]
target_label: __param_module
# Ugh: multiselect is of form ['foo','bar'] and we need foo,bar. There is no gsub.
- source_labels: [__param_module]
regex: "\\['(.*)'\\]"
target_label: __param_module
- source_labels: [__param_module]
regex: "(.*)', *'(.*)"
replacement: "$1,$2"
target_label: __param_module
- source_labels: [__param_module]
regex: "(.*)', *'(.*)"
replacement: "$1,$2"
target_label: __param_module
- source_labels: [__param_module]
regex: "(.*)', *'(.*)"
replacement: "$1,$2"
target_label: __param_module
(which works for a maximum of 4 selected values). That's pretty ugly.
I'm not sure of the best solution here. Automatically converting a list value into a comma-joined string would be the easy thing for this particular case, but I guess there might be other cases where people would want something different. (I considered that a plain value could contain a comma, but this doesn't apply in a Netbox custom field select or multiselect, because the list of allowed values in the custom field definition is itself comma-separated)
EDIT: Since Netbox 3.6 has added Custom Field choice sets, comma is no longer a special field (although colon is)
For fields with a comma you could do backslash escaping ("\" -> "\\", "," -> "\,") or URL-style escaping ("%" -> "%25", "," -> "%2c"), or CSV formatting (wrap field with double quotes, and replace one double-quote with two - but only if the field contains a comma)
This issue has been automatically closed because it has been inactive for more than 60 days. Please reopen if you still intend to submit this pull request.