netbox-plugin-prometheus-sd 2.0.0__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. netbox_plugin_prometheus_sd-2.2.0/PKG-INFO +297 -0
  2. netbox_plugin_prometheus_sd-2.2.0/README.md +278 -0
  3. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/__init__.py +1 -1
  4. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/filtersets.py +33 -5
  5. netbox_plugin_prometheus_sd-2.2.0/netbox_prometheus_sd/tests/test_filtersets.py +63 -0
  6. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/test_serializers.py +13 -2
  7. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/utils.py +22 -4
  8. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/pyproject.toml +1 -1
  9. netbox_plugin_prometheus_sd-2.0.0/PKG-INFO +0 -158
  10. netbox_plugin_prometheus_sd-2.0.0/README.md +0 -141
  11. netbox_plugin_prometheus_sd-2.0.0/netbox_prometheus_sd/tests/test_filtersets.py +0 -36
  12. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/LICENSE +0 -0
  13. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/__init__.py +0 -0
  14. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/serializers.py +0 -0
  15. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/urls.py +0 -0
  16. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/utils.py +0 -0
  17. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/api/views.py +0 -0
  18. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/__init__.py +0 -0
  19. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/test_api.py +0 -0
  20. {netbox_plugin_prometheus_sd-2.0.0 → netbox_plugin_prometheus_sd-2.2.0}/netbox_prometheus_sd/tests/test_utils.py +0 -0
