netbox-inventree-sync 0.1.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.
- netbox_inventree_sync-0.1.0/LICENSE +21 -0
- netbox_inventree_sync-0.1.0/PKG-INFO +356 -0
- netbox_inventree_sync-0.1.0/README.md +333 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/__init__.py +42 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/api/__init__.py +8 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/api/exceptions.py +19 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/api/serializers.py +253 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/api/urls.py +25 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/api/views.py +185 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/choices.py +28 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/filtersets.py +104 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/forms.py +194 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/graphql/__init__.py +18 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/graphql/filters.py +31 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/graphql/schema.py +19 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/graphql/types.py +51 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/jobs.py +114 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/management/__init__.py +0 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/management/commands/__init__.py +0 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/management/commands/inventree_sync.py +167 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/management/commands/seed_starter_mappings.py +198 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/migrations/0001_initial.py +140 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/migrations/0002_field_validators_and_help_text.py +17 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/migrations/__init__.py +0 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/models.py +310 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/navigation.py +51 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/search.py +37 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/signals.py +258 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/sync/__init__.py +13 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/sync/client.py +237 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/sync/compatibility.py +60 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/sync/engine.py +671 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/sync/fields.py +203 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/sync/payload.py +47 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/sync/validation.py +295 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tables.py +322 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/template_content.py +102 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/__init__.py +0 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_api.py +589 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_builder_ui.py +264 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_client.py +377 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_compatibility.py +158 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_engine.py +452 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_extras.py +285 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_fields.py +106 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_jobs.py +470 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_models.py +241 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_pointer_resolution.py +624 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_ui.py +531 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/tests/test_validation.py +382 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/urls.py +33 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync/views.py +630 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync.egg-info/PKG-INFO +356 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync.egg-info/SOURCES.txt +57 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync.egg-info/dependency_links.txt +1 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync.egg-info/requires.txt +5 -0
- netbox_inventree_sync-0.1.0/netbox_inventree_sync.egg-info/top_level.txt +1 -0
- netbox_inventree_sync-0.1.0/pyproject.toml +36 -0
- netbox_inventree_sync-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eduardo Soares
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netbox-inventree-sync
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Synchronize NetBox inventory data to InvenTree
|
|
5
|
+
Author-email: Eduardo Soares <edurbs@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/edurbs/netbox-inventree-sync
|
|
8
|
+
Project-URL: Documentation, https://github.com/edurbs/netbox-inventree-sync/tree/main/docs
|
|
9
|
+
Project-URL: Changelog, https://github.com/edurbs/netbox-inventree-sync/releases
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: System :: Systems Administration
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: requests
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest; extra == "dev"
|
|
21
|
+
Requires-Dist: responses; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# netbox-inventree-sync
|
|
25
|
+
|
|
26
|
+
A NetBox plugin that synchronizes NetBox inventory data to
|
|
27
|
+
[InvenTree](https://inventree.org).
|
|
28
|
+
|
|
29
|
+
NetBox is the source of truth: mapped objects are pushed to InvenTree on
|
|
30
|
+
every change (create/update via a background job, delete via remote
|
|
31
|
+
deactivation — InvenTree objects are never deleted), and a recurring
|
|
32
|
+
reconcile pass re-verifies every link against the live InvenTree state and
|
|
33
|
+
repairs drift. Mappings pair NetBox fields with InvenTree endpoint fields
|
|
34
|
+
through a type-compatibility matrix, with pointer→related pairs resolving
|
|
35
|
+
depth-first through pairing mappings (e.g. Inventory Item Role → Part
|
|
36
|
+
Category before the part that references it).
|
|
37
|
+
|
|
38
|
+
## Development environment
|
|
39
|
+
|
|
40
|
+
One-time setup:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
./scripts/setup_netbox.sh # clone NetBox v4.7.4, build netbox/venv, migrate, seed admin+token, install this plugin
|
|
44
|
+
./scripts/setup_inventree.sh # InvenTree 1.7 stack (docker compose), seed admin+token
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Daily start (idempotent — re-running restarts the detached processes and
|
|
48
|
+
picks up code changes):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
./scripts/start.sh # docker compose up -d + NetBox runserver + rqworker (supervised, logs under /tmp/opencode/netbox-inventree-sync/)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
After that:
|
|
55
|
+
|
|
56
|
+
- NetBox at <http://localhost:8000> (admin / admin); its API token is
|
|
57
|
+
written to `.env` as `NETBOX_TOKEN`.
|
|
58
|
+
- InvenTree at <http://localhost:1337>; its API token is written to `.env`
|
|
59
|
+
as `INVENTREE_TOKEN`.
|
|
60
|
+
- The RQ worker must be running for signal-driven syncs and reconciles
|
|
61
|
+
(`start.sh` starts it; check `/api/core/background-workers/`).
|
|
62
|
+
|
|
63
|
+
Requires: Python 3.12 (a uv-managed install works — see
|
|
64
|
+
`scripts/setup_netbox.sh`), Docker with compose v2.
|
|
65
|
+
|
|
66
|
+
## Installing into a running production environment
|
|
67
|
+
|
|
68
|
+
The development setup above builds a throwaway stack. This section is for
|
|
69
|
+
adding the plugin to NetBox and InvenTree installations that already run
|
|
70
|
+
with real data.
|
|
71
|
+
|
|
72
|
+
**What the plugin touches — and what it never touches:**
|
|
73
|
+
|
|
74
|
+
- In NetBox, the plugin creates **only its own tables**
|
|
75
|
+
(`netbox_inventree_sync_*`: connections, mappings, field pairs, the
|
|
76
|
+
link table, the sync log). It never modifies, migrates or deletes any
|
|
77
|
+
existing NetBox table, and it never writes to NetBox objects — the
|
|
78
|
+
engine only *reads* mapped objects.
|
|
79
|
+
- InvenTree is contacted **exclusively through its REST API** with a
|
|
80
|
+
token you configure. The plugin never connects to InvenTree's database.
|
|
81
|
+
- The InvenTree side is never deleted from: removing a NetBox object
|
|
82
|
+
*deactivates* its remote counterpart. Existing InvenTree parts are
|
|
83
|
+
adopted through match keys only when the match is unambiguous
|
|
84
|
+
(`name`→`name` by default); anything ambiguous or blank is flagged for
|
|
85
|
+
manual review and left untouched.
|
|
86
|
+
|
|
87
|
+
Consequently, installing — or removing — the plugin cannot lose data in
|
|
88
|
+
either system. Still, take a standard backup before any production
|
|
89
|
+
change (`pg_dump` of the NetBox database at minimum).
|
|
90
|
+
|
|
91
|
+
Steps:
|
|
92
|
+
|
|
93
|
+
1. **Backup** — `pg_dump` the NetBox database (and InvenTree's, if you
|
|
94
|
+
want the extra comfort).
|
|
95
|
+
2. **Create an InvenTree API token** for a dedicated service user with
|
|
96
|
+
write access limited to what you plan to sync (parts, companies,
|
|
97
|
+
categories). The token is stored in NetBox's database (plaintext at
|
|
98
|
+
rest, write-only over the API — treat your DB backups accordingly).
|
|
99
|
+
3. **Install the package into NetBox's Python environment** (the same
|
|
100
|
+
virtualenv/containers that run NetBox):
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install netbox-inventree-sync
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
To track the project's development instead of a PyPI release, install
|
|
107
|
+
from the repository directly:
|
|
108
|
+
`pip install git+https://github.com/edurbs/netbox-inventree-sync.git`.
|
|
109
|
+
|
|
110
|
+
For containerized NetBox (e.g. netbox-docker), install the package
|
|
111
|
+
inside the image build (a custom `Dockerfile` extending the official
|
|
112
|
+
image with the `pip install` above) so it survives recreation.
|
|
113
|
+
4. **Enable the plugin** in `configuration.py` (no plugin settings are
|
|
114
|
+
required):
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
PLUGINS = ['netbox_inventree_sync']
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
5. **Migrate and collect static files** — this only creates the plugin's
|
|
121
|
+
own tables:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python manage.py migrate
|
|
125
|
+
python manage.py collectstatic --no-input
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
6. **Restart every NetBox process — including the RQ worker(s)**. The
|
|
129
|
+
web processes serve the plugin's UI; the workers execute the sync
|
|
130
|
+
jobs. A worker that was not restarted will not know the plugin
|
|
131
|
+
exists.
|
|
132
|
+
7. **Roll out gradually**: create the connection, run
|
|
133
|
+
`seed_starter_mappings --connection <name> --validate` (everything is
|
|
134
|
+
created *disabled*), review the pairs in the builder, then enable one
|
|
135
|
+
mapping and watch *Synced Objects* / *Sync Log*. Be aware of the
|
|
136
|
+
first-reconcile note below: enabling a mapping publishes every
|
|
137
|
+
mapped-but-unlinked NetBox object, so on a large catalog the first
|
|
138
|
+
pass is long.
|
|
139
|
+
|
|
140
|
+
**Rollback / removal:** disable all mappings (this cancels their
|
|
141
|
+
recurring schedules and stops event-driven pushes), remove the plugin
|
|
142
|
+
from `PLUGINS`, restart the processes. Optionally drop the plugin's
|
|
143
|
+
tables or simply delete its rows — no NetBox inventory data is involved.
|
|
144
|
+
InvenTree keeps whatever was synced (deactivated items stay
|
|
145
|
+
deactivated); simply delete or reactivate them there if you retire the
|
|
146
|
+
integration.
|
|
147
|
+
|
|
148
|
+
## Usage walkthrough
|
|
149
|
+
|
|
150
|
+
1. **Create a connection** — *Plugins → InvenTree Sync → InvenTree
|
|
151
|
+
Instances → Add*: base URL (e.g. `http://localhost:1337`) and an API
|
|
152
|
+
token of a InvenTree user with write access. The token is stored
|
|
153
|
+
server-side and never returned by the API. The edit form offers a
|
|
154
|
+
*Test connection* probe (the token field stays blank on edit —
|
|
155
|
+
leaving it blank keeps the stored token):
|
|
156
|
+
|
|
157
|
+

