Conversation
Re-running a play that connected a non-interface termination raised an exception and could report a false `changed`. The update path rebuilt each existing termination's content type from pynetbox internals via `termination.endpoint.name`, which failed three ways: dash-form endpoint names missed the underscore-keyed lookup maps, plain-dict terminations have no `.endpoint`, and even a successful lookup produced `dcim.power_port` instead of the user-side `dcim.powerport`. `_convert_termination` is now a method that normalizes all three pynetbox shapes (plain dict, GenericListObject, bare Record) to the dotted content-type form, so cables are idempotent for console, console-server, power port/outlet/feed, front/rear port and circuit terminations - not only `dcim.interface`. The one-off `rear-ports` dash band-aid in `API_APPS_ENDPOINTS` and `ENDPOINT_NAME_MAPPING` is removed. Adds unit coverage for the reconstruction helper across all termination types and pynetbox shapes, plus second-run integration assertions for the console and circuit-termination cases. Fixes netbox-community#1274 Fixes netbox-community#1015 Fixes netbox-community#1040 Fixes netbox-community#946 Fixes netbox-community#1217 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes netbox_cable idempotency for all supported termination types by normalizing existing cable terminations into the same dotted app.model form used by user-supplied module data, preventing second-run exceptions and false “changed” results.
Changes:
- Normalize existing cable terminations across pynetbox return shapes (dict / GenericListObject-like / bare Record) via a dedicated
_convert_termination()method. - Remove the previous special-case dash-form mapping (“rear-ports”) now that normalization handles dash vs underscore endpoints generically.
- Add unit tests (broad termination-type coverage + idempotent re-run behavior) and add integration assertions for second-run idempotency cases.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
plugins/module_utils/netbox_utils.py |
Implements robust termination normalization and removes obsolete dash-form rear-port mapping. |
tests/unit/module_utils/netbox_utils/test_netbox_module.py |
Adds unit coverage for termination normalization across all termination types and pynetbox shapes, plus idempotent cable re-run checks. |
tests/integration/targets/v4.5/tasks/netbox_cable.yml |
Adds second-run idempotency assertions for console and circuit-termination cables. |
changelogs/fragments/int-412-netbox-cable-termination-idempotency.yml |
Documents the bugfix and links impacted issues. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Independent confirmation that this fixes the problem, from a production NetBox with a large cable inventory. Environment: NetBox 4.x, Before. 238 cables in the model, of which 236 are Because a play stops at its first failure, each of those two files lost every task below the cable as well. All 236 interface cables were unaffected, which matches the "interface works, everything else fails" pattern described here. After, with this branch installed unchanged at Three observations that may be useful to a reviewer:
This is still marked as a draft. Is there anything blocking it, and is there anything we can usefully test? #1015 has been open since 2023-06-06, and #1040 and #1274 describe the same thing, so there is real demand. |
Summary
Connecting a cable works, but re-running the same play raised an exception for every termination type except
dcim.interface↔dcim.interface. Users had to delete cables before re-running their playbooks. This makesnetbox_cableidempotent for all termination types — console, console-server, power port/outlet/feed, front/rear port and circuit termination — so a second run reportsokinstead of crashing or falsely reporting a change.What was wrong
On update, the module rebuilt each existing termination's content type from pynetbox internals (
termination.endpoint.name). That reconstruction failed in three independent ways:power-ports,console-ports,circuit-terminations), but the internal lookup maps are keyed in underscore form, so the lookup raised.dcim.interfacewas the only type that survived, becauseinterfacescontains no dash — matching the reported "interface works, everything else fails" pattern..endpointattribute, raisingAttributeError.dcim.power_port, which can never equal the user-supplieddcim.powerport, so the cable was reported as changed on every run.A previous one-off fix had added a single dash-form key for rear ports only, papering over one endpoint at a time.
What changed
GenericListObject), and otherwise rebuilds it from the record's endpoint, matching the dottedapp.modelform the module emits on the data side. This covers all termination types and is robust across pynetbox versions.Tests
Note: the
netbox_cableintegration target is currently disabled in the per-versionmain.yml(it predates the v4 task directories), so the integration assertions above are authored but not yet wired into a CI run; re-enabling and validating that target against current netbox-docker is best done as a follow-up. The unit suite is the executable regression guard for this fix.Fixes #1274
Fixes #1015
Fixes #1040
Fixes #946
Fixes #1217