Skip to content

Fix import with slugs - #130

Open
jgroom33 wants to merge 2 commits into
netbox-community:developfrom
jgroom33:jgroom33-patch-1
Open

jgroom33 wants to merge 2 commits into
netbox-community:developfrom
jgroom33:jgroom33-patch-1

Conversation

@jgroom33

Copy link
Copy Markdown

resolve #117

@bastianleicht bastianleicht left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it on my instance

@robinreinhardt robinreinhardt left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works on my end too

@qspark-eliezerlp

Copy link
Copy Markdown

This fix also worked for me. Anything missing in order to merge this? Thanks!

mmguero pushed a commit to mmguero-dev/Device-Type-Library-Import that referenced this pull request Sep 14, 2026
…ommunity#130)

* fix(netbox): name the item NetBox rejected in a bulk create

pynetbox sets `RequestError.error` to `response.text`, which is always a
string. The `isinstance(excep.error, list)` branch in `_create_generic`
was therefore unreachable, and every rejected bulk create fell through to
the whole-batch message. NetBox rejects the entire POST when one item is
invalid, so the log named all submitted components:

    Error '[{"rf_role":[...]},{},{},{}]' creating Interface.
    Items: ['Uplink', 'Downlink 2.5G', 'Downlink 1', 'Downlink 2']

when only 'Uplink' was at fault.

Decode the response body and map the per-item error array onto the
submitted items by position, which is how NetBox returns it. Fall back to
the whole-batch message when the payload is not a list, when its length
does not match the batch, or when no item carries a message, so a failure
is never swallowed.

`_extract_error_payload` already did this decoding for the subdevice_role
classifier; promote it to `extract_error_payload` and share it instead of
adding a second decoder.

The previous tests built the error from a MagicMock response and then
assigned `err.error = [...]` by hand, fabricating a shape pynetbox never
produces, and asserted only that something was logged. Rebuild them on a
real `requests.Response` and assert the message content.

* feat(report): name the vendor in each change-detection banner

A run loops over vendors and prints one report per vendor, so consecutive
banners were indistinguishable: three "CHANGE DETECTION REPORT" blocks in a
row gave no clue which vendor each covered.

Append the vendor to the banner title, on the title line itself:

    CHANGE DETECTION REPORT: TP-Link
    MODULE TYPE CHANGE DETECTION: TP-Link
    RACK TYPE CHANGE DETECTION: TP-Link

The rack-type banner had no title line at all, only a bare count block, so
it gains one for consistency. The vendor argument defaults to empty and the
titles then render exactly as before, which keeps every existing caller and
the integration-test banner matching intact.

* fix(report): count device-type creation failures in the run report

Only the update path recorded an outcome. A device type rejected at creation
was logged inline and then vanished: it was missing from the FAILED / PARTIAL
UPDATE REPORT and absent from the device_types_failed count, so a run could
report fewer failures than it actually had.

Record a FAILED outcome for both creation failure paths, the API rejection
and the exhausted connection retry, and carry the source file as the hint so
the operator can find the definition.

A parsed YAML dict carries only the manufacturer slug, so the identity reads
"ribbon/SBC 5400" where the update path can use the NetBox manufacturer name.

* fix(report): record rack-type and module-type failures in the run report