|
|
158
|
+
|
|
159
|
+
2. **Seed the starter mappings** (or add mappings by hand):
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
netbox/venv/bin/python netbox/netbox/manage.py seed_starter_mappings --connection <id-or-name> --validate
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
This creates three *disabled* mappings: Inventory Item → `/api/part/`
|
|
166
|
+
(name→name, part_id→IPN, description→description,
|
|
167
|
+
`manufacturer.name`→keywords), Manufacturer → `/api/company/`, and
|
|
168
|
+
Inventory Item Role → `/api/part/category/`, each with name→name as
|
|
169
|
+
its match (adopt) key. `--validate` checks every pair against the
|
|
170
|
+
live InvenTree field metadata. After seeding, the Mappings list shows
|
|
171
|
+
all three (✔/✗ in the *Enabled* column):
|
|
172
|
+
|
|
173
|
+

|
|
174
|
+
|
|
175
|
+
3. **Enable a mapping** — edit it in the UI (or `PATCH .../mappings/<id>/`
|
|
176
|
+
`{"enabled": true}`). Enabling schedules a recurring reconcile job at
|
|
177
|
+
the mapping's `reconcile_interval_minutes` (default 60); the interval
|
|
178
|
+
schedule is replaced whenever the mapping is saved with a new interval
|
|
179
|
+
and removed when the mapping is disabled:
|
|
180
|
+
|
|
181
|
+

