Skip to content

docs(netbox_ip_address): clarify state=present identity semantics + query_params escape hatch (#1030)#1572

Draft
ldrozdz93 wants to merge 1 commit into
develfrom
int-413-ip-address-identity-semantics
Draft

docs(netbox_ip_address): clarify state=present identity semantics + query_params escape hatch (#1030)#1572
ldrozdz93 wants to merge 1 commit into
develfrom
int-413-ip-address-identity-semantics

Conversation

@ldrozdz93

Copy link
Copy Markdown
Collaborator

What this does

Makes the identity rules for netbox_ip_address with state: present explicit, and documents how to update an address in place.

When you run netbox_ip_address with state: present, the module decides whether an IP "already exists" by matching on its assignment as well as its address — the lookup keys are address, vrf, device, interface and assigned_object. So if you give the same address but a different assignment (for example moving an IP from one interface or VM to another), the module does not find the existing record and creates a second, duplicate IP instead of updating the one you meant.

This is intentional — NetBox itself permits duplicate addresses (anycast, VRRP), so "the address already exists" can't be treated as automatically meaning "update it". But the behaviour was undocumented, which is why it keeps surprising people (issue #1030 has a long thread of users expecting an update and getting a duplicate).

This change keeps the existing default behaviour unchanged and instead documents it clearly, and documents the supported way to get update-in-place.

Changes

  • Module docs (netbox_ip_address) — the state description now spells out that an existing IP is matched on its assignment as well as its address, why that is (duplicate addresses are legitimate in NetBox), and that you can narrow the match to the address alone with query_params: ['address'] to update an existing address regardless of its assignment.
  • New example — a task showing the query_params: ['address'] update-in-place pattern.
  • Integration tests (v4.4 and v4.5) — create an address on one interface, then with query_params: ['address'] update it onto a different interface (same record id, no duplicate), and prove a clean re-run reports no change.

Identity semantics — decision

Three options were on the table for resolving #1030: (a) keep today's default and document it plus the query_params escape hatch; (b) change the default so identity is address-only; (c) add a new option/state for update-in-place. This PR takes (a).

Option (b) is deliberately avoided because it would be a breaking change: playbooks that today rely on assignment-aware identity to create intentional duplicates (anycast/VRRP) would silently start updating an existing record instead. Documenting the existing, already-supported query_params: ['address'] hatch resolves the confusion without changing behaviour for anyone.

Out of scope

The thread on #1030 also mentions primary_ip4 / oob_ip being silently ignored on netbox_device. That is a separate module and a separate behaviour; it is not addressed here.

Testing

  • Unit suite: all passing.
  • black, yamllint, ansible-lint (production profile), antsibull-changelog lint, and validate-modules sanity on the module: all passing.
  • Integration plays for v4.4/v4.5 were authored against the seeded netbox-deploy.py fixtures (test100 / GigabitEthernet1 / GigabitEthernet2) but were not executed here; the new tasks only ever reference a single address (198.51.100.50/24) and update it in place, so they never create a true duplicate and do not depend on NetBox's duplicate-address enforcement setting.

Fixes #1030.

…uery_params escape hatch (#1030)

By default an existing IP is matched on its assignment as well as its
address, so the same address with a different assignment creates a new
(duplicate) IP rather than updating the existing one. This is intentional
(NetBox permits duplicate addresses for anycast/VRRP), but it was
undocumented, so users repeatedly hit it as a surprise on #1030.

Document the semantics explicitly in the module docs, describe the
query_params: ['address'] escape hatch for update-in-place with a worked
example, and add integration coverage proving the update-in-place path and
a clean idempotent re-run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

This PR clarifies the netbox_ip_address module’s state: present identity/lookup semantics (especially around assignment-aware matching and intentional duplicate-address behavior in NetBox) and adds integration coverage demonstrating the documented query_params “update-in-place” pattern.

Changes:

  • Document how state: present determines whether an IP “already exists”, and how to force address-only matching via query_params.
  • Add a new module example showing the address-only query_params approach to move an IP to a different interface.
  • Add v4.4 and v4.5 integration tasks asserting update-in-place (same object id), idempotency, and cleanup.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
plugins/modules/netbox_ip_address.py Expands module docs for state: present identity semantics and adds an example using query_params.
tests/integration/targets/v4.4/tasks/netbox_ip_address.yml Adds integration tasks validating “move in place” via query_params: [address] and idempotency.
tests/integration/targets/v4.5/tasks/netbox_ip_address.yml Same as v4.4, using the v4.5 token fixture pattern.
changelogs/fragments/1030-ip-address-identity-semantics.yml Adds a minor changelog entry describing the documentation update and escape hatch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +159 to +168
- |
Identity for C(present) - an existing IP is matched on its assignment as
well as its address. The lookup keys are C(address), C(vrf), C(device),
C(interface) and C(assigned_object). The same C(address) with a
different assignment therefore does not match the existing record, and a
new (duplicate) IP is created. This is intentional, because NetBox
permits duplicate addresses (for example anycast or VRRP). To update an
existing address in place regardless of its assignment, narrow the match
to the address alone with C(query_params=['address']) on the task (see
the I(query_params) option and the examples below).
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.

[Bug]: netbox_ip_address creating duplicated IP even if state=present

2 participants