@@ -0,0 +1,297 @@
1
+ Metadata-Version: 2.4
2
+ Name: netbox-plugin-prometheus-sd
3
+ Version: 2.2.0
4
+ Summary: A Netbox plugin to provide Netbox entires to Prometheus HTTP service discovery
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: Felix Peters
8
+ Author-email: felix.peters@breuninger.de
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Description-Content-Type: text/markdown
18
+
19
+ # netbox-plugin-prometheus-sd
20
+
21
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
22
+ [![CI](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/workflows/CI/badge.svg?event=push)](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/actions?query=workflow%3ACI)
23
+ [![PyPI](https://img.shields.io/pypi/v/netbox-plugin-prometheus-sd)](https://pypi.org/project/netbox-plugin-prometheus-sd/)
24
+
25
+ [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/flxpeters)
26
+
27
+ Provide Prometheus `http_sd` compatible API Endpoint with data from Netbox.
28
+
29
+ HTTP SD is a feature since Prometheus 2.28.0 that allows hosts to be found via a URL instead of just files.
30
+ This plugin implements API endpoints in Netbox to make devices, services, IPs and virtual machines available to Prometheus.
31
+
32
+ ## Compatibility
33
+
34
+ We aim to support the latest major versions of Netbox.
35
+
36
+ | Plugin | Netbox |
37
+ |---|---|
38
+ | `2.x` | `>= 4.0` |
39
+ | `1.x` | `3.x` (no longer maintained) |
40
+
41
+ Check the `.github/workflows/ci.yml` pipeline for the current tested builds.
42
+ Other versions may work, but we do not test them explicitly. All relevant target versions are tested in CI.
43
+
44
+ Plugin `2.0` also fixed a set of N+1 queries that made the endpoints very slow on
45
+ larger installations ([#265](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/issues/265)).
46
+ If service discovery is putting noticeable load on your Netbox database, upgrading
47
+ is worthwhile.
48
+
49
+ ## Installation
50
+
51
+ The plugin is available as a Python package in pypi and can be installed with pip
52
+
53
+ ```bash
54
+ pip install netbox-plugin-prometheus-sd
55
+ ```
56
+
57
+ Enable the plugin in /opt/netbox/netbox/netbox/configuration.py:
58
+
59
+ ```python
60
+ PLUGINS = ['netbox_prometheus_sd']
61
+ ```
62
+
63
+ The plugin has not further plugin configuration.
64
+
65
+ ## Usage
66
+
67
+ The plugin only provides a new API endpoint on the Netbox API. There is no further action required after installation.
68
+
69
+ ### API
70
+
71
+ The plugin reuses Netbox API view sets with new serializers for Prometheus.
72
+ This means that all filters that can be used on the Netbox API can also be used to filter Prometheus targets.
73
+ Paging is disabled because Prometheus does not support paged results.
74
+
75
+ The plugin also reuses the Netbox authentication and permission model.
76
+ Depending on the Netbox configuration, a token with valid object permissions must be passed to Netbox.
77
+
78
+ ```
79
+ GET /api/plugins/prometheus-sd/devices/ Get a list of devices in a prometheus compatible format
80
+ GET /api/plugins/prometheus-sd/virtual-machines/ Get a list of vms in a prometheus compatible format
81
+ GET /api/plugins/prometheus-sd/services/ Get a list of services in a prometheus compatible format
82
+ GET /api/plugins/prometheus-sd/ip-addresses/ Get a list of ip in a prometheus compatible format
83
+ ```
84
+
85
+ #### Extended services filters
86
+
87
+ Apart from standard Netbox filters, services endpoint also supports `tenant=<slug>` or `tenant_id=<id>`, and
88
+ `site=<slug>` or `site_id=<id>` parameters. The lookup is only executed against the `tenant`/`site` attribute of the
89
+ object associated with the service.
90
+
91
+ #### Filtering and response size
92
+
93
+ Because paging is disabled, one request serializes every object the token is
94
+ allowed to see. Filtering with the standard Netbox query parameters is the
95
+ supported way to keep responses small, and it is usually what you want anyway —
96
+ Prometheus should not be told about decommissioned hardware:
97
+
98
+ ```
99
+ /api/plugins/prometheus-sd/devices/?status=active&tag=monitoring&site=dc1
100
+ ```
101
+
102
+ ### Labels
103
+
104
+ Every target carries `__meta_netbox_*` labels describing the Netbox object.
105
+ **Prometheus discards labels starting with `__` after service discovery**, so
106
+ they have to be copied into real labels with `relabel_configs` (see below) if you
107
+ want them on your metrics.
108
+
109
+ A label is only present when the underlying field is populated — a device with no
110
+ tenant has no `__meta_netbox_tenant`.
111
+
112
+ | Label | Devices | VMs | Services | IPs |
113
+ |---|:-:|:-:|:-:|:-:|
114
+ | `id`, `name`, `status`, `model` | ✓ | ✓ | id/name only | ✓ (no name) |
115
+ | `primary_ip`, `primary_ip4`, `primary_ip6` | ✓ | ✓ | from parent | |
116
+ | `oob_ip` | ✓ | | from parent | |
117
+ | `ip` | | | | ✓ |
118
+ | `role`, `role_slug` | ✓ | ✓ | | `role` only |
119
+ | `platform`, `platform_slug` | ✓ | ✓ | | |
120
+ | `device_type`, `device_type_slug` | ✓ | | | |
121
+ | `site`, `site_slug` | ✓ | ✓ | from parent | |
122
+ | `scope`, `scope_slug` | | ✓ ¹ | | |
123
+ | `location`, `location_slug` | ✓ | | | |
124
+ | `rack`, `rack_u_position` | ✓ | | | |
125
+ | `cluster`, `cluster_group`, `cluster_type` | ✓ ² | ✓ | | |
126
+ | `tenant`, `tenant_slug` | ✓ | ✓ | from parent | ✓ |
127
+ | `tenant_group`, `tenant_group_slug` | ✓ | ✓ | from parent | ✓ |
128
+ | `tags`, `tag_slugs` | ✓ | ✓ | ✓ | ✓ |
129
+ | `services` | ✓ | ✓ | | |
130
+ | `contact_<priority>_{name,email,comments,role}` | ✓ | ✓ | from parent | |
131
+ | `custom_field_<name>` | ✓ | ✓ | ✓ | ✓ |
132
+ | `description` | ✓ | | | |
133
+ | `parent`, `display`, `ports`, `ipaddresses` | | | ✓ | |
134
+
135
+ ¹ Netbox 4.2 replaced the cluster's site with a generic `scope`. On 4.2+ a VM
136
+ emits `scope`/`scope_slug` for the cluster, and `site`/`site_slug` for its own
137
+ site; below 4.2 the cluster's site is reported as `site`.
138
+ ² Only when the device is assigned to a cluster.
139
+
140
+ Config context can additionally set `__metrics_path__` and `__scheme__`, which
141
+ Prometheus consumes directly (see [Config context](#config-context)).
142
+
143
+ ### Relabeling
144
+
145
+ The `__meta_netbox_*` labels are dropped unless you map them. A typical device job
146
+ scraping node_exporter on the primary IP:
147
+
148
+ ```yaml
149
+ scrape_configs:
150
+ - job_name: netbox-devices
151
+ http_sd_configs:
152
+ - url: http://netbox:8080/api/plugins/prometheus-sd/devices/?status=active&tag=monitoring
153
+ refresh_interval: 60s
154
+ authorization:
155
+ type: Token
156
+ credentials: "<your-netbox-api-token>"
157
+
158
+ relabel_configs:
159
+ # Skip anything without a primary IPv4, otherwise the address below is empty.
160
+ - source_labels: [__meta_netbox_primary_ip4]
161
+ regex: ^$
162
+ action: drop
163
+
164
+ # Scrape the primary IP instead of the device name, which may not resolve.
165
+ - source_labels: [__meta_netbox_primary_ip4]
166
+ target_label: __address__
167
+ replacement: "$1:9100"
168
+
169
+ # Keep the Netbox name as the instance label rather than the IP.
170
+ - source_labels: [__meta_netbox_name]
171
+ target_label: instance
172
+
173
+ # Promote the dimensions worth alerting and grouping on.
174
+ - source_labels: [__meta_netbox_site_slug]
175
+ target_label: site
176
+ - source_labels: [__meta_netbox_role_slug]
177
+ target_label: role
178
+ - source_labels: [__meta_netbox_tenant_slug]
179
+ target_label: tenant
180
+ ```
181
+
182
+ Two things worth knowing:
183
+
184
+ - `tags` and `tag_slugs` are comma-joined, so match them with `.*,?value,?.*`
185
+ rather than `=`.
186
+ - Devices and VMs use the same label names, so one set of `relabel_configs` can
187
+ be reused across both jobs.
188
+
189
+ ### Config context
190
+
191
+ The plugin can also discover extra config to inject in the HTTP SD JSON from the config context of the devices/virtual machines.
192
+ If you have a `prometheus-plugin-prometheus-sd` entry in your config context with the following schema it will be automatically picked up:
193
+
194
+ ```yaml
195
+ prometheus-plugin-prometheus-sd:
196
+ - metrics_path: /not/metrics
197
+ port: 4242
198
+ scheme: https
199
+ - port: 4243
200
+ ```
201
+
202
+ This allow you to configure those values directly into netbox instead of doing that inside the Prometheus
203
+ config and filtering each scenario by a specific tag for instance.
204
+
205
+ If there is only one entry you can also use this form:
206
+
207
+ ```yaml
208
+ prometheus-plugin-prometheus-sd:
209
+ metrics_path: /not/metrics
210
+ port: 4242
211
+ scheme: https
212
+ ```
213
+
214
+ ### Example
215
+
216
+ [`example/prometheus.yml`](example/prometheus.yml) is a complete Prometheus
217
+ configuration covering all four endpoints, with `relabel_configs` mapping the
218
+ `__meta_netbox_*` labels onto real ones.
219
+
220
+ It is not a snippet that happens to be in the repository: the test suite runs
221
+ Prometheus against this exact file and asserts that the expected targets and
222
+ labels are discovered, so it cannot quietly stop working.
223
+
224
+ To see it running, with a Netbox that has demo data already loaded:
225
+
226
+ ```bash
227
+ poetry run invoke build-dev
228
+ ```
229
+
230
+ - Netbox: <http://localhost:8000> (`admin` / `admin`)
231
+ - Prometheus: <http://localhost:9090> — discovered targets are under
232
+ Status → Target health, and expanding one shows the raw `__meta_netbox_*`
233
+ labels before relabeling.
234
+
235
+ The demo data is the unit-test fixtures, so it also exercises config context
236
+ (the VMs get two targets each, on different ports), services, contacts and tags.
237
+
238
+ ## Development
239
+
240
+ We use [Poetry](https://python-poetry.org/) for dependency management and [invoke](https://www.pyinvoke.org/) as task runner.
241
+ To test the plugin in an isolated environment, we use [testcontainers](https://testcontainers.com/?language=python)
242
+ which creates "throwaway, lightweight" Netbox Docker containers.
243
+
244
+ Install the dependencies with `poetry install`, then run the tasks through
245
+ `poetry run` (this works on every Poetry version, whereas `poetry shell` was moved
246
+ into a separate plugin in Poetry 2.0):
247
+
248
+ ```bash
249
+ # Unit tests plus the Prometheus end-to-end check
250
+ poetry run invoke test
251
+
252
+ # Test against a specific Netbox release (default: latest)
253
+ NETBOX_VER=v4.6.5 poetry run invoke test
254
+
255
+ # Either half on its own
256
+ poetry run invoke unittest
257
+ poetry run invoke test-prometheus
258
+ ```
259
+
260
+ The Netbox image is built from the `Dockerfile` in the repository root.
261
+
262
+ Testing has two layers:
263
+
264
+ - **Unit tests** — plain Django tests under `netbox_prometheus_sd/tests/`,
265
+ executed inside the Netbox container. These cover the serializers and the
266
+ label output.
267
+ - **Prometheus end-to-end** — Netbox serving real HTTP with seeded data, and a
268
+ real Prometheus configured from `example/prometheus.yml`. It asserts that the
269
+ expected jobs discover targets and that relabeling produced the expected
270
+ labels.
271
+
272
+ The second layer exists because "valid JSON with targets and labels" is not the
273
+ same as "Prometheus accepts this as an `http_sd` source". A response that
274
+ Prometheus rejects would pass every unit test in this repository. It also keeps
275
+ the documented example honest, since it is the file under test.
276
+
277
+ Features should be covered by a test, but sometimes it is easier to develop
278
+ against a running system:
279
+
280
+ ```bash
281
+ # Netbox + Prometheus with demo data, left running until Ctrl+C
282
+ poetry run invoke build-dev
283
+ ```
284
+
285
+ Netbox is on <http://localhost:8000> (`admin` / `admin`) and Prometheus on
286
+ <http://localhost:9090>, already scraping it.
287
+
288
+ API endpoints for testing can be found at http://localhost:8000/api/plugins/prometheus-sd/
289
+
290
+ ## Conventional Commits
291
+
292
+ This repository follows the Conventional Commits specification for versioning and changelog generation.
293
+ Conventional Commits provide a standardized way of writing commit messages to convey semantic meaning
294
+ about the changes made. Each commit message follows a defined format that includes a type,
295
+ an optional scope, and a message. The types typically include features, fixes, documentation, and more.
296
+ By adhering to this convention, we ensure clear and automated versioning, release notes, and changelog generation.
297
+
@@ -0,0 +1,278 @@
1
+ # netbox-plugin-prometheus-sd
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![CI](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/workflows/CI/badge.svg?event=push)](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/actions?query=workflow%3ACI)
5
+ [![PyPI](https://img.shields.io/pypi/v/netbox-plugin-prometheus-sd)](https://pypi.org/project/netbox-plugin-prometheus-sd/)
6
+
7
+ [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/flxpeters)
8
+
9
+ Provide Prometheus `http_sd` compatible API Endpoint with data from Netbox.
10
+
11
+ HTTP SD is a feature since Prometheus 2.28.0 that allows hosts to be found via a URL instead of just files.
12
+ This plugin implements API endpoints in Netbox to make devices, services, IPs and virtual machines available to Prometheus.
13
+
14
+ ## Compatibility
15
+
16
+ We aim to support the latest major versions of Netbox.
17
+
18
+ | Plugin | Netbox |
19
+ |---|---|
20
+ | `2.x` | `>= 4.0` |
21
+ | `1.x` | `3.x` (no longer maintained) |
22
+
23
+ Check the `.github/workflows/ci.yml` pipeline for the current tested builds.
24
+ Other versions may work, but we do not test them explicitly. All relevant target versions are tested in CI.
25
+
26
+ Plugin `2.0` also fixed a set of N+1 queries that made the endpoints very slow on
27
+ larger installations ([#265](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/issues/265)).
28
+ If service discovery is putting noticeable load on your Netbox database, upgrading
29
+ is worthwhile.
30
+
31
+ ## Installation
32
+
33
+ The plugin is available as a Python package in pypi and can be installed with pip
34
+
35
+ ```bash
36
+ pip install netbox-plugin-prometheus-sd
37
+ ```
38
+
39
+ Enable the plugin in /opt/netbox/netbox/netbox/configuration.py:
40
+
41
+ ```python
42
+ PLUGINS = ['netbox_prometheus_sd']
43
+ ```
44
+
45
+ The plugin has not further plugin configuration.
46
+
47
+ ## Usage
48
+
49
+ The plugin only provides a new API endpoint on the Netbox API. There is no further action required after installation.
50
+
51
+ ### API
52
+
53
+ The plugin reuses Netbox API view sets with new serializers for Prometheus.
54
+ This means that all filters that can be used on the Netbox API can also be used to filter Prometheus targets.
55
+ Paging is disabled because Prometheus does not support paged results.
56
+
57
+ The plugin also reuses the Netbox authentication and permission model.
58
+ Depending on the Netbox configuration, a token with valid object permissions must be passed to Netbox.
59
+
60
+ ```
61
+ GET /api/plugins/prometheus-sd/devices/ Get a list of devices in a prometheus compatible format
62
+ GET /api/plugins/prometheus-sd/virtual-machines/ Get a list of vms in a prometheus compatible format
63
+ GET /api/plugins/prometheus-sd/services/ Get a list of services in a prometheus compatible format
64
+ GET /api/plugins/prometheus-sd/ip-addresses/ Get a list of ip in a prometheus compatible format
65
+ ```
66
+
67
+ #### Extended services filters
68
+
69
+ Apart from standard Netbox filters, services endpoint also supports `tenant=<slug>` or `tenant_id=<id>`, and
70
+ `site=<slug>` or `site_id=<id>` parameters. The lookup is only executed against the `tenant`/`site` attribute of the
71
+ object associated with the service.
72
+
73
+ #### Filtering and response size
74
+
75
+ Because paging is disabled, one request serializes every object the token is
76
+ allowed to see. Filtering with the standard Netbox query parameters is the
77
+ supported way to keep responses small, and it is usually what you want anyway —
78
+ Prometheus should not be told about decommissioned hardware:
79
+
80
+ ```
81
+ /api/plugins/prometheus-sd/devices/?status=active&tag=monitoring&site=dc1
82
+ ```
83
+
84
+ ### Labels
85
+
86
+ Every target carries `__meta_netbox_*` labels describing the Netbox object.
87
+ **Prometheus discards labels starting with `__` after service discovery**, so
88
+ they have to be copied into real labels with `relabel_configs` (see below) if you
89
+ want them on your metrics.
90
+
91
+ A label is only present when the underlying field is populated — a device with no
92
+ tenant has no `__meta_netbox_tenant`.
93
+
94
+ | Label | Devices | VMs | Services | IPs |
95
+ |---|:-:|:-:|:-:|:-:|
96
+ | `id`, `name`, `status`, `model` | ✓ | ✓ | id/name only | ✓ (no name) |
97
+ | `primary_ip`, `primary_ip4`, `primary_ip6` | ✓ | ✓ | from parent | |
98
+ | `oob_ip` | ✓ | | from parent | |
99
+ | `ip` | | | | ✓ |
100
+ | `role`, `role_slug` | ✓ | ✓ | | `role` only |
101
+ | `platform`, `platform_slug` | ✓ | ✓ | | |
102
+ | `device_type`, `device_type_slug` | ✓ | | | |
103
+ | `site`, `site_slug` | ✓ | ✓ | from parent | |
104
+ | `scope`, `scope_slug` | | ✓ ¹ | | |
105
+ | `location`, `location_slug` | ✓ | | | |
106
+ | `rack`, `rack_u_position` | ✓ | | | |
107
+ | `cluster`, `cluster_group`, `cluster_type` | ✓ ² | ✓ | | |
108
+ | `tenant`, `tenant_slug` | ✓ | ✓ | from parent | ✓ |
109
+ | `tenant_group`, `tenant_group_slug` | ✓ | ✓ | from parent | ✓ |
110
+ | `tags`, `tag_slugs` | ✓ | ✓ | ✓ | ✓ |
111
+ | `services` | ✓ | ✓ | | |
112
+ | `contact_<priority>_{name,email,comments,role}` | ✓ | ✓ | from parent | |
113
+ | `custom_field_<name>` | ✓ | ✓ | ✓ | ✓ |
114
+ | `description` | ✓ | | | |
115
+ | `parent`, `display`, `ports`, `ipaddresses` | | | ✓ | |
116
+
117
+ ¹ Netbox 4.2 replaced the cluster's site with a generic `scope`. On 4.2+ a VM
118
+ emits `scope`/`scope_slug` for the cluster, and `site`/`site_slug` for its own
119
+ site; below 4.2 the cluster's site is reported as `site`.
120
+ ² Only when the device is assigned to a cluster.
121
+
122
+ Config context can additionally set `__metrics_path__` and `__scheme__`, which
123
+ Prometheus consumes directly (see [Config context](#config-context)).
124
+
125
+ ### Relabeling
126
+
127
+ The `__meta_netbox_*` labels are dropped unless you map them. A typical device job
128
+ scraping node_exporter on the primary IP:
129
+
130
+ ```yaml
131
+ scrape_configs:
132
+ - job_name: netbox-devices
133
+ http_sd_configs:
134
+ - url: http://netbox:8080/api/plugins/prometheus-sd/devices/?status=active&tag=monitoring
135
+ refresh_interval: 60s
136
+ authorization:
137
+ type: Token
138
+ credentials: "<your-netbox-api-token>"
139
+
140
+ relabel_configs:
141
+ # Skip anything without a primary IPv4, otherwise the address below is empty.
142
+ - source_labels: [__meta_netbox_primary_ip4]
143
+ regex: ^$
144
+ action: drop
145
+
146
+ # Scrape the primary IP instead of the device name, which may not resolve.
147
+ - source_labels: [__meta_netbox_primary_ip4]
148
+ target_label: __address__
149
+ replacement: "$1:9100"
150
+
151
+ # Keep the Netbox name as the instance label rather than the IP.
152
+ - source_labels: [__meta_netbox_name]
153
+ target_label: instance
154
+
155
+ # Promote the dimensions worth alerting and grouping on.
156
+ - source_labels: [__meta_netbox_site_slug]
157
+ target_label: site
158
+ - source_labels: [__meta_netbox_role_slug]
159
+ target_label: role
160
+ - source_labels: [__meta_netbox_tenant_slug]
161
+ target_label: tenant
162
+ ```
163
+
164
+ Two things worth knowing:
165
+
166
+ - `tags` and `tag_slugs` are comma-joined, so match them with `.*,?value,?.*`
167
+ rather than `=`.
168
+ - Devices and VMs use the same label names, so one set of `relabel_configs` can
169
+ be reused across both jobs.
170
+
171
+ ### Config context
172
+
173
+ The plugin can also discover extra config to inject in the HTTP SD JSON from the config context of the devices/virtual machines.
174
+ If you have a `prometheus-plugin-prometheus-sd` entry in your config context with the following schema it will be automatically picked up:
175
+
176
+ ```yaml
177
+ prometheus-plugin-prometheus-sd:
178
+ - metrics_path: /not/metrics
179
+ port: 4242
180
+ scheme: https
181
+ - port: 4243
182
+ ```
183
+
184
+ This allow you to configure those values directly into netbox instead of doing that inside the Prometheus
185
+ config and filtering each scenario by a specific tag for instance.
186
+
187
+ If there is only one entry you can also use this form:
188
+
189
+ ```yaml
190
+ prometheus-plugin-prometheus-sd:
191
+ metrics_path: /not/metrics
192
+ port: 4242
193
+ scheme: https
194
+ ```
195
+
196
+ ### Example
197
+
198
+ [`example/prometheus.yml`](example/prometheus.yml) is a complete Prometheus
199
+ configuration covering all four endpoints, with `relabel_configs` mapping the
200
+ `__meta_netbox_*` labels onto real ones.
201
+
202
+ It is not a snippet that happens to be in the repository: the test suite runs
203
+ Prometheus against this exact file and asserts that the expected targets and
204
+ labels are discovered, so it cannot quietly stop working.
205
+
206
+ To see it running, with a Netbox that has demo data already loaded:
207
+
208
+ ```bash
209
+ poetry run invoke build-dev
210
+ ```
211
+
212
+ - Netbox: <http://localhost:8000> (`admin` / `admin`)
213
+ - Prometheus: <http://localhost:9090> — discovered targets are under
214
+ Status → Target health, and expanding one shows the raw `__meta_netbox_*`
215
+ labels before relabeling.
216
+
217
+ The demo data is the unit-test fixtures, so it also exercises config context
218
+ (the VMs get two targets each, on different ports), services, contacts and tags.
219
+
220
+ ## Development
221
+
222
+ We use [Poetry](https://python-poetry.org/) for dependency management and [invoke](https://www.pyinvoke.org/) as task runner.
223
+ To test the plugin in an isolated environment, we use [testcontainers](https://testcontainers.com/?language=python)
224
+ which creates "throwaway, lightweight" Netbox Docker containers.
225
+
226
+ Install the dependencies with `poetry install`, then run the tasks through
227
+ `poetry run` (this works on every Poetry version, whereas `poetry shell` was moved
228
+ into a separate plugin in Poetry 2.0):
229
+
230
+ ```bash
231
+ # Unit tests plus the Prometheus end-to-end check
232
+ poetry run invoke test
233
+
234
+ # Test against a specific Netbox release (default: latest)
235
+ NETBOX_VER=v4.6.5 poetry run invoke test
236
+
237
+ # Either half on its own
238
+ poetry run invoke unittest
239
+ poetry run invoke test-prometheus
240
+ ```
241
+
242
+ The Netbox image is built from the `Dockerfile` in the repository root.
243
+
244
+ Testing has two layers:
245
+
246
+ - **Unit tests** — plain Django tests under `netbox_prometheus_sd/tests/`,
247
+ executed inside the Netbox container. These cover the serializers and the
248
+ label output.
249
+ - **Prometheus end-to-end** — Netbox serving real HTTP with seeded data, and a
250
+ real Prometheus configured from `example/prometheus.yml`. It asserts that the
251
+ expected jobs discover targets and that relabeling produced the expected
252
+ labels.
253
+
254
+ The second layer exists because "valid JSON with targets and labels" is not the
255
+ same as "Prometheus accepts this as an `http_sd` source". A response that
256
+ Prometheus rejects would pass every unit test in this repository. It also keeps
257
+ the documented example honest, since it is the file under test.
258
+
259
+ Features should be covered by a test, but sometimes it is easier to develop
260
+ against a running system:
261
+
262
+ ```bash
263
+ # Netbox + Prometheus with demo data, left running until Ctrl+C
264
+ poetry run invoke build-dev
265
+ ```
266
+
267
+ Netbox is on <http://localhost:8000> (`admin` / `admin`) and Prometheus on
268
+ <http://localhost:9090>, already scraping it.
269
+
270
+ API endpoints for testing can be found at http://localhost:8000/api/plugins/prometheus-sd/
271
+
272
+ ## Conventional Commits
273
+
274
+ This repository follows the Conventional Commits specification for versioning and changelog generation.
275
+ Conventional Commits provide a standardized way of writing commit messages to convey semantic meaning
276
+ about the changes made. Each commit message follows a defined format that includes a type,
277
+ an optional scope, and a message. The types typically include features, fixes, documentation, and more.
278
+ By adhering to this convention, we ensure clear and automated versioning, release notes, and changelog generation.
@@ -1,7 +1,7 @@
1
1
  from netbox.plugins import PluginConfig
2
2
 
3
3
  # Placeholder for semantic release
4
- __VERSION__ = "2.0.0"
4
+ __VERSION__ = "2.2.0"
5
5
 
6
6
 
7
7
  class PrometheusSD(PluginConfig):
@@ -8,14 +8,19 @@ from utilities.filters import (
8
8
  )
9
9
 
10
10
  from ipam.filtersets import ServiceFilterSet as NetboxServiceFilterSet
11
+ from ipam.models import Service
12
+
13
+ # Netbox 4.7 replaced the Service.ports ArrayField with `port_mappings`, and
14
+ # its filterset metaclass rejects a filter on a field that no longer exists.
15
+ SERVICE_HAS_PORTS_FIELD = any(f.name == "ports" for f in Service._meta.get_fields())
11
16
 
12
17
 
13
18
  class ServiceFilterSet(NetboxServiceFilterSet):
14
- """Filter set to support tenancy over the device/VM foreign key.
19
+ """Filter set to support tenancy and site filtering over the device/VM foreign key.
15
20
 
16
- Tenancy in Netbox is very incosistent and the relationship on its own is defined across many different models. This
21
+ Tenancy in Netbox is very inconsistent and the relationship on its own is defined across many different models. This
17
22
  means that supporting all layers is nearly impossible without a stronger upstream support. For this reason only the
18
- "first level" tenancy is supported by this filter set.
23
+ "first level" tenancy is supported by the tenant filter set.
19
24
  """
20
25
 
21
26
  tenant_id = MultiValueNumberFilter(
@@ -28,9 +33,21 @@ class ServiceFilterSet(NetboxServiceFilterSet):
28
33
  label=_("Tenant (slug)"),
29
34
  )
30
35
 
31
- # fix to make the test_missing_filters pass
36
+ site_id = MultiValueNumberFilter(
37
+ method="filter_by_site_id",
38
+ label=_("Site (ID)"),
39
+ )
40
+
41
+ site = MultiValueCharFilter(
42
+ method="filter_by_site_slug",
43
+ label=_("Site (slug)"),
44
+ )
45
+
46
+ # Netbox < 4.7 only exposes the ports array as `port`, but Netbox's
47
+ # test_missing_filters wants a filter named after the model field.
32
48
  # see: https://github.com/netbox-community/netbox/blob/master/netbox/utilities/testing/filtersets.py#L98
33
- ports = NumericArrayFilter(field_name="ports", lookup_expr="contains")
49
+ if SERVICE_HAS_PORTS_FIELD:
50
+ ports = NumericArrayFilter(field_name="ports", lookup_expr="contains")
34
51
 
35
52
  def filter_by_cluster_tenant_id(self, queryset, name, value):
36
53
  return queryset.filter(
@@ -54,3 +71,14 @@ class ServiceFilterSet(NetboxServiceFilterSet):
54
71
  Q(device__tenant__slug__in=value)
55
72
  | Q(virtual_machine__tenant__slug__in=value)
56
73
  )
74
+
75
+ def filter_by_site_id(self, queryset, name, value):
76
+ return queryset.filter(
77
+ Q(device__site_id__in=value) | Q(virtual_machine__site_id__in=value)
78
+ )
79
+
80
+ def filter_by_site_slug(self, queryset, name, value):
81
+ return queryset.filter(
82
+ Q(device__site__slug__in=value)
83
+ | Q(virtual_machine__site__slug__in=value)
84
+ )
@@ -0,0 +1,63 @@
1
+ from django.test import TestCase
2
+
3
+ from dcim.models.sites import Site
4
+ from ipam.models import Service
5
+ from tenancy.models import Tenant
6
+
7
+ try: # NetBox 4.7+
8
+ from utilities.testing import ChangeLoggedFilterSetTestMixin
9
+ except ImportError: # NetBox <4.7
10
+ from utilities.testing import (
11
+ ChangeLoggedFilterSetTests as ChangeLoggedFilterSetTestMixin,
12
+ )
13
+
14
+ from . import utils
15
+ from ..filtersets import ServiceFilterSet
16
+
17
+
18
+ class ServiceTestCase(TestCase, ChangeLoggedFilterSetTestMixin):
19
+ queryset = Service.objects.all()
20
+ filterset = ServiceFilterSet
21
+
22
+ @classmethod
23
+ def setUpTestData(cls):
24
+ """Netbox requires us to define test data in this method, otherwise the ORM won't pick them."""
25
+ for i in range(1, 4):
26
+ utils.build_device_full(f"firewall-full-0{i}", i)
27
+ utils.build_vm_full(f"vm-full-0{i}.example.com", i)
28
+
29
+ def test_device_tenant(self):
30
+ tenant = Tenant.objects.all()[0]
31
+
32
+ params = {"tenant_id": [tenant.pk]}
33
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
34
+ params = {"tenant": [tenant.slug]}
35
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
36
+
37
+ def test_vm_tenant(self):
38
+ tenant = Tenant.objects.all()[0]
39
+
40
+ params = {"tenant_id": [tenant.pk]}
41
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
42
+ params = {"tenant": [tenant.slug]}
43
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
44
+
45
+ def test_device_site(self):
46
+ # build_device_full() assigns devices directly to the "site" site.
47
+ site = Site.objects.get(slug="site")
48
+
49
+ params = {"site_id": [site.pk]}
50
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
51
+ params = {"site": [site.slug]}
52
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
53
+
54
+ def test_vm_site(self):
55
+ # build_vm_full() assigns VMs directly to the "campus-a" site, distinct
56
+ # from the devices' site, so this also verifies the filter doesn't match
57
+ # across device/VM boundaries.
58
+ site = Site.objects.get(slug="campus-a")
59
+
60
+ params = {"site_id": [site.pk]}
61
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
62
+ params = {"site": [site.slug]}
63
+ self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
@@ -9,6 +9,7 @@ from . import utils
9
9
 
10
10
  from ..api.utils import NETBOX_RELEASE_CURRENT, NETBOX_RELEASE_41
11
11
 
12
+
12
13
  class PrometheusVirtualMachineSerializerTests(TestCase):
13
14
  def test_vm_minimal_to_target(self):
14
15
 
@@ -498,7 +499,12 @@ class PrometheusServiceSerializerTests(TestCase):
498
499
  )
499
500
  self.assertTrue(
500
501
  utils.dictContainsSubset(
501
- {"__meta_netbox_display": "ssh (TCP/22)"}, data["labels"]
502
+ {
503
+ "__meta_netbox_display": utils.expected_service_display(
504
+ "ssh", "TCP", 22
505
+ )
506
+ },
507
+ data["labels"],
502
508
  )
503
509
  )
504
510
  self.assertTrue(
@@ -562,7 +568,12 @@ class PrometheusServiceSerializerTests(TestCase):
562
568
  )
563
569
  self.assertTrue(
564
570
  utils.dictContainsSubset(
565
- {"__meta_netbox_display": "ssh (TCP/22)"}, data["labels"]
571
+ {
572
+ "__meta_netbox_display": utils.expected_service_display(
573
+ "ssh", "TCP", 22
574
+ )
575
+ },
576
+ data["labels"],
566
577
  )
567
578
  )
568
579
  self.assertTrue(
@@ -29,7 +29,7 @@ def dictContainsSubset(subset, fullset):
29
29
 
30
30
 
31
31
  def build_cluster():
32
- try: # NetBox 4.2+
32
+ try: # NetBox 4.2+
33
33
  scope_type = ContentType.objects.get_for_model(Site)
34
34
  return Cluster.objects.get_or_create(
35
35
  name="DC1",
@@ -38,7 +38,7 @@ def build_cluster():
38
38
  scope_type=scope_type,
39
39
  scope_id=Site.objects.get_or_create(name="Campus A", slug="campus-a")[0].id,
40
40
  )[0]
41
- except FieldError: # NetBox <4.2
41
+ except FieldError: # NetBox <4.2
42
42
  return Cluster.objects.get_or_create(
43
43
  name="DC1",
44
44
  group=ClusterGroup.objects.get_or_create(name="VMware")[0],
@@ -119,6 +119,10 @@ def build_vm_full(name, ip_octet=1):
119
119
  )[0]
120
120
 
121
121
  vm.tenant = build_tenant()
122
+ # Direct site assignment, independent of the cluster's own site/scope -- Netbox
123
+ # supports both, and the plugin's ServiceFilterSet only filters on this direct
124
+ # relation (see filtersets.py).
125
+ vm.site = Site.objects.get_or_create(name="Campus A", slug="campus-a")[0]
122
126
  vm.custom_field_data = build_custom_fields()
123
127
  vm.role = DeviceRole.objects.get_or_create(name="VM", slug="vm", vm_role=True)[0]
124
128
  vm.primary_ip4 = IPAddress.objects.get_or_create(
@@ -137,15 +141,29 @@ def build_vm_full(name, ip_octet=1):
137
141
  return vm
138
142
 
139
143
 
144
+ def expected_service_display(name, protocol, port):
145
+ """Netbox 4.7 reduced Service.__str__ to the bare name; older releases
146
+ append the protocol and port list."""
147
+ if hasattr(Service, "port_mappings"):
148
+ return name
149
+ return f"{name} ({protocol}/{port})"
150
+
151
+
140
152
  def build_service_for(parent, **kwargs):
141
153
  """Create a service bound to parent, with the parent's primary IPv4 attached.
142
154
 
143
155
  Netbox 4.3 replaced Service.device/Service.virtual_machine with a single
144
156
  generic `parent` relation, which is read-only on older releases.
157
+
158
+ Netbox 4.7 replaced Service.protocol/Service.ports with `port_mappings`
159
+ and rejects the legacy pair at the ORM level.
145
160
  """
146
- try: # NetBox 4.3+
161
+ if hasattr(Service, "port_mappings") and "ports" in kwargs:
162
+ protocol = kwargs.pop("protocol", "tcp")
163
+ kwargs["port_mappings"] = [f"{protocol}/{port}" for port in kwargs.pop("ports")]
164
+ try: # NetBox 4.3+
147
165
  service = Service.objects.create(parent=parent, **kwargs)
148
- except AttributeError: # NetBox <4.3
166
+ except AttributeError: # NetBox <4.3
149
167
  field = "device" if isinstance(parent, Device) else "virtual_machine"
150
168
  service = Service.objects.create(**{field: parent}, **kwargs)
151
169
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "netbox-plugin-prometheus-sd"
3
- version = "v2.0.0" # placeholder
3
+ version = "2.2.0" # placeholder
4
4
  description = "A Netbox plugin to provide Netbox entires to Prometheus HTTP service discovery"
5
5
  authors = ["Felix Peters <felix.peters@breuninger.de>"]
6
6
  license = "MIT"
@@ -1,158 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: netbox-plugin-prometheus-sd
3
- Version: 2.0.0
4
- Summary: A Netbox plugin to provide Netbox entires to Prometheus HTTP service discovery
5
- License: MIT
6
- Author: Felix Peters
7
- Author-email: felix.peters@breuninger.de
8
- Requires-Python: >=3.10,<4.0
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Programming Language :: Python :: 3.13
15
- Description-Content-Type: text/markdown
16
-
17
- # netbox-plugin-prometheus-sd
18
-
19
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
20
- [![CI](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/workflows/CI/badge.svg?event=push)](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/actions?query=workflow%3ACI)
21
- [![PyPI](https://img.shields.io/pypi/v/netbox-plugin-prometheus-sd)](https://pypi.org/project/netbox-plugin-prometheus-sd/)
22
-
23
- [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/flxpeters)
24
-
25
- Provide Prometheus `http_sd` compatible API Endpoint with data from Netbox.
26
-
27
- HTTP SD is a feature since Prometheus 2.28.0 that allows hosts to be found via a URL instead of just files.
28
- This plugin implements API endpoints in Netbox to make devices, services, IPs and virtual machines available to Prometheus.
29
-
30
- ## Compatibility
31
-
32
- We aim to support the latest major versions of Netbox.
33
- For now we support Netbox `>= 4.0` including bugfix versions. Older versions may work, but without any guarantee.
34
-
35
- Check the `.github/workflows/ci.yml` pipeline for the current tested builds.
36
- Other versions may work, but we do not test them explicitly. All relevant target versions are tested in CI.
37
-
38
- ## Installation
39
-
40
- The plugin is available as a Python package in pypi and can be installed with pip
41
-
42
- ```bash
43
- pip install netbox-plugin-prometheus-sd
44
- ```
45
-
46
- Enable the plugin in /opt/netbox/netbox/netbox/configuration.py:
47
-
48
- ```python
49
- PLUGINS = ['netbox_prometheus_sd']
50
- ```
51
-
52
- The plugin has not further plugin configuration.
53
-
54
- ## Usage
55
-
56
- The plugin only provides a new API endpoint on the Netbox API. There is no further action required after installation.
57
-
58
- ### API
59
-
60
- The plugin reuses Netbox API view sets with new serializers for Prometheus.
61
- This means that all filters that can be used on the Netbox API can also be used to filter Prometheus targets.
62
- Paging is disabled because Prometheus does not support paged results.
63
-
64
- The plugin also reuses the Netbox authentication and permission model.
65
- Depending on the Netbox configuration, a token with valid object permissions must be passed to Netbox.
66
-
67
- ```
68
- GET /api/plugins/prometheus-sd/devices/ Get a list of devices in a prometheus compatible format
69
- GET /api/plugins/prometheus-sd/virtual-machines/ Get a list of vms in a prometheus compatible format
70
- GET /api/plugins/prometheus-sd/services/ Get a list of services in a prometheus compatible format
71
- GET /api/plugins/prometheus-sd/ip-addresses/ Get a list of ip in a prometheus compatible format
72
- ```
73
-
74
- #### Extended services filters
75
-
76
- Apart from standard Netbox filters, services endpoint also supports `tenant=<slug>` or `tenant_id=<id>` parameters.
77
- The lookup is only executed against the `tenant` attribute of the object associated with the service.
78
-
79
- ### Config context
80
-
81
- The plugin can also discover extra config to inject in the HTTP SD JSON from the config context of the devices/virtual machines.
82
- If you have a `prometheus-plugin-prometheus-sd` entry in your config context with the following schema it will be automatically picked up:
83
-
84
- ```yaml
85
- prometheus-plugin-prometheus-sd:
86
- - metrics_path: /not/metrics
87
- port: 4242
88
- scheme: https
89
- - port: 4243
90
- ```
91
-
92
- This allow you to configure those values directly into netbox instead of doing that inside the Prometheus
93
- config and filtering each scenario by a specific tag for instance.
94
-
95
- If there is only one entry you can also use this form:
96
-
97
- ```yaml
98
- prometheus-plugin-prometheus-sd:
99
- metrics_path: /not/metrics
100
- port: 4242
101
- scheme: https
102
- ```
103
-
104
- ### Example
105
-
106
- A working example on how to use this plugin with Prometheus is located at the `example` folder.
107
- Netbox content is created by using Netbox docker initializers.
108
-
109
- The demo data doesn't make sense, but they are good enough for demonstrating how to configure Prometheus
110
- and get demo data to Prometheus service discovery.
111
-
112
- Go to the `example` folder and run `docker-compose up --build`. Prometheus should get available on `http://localhost:9090`.
113
-
114
- Push some example devices and objects to Netbox using the initializers:
115
-
116
- ```
117
- docker-compose exec netbox /opt/netbox/netbox/manage.py load_initializer_data --path /opt/netbox/initializers
118
- ```
119
-
120
- Netbox content should then be available in the service discovery tab.
121
-
122
- ## Development
123
-
124
- We use [Poetry](https://python-poetry.org/) for dependency management and [invoke](https://www.pyinvoke.org/) as task runner.
125
- To test the plugin in an isolated environment, we use [testcontainers](https://testcontainers.com/?language=python)
126
- which creates "throwaway, lightweight" Netbox Docker containers.
127
-
128
- All code to run in docker is located under `develop`.
129
- To start a virtual env managed by poetry run `poetry shell`.
130
- All following commands are started inside this environment.
131
-
132
- In order to run tests invoke the tests:
133
-
134
- ```bash
135
- # Build the containers and execute all tests
136
- invoke test
137
- ```
138
-
139
- Features should be covered by a unit test, but some times it's easier to develop on an running system.
140
-
141
- ```bash
142
- # Start a development environment
143
- invoke build_dev
144
- ```
145
-
146
- Visit http://localhost:8000 and log in with the default admin credentials.
147
- You can now define Netbox entities and test around.
148
-
149
- API endpoints for testing can be found at http://localhost:8000/api/plugins/prometheus-sd/
150
-
151
- ## Conventional Commits
152
-
153
- This repository follows the Conventional Commits specification for versioning and changelog generation.
154
- Conventional Commits provide a standardized way of writing commit messages to convey semantic meaning
155
- about the changes made. Each commit message follows a defined format that includes a type,
156
- an optional scope, and a message. The types typically include features, fixes, documentation, and more.
157
- By adhering to this convention, we ensure clear and automated versioning, release notes, and changelog generation.
158
-
@@ -1,141 +0,0 @@
1
- # netbox-plugin-prometheus-sd
2
-
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![CI](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/workflows/CI/badge.svg?event=push)](https://github.com/FlxPeters/netbox-plugin-prometheus-sd/actions?query=workflow%3ACI)
5
- [![PyPI](https://img.shields.io/pypi/v/netbox-plugin-prometheus-sd)](https://pypi.org/project/netbox-plugin-prometheus-sd/)
6
-
7
- [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/flxpeters)
8
-
9
- Provide Prometheus `http_sd` compatible API Endpoint with data from Netbox.
10
-
11
- HTTP SD is a feature since Prometheus 2.28.0 that allows hosts to be found via a URL instead of just files.
12
- This plugin implements API endpoints in Netbox to make devices, services, IPs and virtual machines available to Prometheus.
13
-
14
- ## Compatibility
15
-
16
- We aim to support the latest major versions of Netbox.
17
- For now we support Netbox `>= 4.0` including bugfix versions. Older versions may work, but without any guarantee.
18
-
19
- Check the `.github/workflows/ci.yml` pipeline for the current tested builds.
20
- Other versions may work, but we do not test them explicitly. All relevant target versions are tested in CI.
21
-
22
- ## Installation
23
-
24
- The plugin is available as a Python package in pypi and can be installed with pip
25
-
26
- ```bash
27
- pip install netbox-plugin-prometheus-sd
28
- ```
29
-
30
- Enable the plugin in /opt/netbox/netbox/netbox/configuration.py:
31
-
32
- ```python
33
- PLUGINS = ['netbox_prometheus_sd']
34
- ```
35
-
36
- The plugin has not further plugin configuration.
37
-
38
- ## Usage
39
-
40
- The plugin only provides a new API endpoint on the Netbox API. There is no further action required after installation.
41
-
42
- ### API
43
-
44
- The plugin reuses Netbox API view sets with new serializers for Prometheus.
45
- This means that all filters that can be used on the Netbox API can also be used to filter Prometheus targets.
46
- Paging is disabled because Prometheus does not support paged results.
47
-
48
- The plugin also reuses the Netbox authentication and permission model.
49
- Depending on the Netbox configuration, a token with valid object permissions must be passed to Netbox.
50
-
51
- ```
52
- GET /api/plugins/prometheus-sd/devices/ Get a list of devices in a prometheus compatible format
53
- GET /api/plugins/prometheus-sd/virtual-machines/ Get a list of vms in a prometheus compatible format
54
- GET /api/plugins/prometheus-sd/services/ Get a list of services in a prometheus compatible format
55
- GET /api/plugins/prometheus-sd/ip-addresses/ Get a list of ip in a prometheus compatible format
56
- ```
57
-
58
- #### Extended services filters
59
-
60
- Apart from standard Netbox filters, services endpoint also supports `tenant=<slug>` or `tenant_id=<id>` parameters.
61
- The lookup is only executed against the `tenant` attribute of the object associated with the service.
62
-
63
- ### Config context
64
-
65
- The plugin can also discover extra config to inject in the HTTP SD JSON from the config context of the devices/virtual machines.
66
- If you have a `prometheus-plugin-prometheus-sd` entry in your config context with the following schema it will be automatically picked up:
67
-
68
- ```yaml
69
- prometheus-plugin-prometheus-sd:
70
- - metrics_path: /not/metrics
71
- port: 4242
72
- scheme: https
73
- - port: 4243
74
- ```
75
-
76
- This allow you to configure those values directly into netbox instead of doing that inside the Prometheus
77
- config and filtering each scenario by a specific tag for instance.
78
-
79
- If there is only one entry you can also use this form:
80
-
81
- ```yaml
82
- prometheus-plugin-prometheus-sd:
83
- metrics_path: /not/metrics
84
- port: 4242
85
- scheme: https
86
- ```
87
-
88
- ### Example
89
-
90
- A working example on how to use this plugin with Prometheus is located at the `example` folder.
91
- Netbox content is created by using Netbox docker initializers.
92
-
93
- The demo data doesn't make sense, but they are good enough for demonstrating how to configure Prometheus
94
- and get demo data to Prometheus service discovery.
95
-
96
- Go to the `example` folder and run `docker-compose up --build`. Prometheus should get available on `http://localhost:9090`.
97
-
98
- Push some example devices and objects to Netbox using the initializers:
99
-
100
- ```
101
- docker-compose exec netbox /opt/netbox/netbox/manage.py load_initializer_data --path /opt/netbox/initializers
102
- ```
103
-
104
- Netbox content should then be available in the service discovery tab.
105
-
106
- ## Development
107
-
108
- We use [Poetry](https://python-poetry.org/) for dependency management and [invoke](https://www.pyinvoke.org/) as task runner.
109
- To test the plugin in an isolated environment, we use [testcontainers](https://testcontainers.com/?language=python)
110
- which creates "throwaway, lightweight" Netbox Docker containers.
111
-
112
- All code to run in docker is located under `develop`.
113
- To start a virtual env managed by poetry run `poetry shell`.
114
- All following commands are started inside this environment.
115
-
116
- In order to run tests invoke the tests:
117
-
118
- ```bash
119
- # Build the containers and execute all tests
120
- invoke test
121
- ```
122
-
123
- Features should be covered by a unit test, but some times it's easier to develop on an running system.
124
-
125
- ```bash
126
- # Start a development environment
127
- invoke build_dev
128
- ```
129
-
130
- Visit http://localhost:8000 and log in with the default admin credentials.
131
- You can now define Netbox entities and test around.
132
-
133
- API endpoints for testing can be found at http://localhost:8000/api/plugins/prometheus-sd/
134
-
135
- ## Conventional Commits
136
-
137
- This repository follows the Conventional Commits specification for versioning and changelog generation.
138
- Conventional Commits provide a standardized way of writing commit messages to convey semantic meaning
139
- about the changes made. Each commit message follows a defined format that includes a type,
140
- an optional scope, and a message. The types typically include features, fixes, documentation, and more.
141
- By adhering to this convention, we ensure clear and automated versioning, release notes, and changelog generation.
@@ -1,36 +0,0 @@
1
- from django.test import TestCase
2
-
3
- from ipam.models import Service
4
- from tenancy.models import Tenant
5
- from utilities.testing import ChangeLoggedFilterSetTests
6
-
7
- from . import utils
8
- from ..filtersets import ServiceFilterSet
9
-
10
-
11
- class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
12
- queryset = Service.objects.all()
13
- filterset = ServiceFilterSet
14
-
15
- @classmethod
16
- def setUpTestData(cls):
17
- """Netbox requires us to define test data in this method, otherwise the ORM won't pick them."""
18
- for i in range(1, 4):
19
- utils.build_device_full(f"firewall-full-0{i}", i)
20
- utils.build_vm_full(f"vm-full-0{i}.example.com", i)
21
-
22
- def test_device_tenant(self):
23
- tenant = Tenant.objects.all()[0]
24
-
25
- params = {"tenant_id": [tenant.pk]}
26
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
27
- params = {"tenant": [tenant.slug]}
28
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
29
-
30
- def test_vm_tenant(self):
31
- tenant = Tenant.objects.all()[0]
32
-
33
- params = {"tenant_id": [tenant.pk]}
34
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
35
- params = {"tenant": [tenant.slug]}
36
- self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)