|
|
182
|
+
|
|
183
|
+
4. **Open the mapping builder** — the *Field pairs* tab of a mapping
|
|
184
|
+
renders both field lists (NetBox content-type fields incl. custom
|
|
185
|
+
fields and tags; the InvenTree endpoint's writable fields) with
|
|
186
|
+
incompatible pairings greyed out. Saving validates server-side
|
|
187
|
+
through the same compatibility gate as the REST API:
|
|
188
|
+
|
|
189
|
+

|
|
190
|
+
|
|
191
|
+
5. **Sync now** — the *Sync now* button on a mapping (or
|
|
192
|
+
`POST /api/plugins/netbox_inventree_sync/mappings/<id>/sync/`)
|
|
193
|
+
enqueues a one-shot reconcile in addition to the recurring schedule.
|
|
194
|
+
This works on *disabled* mappings too (deliberate): the action runs
|
|
195
|
+
regardless of the `enabled` flag, so a mapping can be exercised once
|
|
196
|
+
before its schedule and event-driven pushes are turned on:
|
|
197
|
+
|
|
198
|
+

|
|
199
|
+
|
|
200
|
+
From then on, saving a mapped NetBox object enqueues a push, and deleting
|
|
201
|
+
one deactivates (never deletes) the remote counterpart. Status per object:
|
|
202
|
+
*Plugins → InvenTree Sync → Synced Objects*; every push attempt is logged
|
|
203
|
+
in *Sync Log*.
|
|
204
|
+
|
|
205
|
+
## Seeing it in action
|
|
206
|
+
|
|
207
|
+
The screenshots below show how the application works, taken from the
|
|
208
|
+
live dev stack. First, the mapping's **field-pair builder**: NetBox
|
|
209
|
+
fields on the left, the InvenTree endpoint's fields on the right, linked
|
|
210
|
+
pairs with type badges — and options whose types don't accept the
|
|
211
|
+
selected NetBox field are greyed out (the helper line under the selects
|
|
212
|
+
says so; the server enforces it on save). This example is the starter
|
|
213
|
+
Inventory Item → Part mapping (name→name, part_id→IPN,
|
|
214
|
+
description→description, and the manufacturer pointer rendered as text
|
|
215
|
+
into keywords):
|
|
216
|
+
|
|
217
|
+

|
|
218
|
+
|
|
219
|
+
Next, one complete round trip of a sync under the Inventory Item → Part
|
|
220
|
+
mapping (starter field pairs: name→name, part_id→IPN,
|
|
221
|
+
description→description, `manufacturer.name`→keywords).
|
|
222
|
+
|
|
223
|
+
**1. The modification is made in the NetBox UI** — editing the
|
|
224
|
+
`Demo SFP+ 10G` inventory item and changing its *Description* to
|
|
225
|
+
"Transceptor SFP+ 10G, 850nm — alcance 300m em OM3 (revisado no NetBox)":
|
|
226
|
+
|
|
227
|
+

|
|
228
|
+
|
|
229
|
+
**2. Saving enqueues the push** — the object's detail page now shows the
|
|
230
|
+
*InvenTree* panel (added by the plugin) with the link's status: the part
|
|
231
|
+
was pushed and is *Synced*, with the InvenTree object (`Part #83`) one
|
|
232
|
+
click away:
|
|
233
|
+
|
|
234
|
+

|
|
235
|
+
|
|
236
|
+
**3. The change is reflected in the InvenTree UI** — opening that part in
|
|
237
|
+
InvenTree shows the description exactly as saved in NetBox (and the other
|
|
238
|
+
mapped fields: IPN, name, and keywords carrying the manufacturer name).
|
|
239
|
+
No one touched InvenTree — the background job pushed the edit within
|
|
240
|
+
seconds of the save:
|
|
241
|
+
|
|
242
|
+

|
|
243
|
+
|
|
244
|
+
The same happens on create (a new mapped object publishes a new InvenTree
|
|
245
|
+
part) and on delete (the InvenTree object is deactivated, never deleted);
|
|
246
|
+
manual edits made directly in InvenTree are overwritten by the next push
|
|
247
|
+
or reconcile, since NetBox is the source of truth.
|
|
248
|
+
|
|
249
|
+
Every link is visible under *Plugins → InvenTree Sync → Synced Objects*,
|
|
250
|
+
with per-object status, the InvenTree counterpart one click away, and the
|
|
251
|
+
last error (if any) — here showing green *Synced* rows alongside orange
|
|
252
|
+
*Orphaned* ones (links whose NetBox object was deleted):
|
|
253
|
+
|
|
254
|
+

|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
> **Sync log retention**: `SyncLogEntry` grows unboundedly — one row per
|
|
258
|
+
> push attempt, reconcile included. There is no automatic retention in
|
|
259
|
+
> v1; prune old rows manually if the table grows too large
|
|
260
|
+
> (e.g. `SyncLogEntry.objects.filter(created__lt=...)` in a shell).
|
|
261
|
+
|
|
262
|
+
### Deterministic sync from the CLI
|
|
263
|
+
|
|
264
|
+
The `inventree_sync` management command runs the production job classes
|
|
265
|
+
inline (no worker round-trip) — the E2E determinism tool:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
netbox/venv/bin/python netbox/netbox/manage.py inventree_sync --mapping '<name>' # every pending/failed link
|
|
269
|
+
netbox/venv/bin/python netbox/netbox/manage.py inventree_sync --mapping '<name>' --object dcim.inventoryitem:42
|
|
270
|
+
netbox/venv/bin/python netbox/netbox/manage.py inventree_sync --mapping '<name>' --reconcile
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Exit code 1 when any processed object ended `failed`.
|
|
274
|
+
|
|
275
|
+
### Reconcile intervals
|
|
276
|
+
|
|
277
|
+
Each enabled mapping carries `reconcile_interval_minutes` (default 60).
|
|
278
|
+
The recurring `ReconcileJob` authoritatively re-verifies every synced
|
|
279
|
+
link (drift in InvenTree is overwritten, vanished remotes re-created),
|
|
280
|
+
retries failed links, publishes unlinked NetBox objects through the
|
|
281
|
+
mapping's match/adopt policy, and orphans links whose NetBox object is
|
|
282
|
+
gone. Keep the interval short for busy catalogs (each pass GETs every
|
|
283
|
+
linked remote object).
|
|
284
|
+
|
|
285
|
+
Note on scale: the **first** reconcile after a mapping starts working
|
|
286
|
+
publishes every mapped NetBox object that has no link yet — on a large
|
|
287
|
+
catalog that is one InvenTree create per object, so expect a long first
|
|
288
|
+
pass (and a correspondingly large first-pass sync log).
|
|
289
|
+
|
|
290
|
+
## Running plugin tests
|
|
291
|
+
|
|
292
|
+
Unit suite (Django test runner, own database; hermetic — InvenTree is
|
|
293
|
+
stubbed):
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
NETBOX_CONFIGURATION=netbox.configuration_test \
|
|
297
|
+
netbox/venv/bin/python netbox/netbox/manage.py test netbox_inventree_sync -v 1 --keepdb --noinput
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`setup_netbox.sh` provisions `netbox/netbox/netbox/configuration_test.py`
|
|
301
|
+
for this: it mirrors the dev config but disables `DEBUG`, so the Django
|
|
302
|
+
debug toolbar is not installed (with `DEBUG = True` every `manage.py test`
|
|
303
|
+
run fails at the `debug_toolbar.E001` system check before any test
|
|
304
|
+
executes).
|
|
305
|
+
|
|
306
|
+
E2E suite (pytest, `tests/e2e/`) against the **live** pair — prerequisites:
|
|
307
|
+
the full stack from `./scripts/start.sh` (runserver *and* rqworker) plus
|
|
308
|
+
the InvenTree containers, and `NETBOX_TOKEN` / `INVENTREE_TOKEN` in `.env`
|
|
309
|
+
(both written by the setup scripts):
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
cd netbox && ./venv/bin/python -m pytest ../tests/e2e -v
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
URLs/tokens can be overridden with `NETBOX_URL`, `NETBOX_TOKEN`,
|
|
316
|
+
`INVENTREE_URL`, `INVENTREE_TOKEN` environment variables. Every test
|
|
317
|
+
cleans up what it created in both systems (engine-created InvenTree
|
|
318
|
+
objects included) and isolates itself with unique `e2e-<run>` name
|
|
319
|
+
prefixes, so the suite can be re-run back-to-back. One scenario stops and
|
|
320
|
+
restarts the `inventree-web` compose service (~90 s); the async-path
|
|
321
|
+
scenario needs the RQ worker. Expect a full run to take 3–4 minutes
|
|
322
|
+
(each deterministic sync boots Django once).
|
|
323
|
+
|
|
324
|
+
## Troubleshooting
|
|
325
|
+
|
|
326
|
+
- **Ports**: NetBox dev server on :8000, InvenTree on :1337, NetBox
|
|
327
|
+
Postgres on :15432 and Redis on :16379 (InvenTree's own Postgres/Redis
|
|
328
|
+
are not published). `docker compose ps` shows the backing services.
|
|
329
|
+
- **Tokens**: both API tokens live in `.env` (written by the setup
|
|
330
|
+
scripts). The plugin connection stores its own copy of the InvenTree
|
|
331
|
+
token; if you regenerate the InvenTree token, update the connection.
|
|
332
|
+
- **"Jobs cannot be assigned to this object type (… Sync Mapping)"** when
|
|
333
|
+
enabling a mapping right after a code update: NetBox caches each
|
|
334
|
+
model's supported features in the `core_objecttype` table, refreshed
|
|
335
|
+
only by the `post_migrate` signal. Run
|
|
336
|
+
`netbox/venv/bin/python netbox/netbox/manage.py migrate` once (a no-op
|
|
337
|
+
migrate is enough) to refresh it.
|
|
338
|
+
- **Worker**: signal-driven syncs and reconciles need the rqworker
|
|
339
|
+
(`./scripts/start.sh`; verify via `/api/core/background-workers/`).
|
|
340
|
+
Without it, changes only sync on the next `inventree_sync` run or
|
|
341
|
+
reconcile.
|
|
342
|
+
- **A mapping's field pairs don't validate**: `POST /mappings/` and the
|
|
343
|
+
builder validate against the endpoint's live OPTIONS metadata; an
|
|
344
|
+
unreachable InvenTree fails the request (502 from the builder view)
|
|
345
|
+
rather than accepting an unvalidated mapping.
|
|
346
|
+
|
|
347
|
+
## Support
|
|
348
|
+
|
|
349
|
+
Issues: https://github.com/edurbs/netbox-inventree-sync/issues
|
|
350
|
+
|
|
351
|
+
Before opening an issue, check [COMPATIBILITY.md](COMPATIBILITY.md) for
|
|
352
|
+
the supported NetBox / InvenTree / Python version matrix.
|
|
353
|
+
|
|
354
|
+
## License
|
|
355
|
+
|
|
356
|
+
Released under the MIT license — see [LICENSE](LICENSE).
|