Skip to content

Commit 2a1cfe4

Browse files
authored
🚀 release (#70)
2 parents 8587357 + 0b0a3b4 commit 2a1cfe4

File tree

11 files changed

+4759
-1447
lines changed

11 files changed

+4759
-1447
lines changed

‎README.md‎

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ pipelines.
1010
More information about Diode can be found
1111
at [https://netboxlabs.com/blog/introducing-diode-streamlining-data-ingestion-in-netbox/](https://netboxlabs.com/blog/introducing-diode-streamlining-data-ingestion-in-netbox/).
1212

13+
## Prerequisites
14+
- Python 3.10 or later installed
15+
1316
## Installation
1417

1518
```bash
@@ -79,6 +82,123 @@ if __name__ == "__main__":
7982

8083
```
8184

85+
See all [examples](./examples) for reference.
86+
87+
### Using Metadata
88+
89+
Entities support attaching custom metadata as key-value pairs. Metadata can be used to store additional context, tracking information, or custom attributes that don't fit into the standard NetBox fields.
90+
91+
```python
92+
from netboxlabs.diode.sdk import DiodeClient, Entity
93+
from netboxlabs.diode.sdk.ingester import Device, Site, IPAddress
94+
95+
with DiodeClient(
96+
target="grpc://localhost:8080/diode",
97+
app_name="my-app",
98+
app_version="1.0.0",
99+
) as client:
100+
# Create a device with metadata
101+
# Note: Both the device and its nested site can have its own metadata
102+
device = Device(
103+
name="Device A",
104+
device_type="Device Type A",
105+
site=Site(
106+
name="Site ABC",
107+
metadata={
108+
"site_region": "us-west",
109+
"site_cost_center": "CC-001",
110+
},
111+
),
112+
role="Role ABC",
113+
metadata={
114+
"source": "network_discovery",
115+
"discovered_at": "2024-01-15T10:30:00Z",
116+
"import_batch": "batch-123",
117+
"priority": 1,
118+
"verified": True,
119+
},
120+
)
121+
122+
# Create an IP address with metadata
123+
ip_address = IPAddress(
124+
address="192.168.1.10/24",
125+
status="active",
126+
metadata={
127+
"last_scan": "2024-01-15T12:00:00Z",
128+
"scan_id": "scan-456",
129+
"response_time": 23.5,
130+
"reachable": True,
131+
"owner_team": "network-ops",
132+
},
133+
)
134+
135+
# Create a site with metadata
136+
site = Site(
137+
name="Data Center 1",
138+
status="active",
139+
metadata={
140+
"region": "us-west",
141+
"cost_center": "CC-001",
142+
"capacity": 500,
143+
"is_primary": True,
144+
"contact_email": "dc1-ops@example.com",
145+
},
146+
)
147+
148+
entities = [Entity(device=device), Entity(ip_address=ip_address), Entity(site=site)]
149+
response = client.ingest(entities=entities)
150+
if response.errors:
151+
print(f"Errors: {response.errors}")
152+
```
153+
154+
#### Adding request-level metadata
155+
156+
In addition to entity-level metadata, you can attach metadata to the entire ingestion request using the `metadata` keyword argument. This is useful for tracking information about the ingestion batch itself, such as the data source, batch ID, or processing context.
157+
158+
```python
159+
from netboxlabs.diode.sdk import DiodeClient, Entity
160+
from netboxlabs.diode.sdk.ingester import Device, Site
161+
162+
with DiodeClient(
163+
target="grpc://localhost:8080/diode",
164+
app_name="my-app",
165+
app_version="1.0.0",
166+
) as client:
167+
# Create device A
168+
device_a = Device(
169+
name="Device A",
170+
site=Site(name="Site ABC"),
171+
)
172+
173+
# Create device B
174+
device_b = Device(
175+
name="Device B",
176+
site=Site(name="Site XYZ"),
177+
)
178+
179+
entities = [Entity(device=device_a), Entity(device=device_b)]
180+
181+
# Add request-level metadata to track the ingestion batch
182+
response = client.ingest(
183+
entities=entities,
184+
metadata={
185+
"batch_id": "import-2024-01-15",
186+
"source_system": "network_scanner",
187+
"import_type": "automated",
188+
"record_count": len(entities),
189+
"validated": True,
190+
},
191+
)
192+
if response.errors:
193+
print(f"Errors: {response.errors}")
194+
```
195+
196+
Request-level metadata is included in the `IngestRequest` and can be useful for:
197+
- Tracking data sources and ingestion pipelines
198+
- Correlating entities within a batch
199+
- Debugging and auditing data imports
200+
- Adding contextual information for downstream processing
201+
82202
### TLS verification and certificates
83203

84204
TLS verification is controlled by the target URL scheme:
@@ -109,6 +229,18 @@ export DIODE_CERT_FILE=/path/to/cert.pem
109229
export DIODE_SKIP_TLS_VERIFY=true
110230
```
111231

232+
#### For legacy certificates (CN-only, no SANs)
233+
234+
```python
235+
client = DiodeClient(
236+
target="grpcs://example.com",
237+
app_name="my-app",
238+
app_version="1.0.0",
239+
cert_file="/path/to/cert.pem",
240+
skip_tls_verify=True,
241+
)
242+
```
243+
112244
### Dry run mode
113245

114246
`DiodeDryRunClient` generates ingestion requests without contacting a Diode server. Requests are printed to stdout by default, or written to JSON files when `output_dir` (or the `DIODE_DRY_RUN_OUTPUT_DIR` environment variable) is specified. The `app_name` parameter serves as the filename prefix; if not provided, `dryrun` is used as the default prefix. The file name is suffixed with a nanosecond-precision timestamp, resulting in the format `<app_name>_<timestamp_ns>.json`.
@@ -149,6 +281,108 @@ diode-replay-dryrun \
149281
my_app_92722156890707.json
150282
```
151283

284+
#### Adding request-level metadata to dry run output
285+
286+
You can include request-level metadata in the dry run output using the `metadata` keyword argument. This metadata will be included in the JSON output file as part of the `IngestRequest`:
287+
288+
```python
289+
from netboxlabs.diode.sdk import DiodeDryRunClient, Entity
290+
from netboxlabs.diode.sdk.ingester import Device
291+
292+
with DiodeDryRunClient(app_name="my_app", output_dir="/tmp") as client:
293+
# Add request-level metadata
294+
client.ingest(
295+
[Entity(device=Device(name="Device A"))],
296+
metadata={
297+
"batch_id": "import-2024-01",
298+
"source": "csv_import",
299+
"validated": True,
300+
"record_count": 150,
301+
}
302+
)
303+
```
304+
305+
The resulting JSON file will include the metadata in the `IngestRequest`, making it visible when reviewing the dry run output.
306+
307+
### CLI to replay dry-run files
308+
309+
A small helper command is included to ingest JSON files created by the
310+
`DiodeDryRunClient` and send them to a running Diode service.
311+
312+
Install the helper using `pip`:
313+
314+
```bash
315+
pip install netboxlabs-diode-sdk
316+
```
317+
318+
Run it by providing one or more JSON files and connection details. The command supports replaying multiple dry-run files in a single request:
319+
320+
```bash
321+
diode-replay-dryrun \
322+
--file /tmp/my_app_92722156890707.json \
323+
--file /tmp/other.json \
324+
--target grpc://localhost:8080/diode \
325+
--app-name my-test-app \
326+
--app-version 0.0.1 \
327+
--client-id YOUR_CLIENT_ID \
328+
--client-secret YOUR_CLIENT_SECRET
329+
```
330+
331+
The `--file`, `--target`, `--app-name`, and `--app-version` arguments are required. You may
332+
repeat `--file` to specify multiple files. OAuth2
333+
credentials can be supplied using `--client-id` and `--client-secret` or the
334+
`DIODE_CLIENT_ID` and `DIODE_CLIENT_SECRET` environment variables.
335+
336+
### OTLP client
337+
338+
`DiodeOTLPClient` converts ingestion entities into OpenTelemetry log records and exports them to an OTLP endpoint (gRPC). This is useful when a collector ingests log data and forwards it to Diode.
339+
340+
```python
341+
from netboxlabs.diode.sdk import Entity, DiodeOTLPClient
342+
343+
with DiodeOTLPClient(
344+
target="grpc://localhost:4317",
345+
app_name="my-producer",
346+
app_version="0.0.1",
347+
) as client:
348+
client.ingest([Entity(site="Site1")])
349+
```
350+
351+
Each entity is serialised to JSON and sent as a log record with producer metadata so downstream collectors can enrich and forward the payload. The client raises `OTLPClientError` when the export fails. TLS behaviour honours the existing `DIODE_SKIP_TLS_VERIFY` and `DIODE_CERT_FILE` environment variables.
352+
353+
#### Adding request-level metadata as OTLP resource attributes
354+
355+
You can add request-level metadata to OTLP exports using the `metadata` keyword argument. This metadata is automatically mapped to OTLP resource attributes with a `diode.metadata.` prefix:
356+
357+
```python
358+
from netboxlabs.diode.sdk import DiodeOTLPClient, Entity
359+
from netboxlabs.diode.sdk.ingester import Site
360+
361+
with DiodeOTLPClient(
362+
target="grpc://localhost:4317",
363+
app_name="otlp-producer",
364+
app_version="1.0.0",
365+
) as client:
366+
# Add request-level metadata
367+
client.ingest(
368+
[Entity(site=Site(name="Site 1"))],
369+
metadata={
370+
"environment": "production",
371+
"deployment": "us-west-2",
372+
"version": "1.2.3",
373+
"priority": 5,
374+
},
375+
)
376+
```
377+
378+
The resulting OTLP log records will include resource attributes like:
379+
- `diode.metadata.environment="production"`
380+
- `diode.metadata.deployment="us-west-2"`
381+
- `diode.metadata.version="1.2.3"`
382+
- `diode.metadata.priority=5` (as integer)
383+
384+
These attributes are added alongside standard OTLP resource attributes (`service.name`, `service.version`, `diode.stream`, etc.), allowing downstream collectors and observability platforms to filter, route, and enrich the data based on this metadata.
385+
152386
## Supported entities (object types)
153387

154388
* ASN

‎netboxlabs/diode/sdk/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
from netboxlabs.diode.sdk.client import (
66
DiodeClient,
77
DiodeDryRunClient,
8+
DiodeOTLPClient,
89
load_dryrun_entities,
910
)
1011

1112
assert DiodeClient
1213
assert DiodeDryRunClient
14+
assert DiodeOTLPClient
1315
assert load_dryrun_entities

0 commit comments

Comments
 (0)