Extends the previous commit to the remaining unreported failure paths. Rack
types recorded no outcome at all, so a rejected rack type was logged inline
and then missing from the summary:

    Error creating Rack Type: {"description":["Ensure this field has no more
    than 200 characters."]} (Context: rack-types/Lenovo/1410HPB.yaml)

Record a FAILED outcome for the rack-type create and update paths and for the
module-type create path, both the API rejection and the exhausted connection
retry, carrying the source file as the hint.

Generalise the device-type-only helper into _record_failure(kind, identity,
reason, src_file) with an optional counter key, plus _yaml_identity for the
parsed dicts that carry only a manufacturer slug.

The module-type update path is left alone: _process_single_module_type
already records an outcome when _try_update_module_type returns False, and
recording there too produced two rows and a double count for one failure.

* fix(report): carry the component error into the device-type reason

Rack and module failures report NetBox's own message, but a device type whose
component changes failed reported only the generic label:

    ✗ [device_type] TP-Link/EAP670
        reason: Component updates failed.

The real message was logged inline during the run and then discarded, so the
summary could not say what went wrong.

Component failures now go through _log_component_error, which logs as before
and keeps the message. create_device_types drains the buffer around the
component work and passes it to the outcome, which prefers it over the generic
label and appends it to a partial-update reason. Up to three messages are
joined, the rest counted:

    ✗ [device_type] TP-Link/EAP670
        reason: Failed to create Interface 'LAN': {'rf_role': ['Wireless role
        may be set only on wireless interfaces.']} (Context: EAP670.yaml)

A recognised constraint from the failure resolver still wins, since it already
carries a description, blocking objects and a hint.

* fix(report): route component transport failures into the outcome reason

The RequestError branches for a component update and removal already went
through _log_component_error, but their sibling retry-exhausted branches
still called handle.log directly. A connection failure was logged and then
lost, so the device-type outcome fell back to "Component updates failed."
instead of naming the transport error.

The create path already routed its retryable branch; this makes the update
and removal paths match.

* fix(report): route skipped-component reasons into the outcome reason

Completes the class the previous commit started. A component change can be
dropped during link resolution before any API call, and those reasons were
logged and then lost, so the device-type outcome fell back to the generic
"Component updates failed."

The clearest case is a power outlet whose power_port names a port that does
not exist. The outlet is removed from the batch and the run reports only the
generic label, when it could name the reference:

    reason: Could not find Power Port "hardwired" for Power Outlet "Hardwire".
            Available: ['Input'] (Context: Online-3000.yaml)

Routed through _log_component_error: unresolvable power-port and rear-port
references, all three front-port mapping resolution failures, and the three
bridge failures.

Left on handle.log deliberately: the "Removed N" success line, the two
"Skipped N ..." summaries whose per-item detail is already routed, the
NetBox < 4.5 multiple-mapping notice where the port is still created, and
the image upload errors, which belong to a different entity.

* refactor(report): derive run-summary failure counts from the outcome registry

The summary counts came from the legacy Counter while the itemised report came
from the OutcomeRegistry, with nothing keeping the two consistent. They had
drifted in three directions:

* a module type that failed to be created recorded an outcome but bumped no
  counter, so it was listed in the report and missing from the counts;
* a partially updated module bumped a counter but recorded no outcome, so it
  was counted in the headline and never itemised;
* a failed rack type had no counter and no summary line at all.

The registry already holds one row per entity with its kind and outcome, so the
counts are derivable from it. RunSummary now carries outcome_counts alongside
failure_lines, both taken from the registry at the same instant, and the summary
derives every failure and partial line from that aggregate. The headline can no
longer disagree with the rows beneath it.

Record the two missing module PARTIAL outcomes, then delete the parallel
device_types_failed, module_update_failed and module_partial_update keys and the
now-unused counter_key argument on _record_failure. Rack types gain their
missing line from rows that already existed.

The created/updated and component-volume keys stay on the Counter: they are
volume tallies rather than per-entity outcomes, and the registry does not hold
them.

Tests assert the invariant the two tallies never enforced: the headline counts
match the itemised rows.

* fix(report): scope component errors per entity and count partial device types

Two review findings, both from the same seam.

The summary read only Outcome.FAILED for device types, so a device type that
ended PARTIAL was itemised in the report with no matching headline count. The
previous commit added that line for modules and missed it for device types.
Add it, and assert the invariant for partials as well as failures.

DeviceTypes._component_errors was drained by one of the four paths that
produce component errors. New device types and new module types never
consumed it, so their component failures reached no outcome, and module
reasons carried none of the buffered NetBox detail. The buffer's interface
carried an unwritten "clear before, read after" contract that only one caller
honoured.

Replace take_errors() with the collect_component_errors() context manager, so
clearing on entry and collecting on exit are structural rather than
remembered, and one entity's failures cannot surface in another's reason.
Scope all four paths through it. A new device type or module type whose
components partly fail now records Outcome.PARTIAL instead of reporting as
cleanly created, and module reasons carry the same NetBox detail device-type
reasons already did.

* fix(report): stop calling create failures update failures

The headline failure lines predate this branch, when Outcome.FAILED could
only come from an update. This branch added create-failure recording for
device types (core/netbox_api.py:1108, :1121) and module types (:1790,
:1801), so both counts now include creates while the text still read
"FAILED to update". A Ribbon device type rejected at create time was
reported as a failed update, pointing the operator at the wrong operation.

Both lines now say "failed to create or update".

The two new tests drive real NetBox rejections through
create_device_types and _process_single_module_type, then render the real
RunSummary through a real LogHandler, so the wording is asserted against
the path that actually produces the count.

Moves the recording console into tests/helpers.py: both test modules need
it now, and one copy keeps them from drifting.
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.

KeyError: 'slug' if you specify an argument in '--slugs'

4 participants