Detect-Only Workflow¶
Detect-only mode lets a collection plan inspect devices and produce a review-able report without changing NetBox. It is the recommended mode for new plans, especially in production.
Enabling¶
Set the detect_only flag on the CollectionPlan (UI checkbox, or
detect_only=true via the REST API).
When enabled:
- The collector still connects to every in-scope device.
- For each detected fact it creates a
FactsReportEntrywith one of four actions andstatus = pending. - It does not create or modify NetBox objects.
When disabled (apply mode):
- The collector creates entries the same way, then marks each one
appliedimmediately after the corresponding NetBox object is written. - The final report status is
Applied.
Action types¶
FactsReportEntry.action is one of:
| Action | Meaning |
|---|---|
new |
The fact was detected on the device and no matching object exists in NetBox yet. |
changed |
A matching object exists in NetBox but its values differ from what the device reports. |
confirmed |
The fact matches NetBox exactly. Entries are recorded for visibility; applying them is a no-op for most collectors. |
stale |
The object exists in NetBox (and carries the Automatically Discovered tag) but was not seen during this run. Applying it removes or unassigns the object. With a grace period configured, the entry is only recorded once the object has been missing for the whole period. |
Entry status¶
FactsReportEntry.status tracks the apply lifecycle:
| Status | Meaning |
|---|---|
pending |
Default. The entry has not been applied or skipped. |
applied |
The per-collector handler ran successfully. applied_at is set. |
skipped |
A reviewer chose not to apply this entry. Later runs of the same plan stop recording that change on that device until the device reports something else; see Skip memory. |
failed |
The handler raised an exception; error_message holds the truncated reason (1000 chars). The savepoint for this entry was rolled back; other entries are unaffected. |
Stale grace period¶
A stale entry is a proposal to remove something, and a run can be wrong
about an absence: a device briefly unreachable mid-run, a transceiver
reseated between two passes. With a grace period configured -- the
stale_grace_period_days plugin setting, or a plan's own
stale_grace_days -- an absence has to persist before it reaches the
review queue at all:
- The first run that cannot find the object tags it Orphaned
(netbox-facts) and records when it went missing. No
staleentry is created, so nothing appears in the queue; the run's summary line reports how many objects it is holding (N objects in grace). - Later runs that still cannot find it confirm the absence. The clock keeps running from the first miss, so a long-gone object is not reprieved by being missed again.
- Once it has been missing for the whole period, the
staleentry is recorded as it always was, and review proceeds normally. - An object the run finds again loses the tag and its record, and nothing is ever proposed.
Applying the entry settles the absence: the tag comes off and the record is dropped. Skipping it does not -- the object stays marked and keeps its record, because a skip is a decision about the entry, not a verdict that the object is still there. Un-skipping needs nothing special either; the entry simply returns to the queue.
With the default grace period of 0 none of this happens: the first
absence records the stale entry, exactly as before.
Reviewing a report¶
You do not have to watch the report list: a finished run raises the
netbox_facts.report_ready event ("Facts report ready for review"), so an
event rule can push the summary counts to a webhook, script, or
notification group the moment a report is ready. See
Notifications.
Open Operational Facts -> Facts Reports and pick a report. The page shows:
- A summary card with counts by action (cached on
FactsReport.summary). - An entry-status card whose Pending, Applied, Skipped and Failed counts link straight to the matching entry tab.
- One tab per entry status, each carrying a count badge. The four tabs are always present, so a tab does not vanish from under the cursor as entries move from pending to applied mid-review.
- A filter form on every tab for
device,action,status,collector_typeandentry_kind, plus a free-text search matching the entry label or the device name. The quick-search box above the table runs the same search. - An Export button on every tab, writing the filtered entries of that tab out the same way the object lists do: the configured columns, all columns, or an export template. See Reviewing entries in the UI.
- For each entry:
object_repr,detected_values,current_values, and any error message.
object_repr is a human-readable label such as
MACAddress aa:bb:cc:11:22:33, InventoryItem PIC 0, or
Cable ge-0/0/0 <-> switch2:ge-0/0/4. It is constructed by
NapalmCollector._object_repr().
Applying or skipping entries¶
Tick one or more pending entries and use the Apply selected or Skip selected action. Both act on the entries you ticked and run inline, in the web request.
To apply a whole report, use Apply All Pending on the report detail
page. It asks for confirmation first, showing how many entries will be
applied, then hands the work to a background job (Facts Report Apply)
that resolves the report's pending entries server-side and calls the same
apply_entries() helper. You are returned to the report with a link to
the job; a report can have only one apply job in flight at a time.
Apply runs through apply_entries() in
netbox_facts/helpers/applier.py:
- Fetch the requested pending entries scoped to the report.
- Open an outer atomic block.
- For each entry:
- Open a per-entry savepoint.
- Look up the collector-type-specific handler in
APPLY_HANDLERS. - Run the handler. On success, set
status=appliedandapplied_at=now. - On exception, mark
status=failed, save the truncated error, and release the inner savepoint so the outer transaction continues.
- After the loop, recompute and persist the report status.
The same path is exposed over the REST API:
POST /api/plugins/facts/factsreports/<id>/apply/
Content-Type: application/json
Authorization: Token <token>
{"entries": [12, 13, 19]}
Response: {"applied": 2, "failed": 1}.
Skip is similar but does not invoke handlers; it just bulk-updates
status=skipped.
Two review-loop habits are worth knowing before you work a queue:
- Rediff before applying an entry that has been waiting. The Rediff selected action (and the per-row Rediff button) re-reads what NetBox holds for each selected pending entry, without contacting the device, so you act on the current comparison rather than the one the run recorded. An entry NetBox already satisfies is marked applied instead of being applied again. See Rediff.
- A skipped entry stays skipped. The next run of the same plan does not record a change you have already declined on that device, until the device reports something different; the run summary counts the suppressions. See Skip memory. Un-skip the entry if you want the change back in the queue.
Report status transitions¶
_update_report_status() derives the report status from its entries:
- All
pending->Pending. - All
applied->Applied(completed_atis set). - All
failed->Failed(completed_atis set). - No
pendingleft, mix ofappliedand others ->Applied(completed_atis set). - No
pendingleft, noapplied->Completed(completed_atis set). - Otherwise ->
Partial(completed_atis left as it stands; a report reopened by an un-skip keeps the completion timestamp it already recorded).
Permissions¶
Applying or skipping requires the netbox_facts.apply_factsreport
permission. Grant it explicitly to the operators who should be allowed to
mutate NetBox from a report; viewing a report only requires
netbox_facts.view_factsreport.
Why a savepoint per entry¶
A handler can fail for many independent reasons (a duplicate IP, a missing ModuleType, an interface that has gained a cable since collection). The per-entry savepoint guarantees that one bad entry does not undo every successful apply that came before it in the same batch.
Working as a team¶
Detect-only mode turns review into a recurring task rather than a one-off migration step. A few practices keep that sustainable as the number of monitored devices grows:
- Start with a segment you understand. Scope the first Collection
Plans to a well-known slice of the network -- 10 to 20 devices is enough
to see real
changedandstaleentries without producing a queue bigger than a reviewer can get through. Widening scope later is just adding devices, sites, or roles to the plan; nothing about starting narrow has to be undone. - Set a review cadence. Decide whether reports get reviewed daily or weekly, and set each plan's Interval (minutes) (see Scheduling and Jobs) to match, so reports land on a schedule reviewers can plan around instead of arriving at random.
- Give the review queue an owner. Grant
netbox_facts.apply_factsreportto whoever is responsible for triage, and treat the Operational Facts -> Facts Reports list, filtered to pending entries, as an owned queue -- the same way an unassigned ticket queue would be treated.netbox_facts.view_factsreportis enough for the rest of the team to watch the queue without being able to mutate NetBox. - Treat recurring drift as a process signal, not just a chore. An
entry that comes back
changedorstaleagainst the same object on every run usually means NetBox and the device disagree about who owns that value: a manual edit that keeps getting overwritten, a decommission that was never reflected in NetBox, or a plan scoped to the wrong device. A report that never reaches zero pending entries is a prompt to fix that underlying disagreement, not a report to keep skipping.