Skip to content

NetBox Manufacturer & DeviceType auto-lifecycle - #446

Merged
SchoolGuy merged 7 commits into
masterfrom
feature/netbox-manufacturer-devicetype-lifecycle
Aug 22, 2026
Merged

NetBox Manufacturer & DeviceType auto-lifecycle#446
SchoolGuy merged 7 commits into
masterfrom
feature/netbox-manufacturer-devicetype-lifecycle

Conversation

@SchoolGuy

Copy link
Copy Markdown
Collaborator

To enable automatic creation and deletion of Manufacturers and Device Types, Orthos 2 is now capable of creating them during device import and on a "Fetch NetBox" task. Furthermore, they get automatically cleaned up after the last machine or enclosure stops referencing them.

Lastly, this PR contains a bugfix for the TaskManager to clear stale tasks both automatically on startup and manually with a "Danger"-colored button.

Manufacturer.fetch_netbox() only ever updates a row that already exists
with a netbox_id set - there's no code path that discovers a NetBox
manufacturer Orthos2 doesn't know about yet and creates it.

Resolves a NetBox manufacturer ID to a Manufacturer, creating it on
first sight. Looks up by netbox_id first, falling back to name (unique)
and backfilling netbox_id onto a match instead of creating a duplicate -
required because migration 0044_initial_required_data.py seeds 28
Manufacturer rows (including "AMD") unlinked from NetBox.

Manufacturer is the "higher" model in the Manufacturer/DeviceType
hierarchy (one Manufacturer has many DeviceTypes), so it gets its own
resolver that DeviceType's equivalent method will delegate to.
Machine.fetch_netbox()/compare_netbox() and the "Add via NetBox ID"
flow never populate device_type, even though NetBox's device payload
includes a full nested device_type object - there was no code path
that discovers a NetBox DeviceType Orthos2 doesn't know about yet and
creates it.

Resolves a NetBox device-type ID to a DeviceType, delegating manufacturer
resolution to Manufacturer.get_or_create_from_netbox(). Looks up by
netbox_id first (short-circuiting before any NetBox call), falling back
to (name, manufacturer) and backfilling netbox_id onto a match instead
of creating a duplicate - DeviceType has no DB-level uniqueness
constraint, but a manufacturer shouldn't end up with two rows for what
NetBox considers the same model.
AddMachineFormView.form_valid() fetched the NetBox device payload
(which includes a full nested device_type object) but discarded it,
leaving device_type unset for both newly-created machines and existing
machines matched by fqdn. Wire in DeviceType.get_or_create_from_netbox()
for both paths. Virtual machines have no device_type in NetBox, so this
only applies to the "device" object type.
fetch_netbox() synced comment/serial_number/product_code and network
interfaces/BMC but never touched device_type, even though the fetched
device payload includes a full nested device_type object. Re-resolve
via DeviceType.get_or_create_from_netbox() whenever it's unset or the
linked DeviceType's netbox_id no longer matches what NetBox reports -
covers both first-sync and NetBox-side device-type changes. Virtual
machines have no device_type in NetBox, so this is a no-op for them.
Delete Manufacturer/DeviceType rows no longer referenced by any Machine
or Enclosure. DeviceType is cleaned up first since DeviceType.manufacturer
is on_delete=CASCADE - a Manufacturer only becomes orphaned once all its
DeviceTypes are gone, and both cleanups run in the same execute() so a
Manufacturer orphaned by this same pass is deleted immediately rather
than waiting for the next run.

Registered as a DailyTask (daily.py, tasks/__init__.py, registry.py,
dailytasks.json fixture) alongside DailyNetboxFetch, following the same
pattern.

Note: migration 0044_initial_required_data.py seeds 28 Manufacturer
rows without any DeviceType attached - on a deployment that hasn't
created DeviceTypes for them yet, enabling this task will delete all of
them the first time it runs, as requested.
If the taskmanager process is killed/restarted mid-execution, the
in-memory running_threads dict that would normally clear a task's
running flag (via _check_threads()) is lost, leaving the DB row stuck
at running=True forever - SingleTask.get_single_tasks() only picks up
running=False rows, and DailyTask's "Execute Now" action refuses to
run a task that's (stale-)marked as already running.

Reset every DailyTask/SingleTask still marked running=True the moment
TaskExecuter.run() starts: a freshly-started process tracks no threads
yet, so anything still flagged running in the database is necessarily
stale, not actually in progress.
Complements the taskmanager's own startup auto-heal: for a task that's
genuinely wedged while the taskmanager process is still alive (rather
than crashed), a superuser previously had no way to clear a stuck
'running' flag on a SingleTask or DailyTask from the UI. Adds a
danger-styled toggle button to both detail pages' action bars.
@SchoolGuy SchoolGuy changed the title NetBox MManufacturer & DeviceType auto-lifecycle NetBox Manufacturer & DeviceType auto-lifecycle Aug 22, 2026
@codacy-production

codacy-production Bot commented Aug 22, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 26 minor

Alerts:
⚠ 26 issues (≤ 0 issues of at least minor severity)

Results:
26 new issues

Category Results
Documentation 26 minor

View in Codacy

🟢 Metrics 69 complexity · 21 duplication

Metric Results
Complexity 69
Duplication 21

View in Codacy

🟢 Coverage 99.78% diff coverage · +0.82% coverage variation

Metric Results
Coverage variation +0.82% coverage variation (-1.00%)
Diff coverage 99.78% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (5c58b70) 23634 17737 75.05%
Head commit (c905edf) 24091 (+457) 18277 (+540) 75.87% (+0.82%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#446) 460 459 99.78%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@SchoolGuy
SchoolGuy merged commit 010f8af into master Aug 22, 2026
14 of 15 checks passed
@SchoolGuy
SchoolGuy deleted the feature/netbox-manufacturer-devicetype-lifecycle branch August 22, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant