Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions changelogs/fragments/1030-ip-address-identity-semantics.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
minor_changes:
- >-
``netbox_ip_address`` - documented the identity semantics of
``state: present``. 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. The module docs
now state this explicitly and describe the ``query_params: ['address']``
escape hatch (with an example) for updating an address in place regardless
of its assignment
(https://github.com/netbox-community/ansible_modules/issues/1030).
26 changes: 26 additions & 0 deletions plugins/modules/netbox_ip_address.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,16 @@
C(present) will check if the IP is already created, and return it if
true. C(new) will force to create it anyway (useful for anycasts, for
example).
- |
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).
Comment on lines +159 to +168
choices: [ absent, new, present ]
default: present
type: str
Expand Down Expand Up @@ -264,6 +274,22 @@
name: GigabitEthernet1
device: test100
state: new

- name: Update an existing address in place, moving it to another interface
netbox.netbox.netbox_ip_address:
netbox_url: http://netbox.local
netbox_token: thisIsMyToken
# By default the assignment is part of the IP's identity, so changing it
# would create a duplicate. Matching on the address alone updates the
# existing record instead.
query_params:
- address
data:
address: 192.168.1.10
assigned_object:
name: GigabitEthernet2
device: test100
state: present
"""

RETURN = r"""
Expand Down
91 changes: 91 additions & 0 deletions tests/integration/targets/v4.4/tasks/netbox_ip_address.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,94 @@
- test_eighteen['ip_address']['family'] == 4
- test_eighteen['ip_address']['assigned_object_type'] == "dcim.interface"
- test_eighteen['ip_address']['assigned_object_id'] == 4

##
## INT-413 / #1030 - state=present identity semantics and the query_params escape hatch
##
- name: "19 - Create 198.51.100.50/24 on GigabitEthernet1 - test100 - State: present"
netbox.netbox.netbox_ip_address:
netbox_url: http://localhost:32768
netbox_token: "0123456789abcdef0123456789abcdef01234567"
data:
address: 198.51.100.50/24
assigned_object:
name: GigabitEthernet1
device: test100
state: present
register: test_nineteen

- name: 19 - ASSERT
ansible.builtin.assert:
that:
- test_nineteen is changed
- test_nineteen['diff']['before']['state'] == "absent"
- test_nineteen['diff']['after']['state'] == "present"
- test_nineteen['msg'] == "ip_address 198.51.100.50/24 created"
- test_nineteen['ip_address']['address'] == "198.51.100.50/24"
- test_nineteen['ip_address']['assigned_object_type'] == "dcim.interface"

- name: "20 - Update 198.51.100.50/24 in place to GigabitEthernet2 via query_params address - State: present"
netbox.netbox.netbox_ip_address:
netbox_url: http://localhost:32768
netbox_token: "0123456789abcdef0123456789abcdef01234567"
query_params:
- address
data:
address: 198.51.100.50/24
description: Moved via address identity
assigned_object:
name: GigabitEthernet2
device: test100
state: present
register: test_twenty

- name: 20 - ASSERT
ansible.builtin.assert:
that:
- test_twenty is changed
- test_twenty['msg'] == "ip_address 198.51.100.50/24 updated"
- test_twenty['ip_address']['address'] == "198.51.100.50/24"
- test_twenty['ip_address']['id'] == test_nineteen['ip_address']['id']
- test_twenty['ip_address']['description'] == "Moved via address identity"
- test_twenty['ip_address']['assigned_object_type'] == "dcim.interface"
- test_twenty['ip_address']['assigned_object_id'] != test_nineteen['ip_address']['assigned_object_id']

- name: "21 - Re-run identical update with query_params address is idempotent - State: present"
netbox.netbox.netbox_ip_address:
netbox_url: http://localhost:32768
netbox_token: "0123456789abcdef0123456789abcdef01234567"
query_params:
- address
data:
address: 198.51.100.50/24
description: Moved via address identity
assigned_object:
name: GigabitEthernet2
device: test100
state: present
register: test_twentyone

- name: 21 - ASSERT
ansible.builtin.assert:
that:
- not test_twentyone['changed']
- test_twentyone['msg'] == "ip_address 198.51.100.50/24 already exists"
- test_twentyone['ip_address']['id'] == test_nineteen['ip_address']['id']
- test_twentyone['ip_address']['assigned_object_id'] == test_twenty['ip_address']['assigned_object_id']

- name: "22 - Delete 198.51.100.50/24 - State: absent"
netbox.netbox.netbox_ip_address:
netbox_url: http://localhost:32768
netbox_token: "0123456789abcdef0123456789abcdef01234567"
data:
address: 198.51.100.50/24
state: absent
register: test_twentytwo

- name: 22 - ASSERT
ansible.builtin.assert:
that:
- test_twentytwo is changed
- test_twentytwo['diff']['before']['state'] == "present"
- test_twentytwo['diff']['after']['state'] == "absent"
- test_twentytwo['msg'] == "ip_address 198.51.100.50/24 deleted"
91 changes: 91 additions & 0 deletions tests/integration/targets/v4.5/tasks/netbox_ip_address.yml
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,94 @@
- test_eighteen['ip_address']['family'] == 4
- test_eighteen['ip_address']['assigned_object_type'] == "dcim.interface"
- test_eighteen['ip_address']['assigned_object_id'] == 4

##
## INT-413 / #1030 - state=present identity semantics and the query_params escape hatch
##
- name: "19 - Create 198.51.100.50/24 on GigabitEthernet1 - test100 - State: present"
netbox.netbox.netbox_ip_address:
netbox_url: http://localhost:32768
netbox_token: "{{ lookup('file', '/tmp/.netbox_test_token', errors='ignore') | default(lookup('env', 'NETBOX_TOKEN', default='0123456789abcdef0123456789abcdef01234567'), true) }}"
data:
address: 198.51.100.50/24
assigned_object:
name: GigabitEthernet1
device: test100
state: present
register: test_nineteen

- name: 19 - ASSERT
ansible.builtin.assert:
that:
- test_nineteen is changed
- test_nineteen['diff']['before']['state'] == "absent"
- test_nineteen['diff']['after']['state'] == "present"
- test_nineteen['msg'] == "ip_address 198.51.100.50/24 created"
- test_nineteen['ip_address']['address'] == "198.51.100.50/24"
- test_nineteen['ip_address']['assigned_object_type'] == "dcim.interface"

- name: "20 - Update 198.51.100.50/24 in place to GigabitEthernet2 via query_params address - State: present"
netbox.netbox.netbox_ip_address:
netbox_url: http://localhost:32768
netbox_token: "{{ lookup('file', '/tmp/.netbox_test_token', errors='ignore') | default(lookup('env', 'NETBOX_TOKEN', default='0123456789abcdef0123456789abcdef01234567'), true) }}"
query_params:
- address
data:
address: 198.51.100.50/24
description: Moved via address identity
assigned_object:
name: GigabitEthernet2
device: test100
state: present
register: test_twenty

- name: 20 - ASSERT
ansible.builtin.assert:
that:
- test_twenty is changed
- test_twenty['msg'] == "ip_address 198.51.100.50/24 updated"
- test_twenty['ip_address']['address'] == "198.51.100.50/24"
- test_twenty['ip_address']['id'] == test_nineteen['ip_address']['id']
- test_twenty['ip_address']['description'] == "Moved via address identity"
- test_twenty['ip_address']['assigned_object_type'] == "dcim.interface"
- test_twenty['ip_address']['assigned_object_id'] != test_nineteen['ip_address']['assigned_object_id']

- name: "21 - Re-run identical update with query_params address is idempotent - State: present"
netbox.netbox.netbox_ip_address:
netbox_url: http://localhost:32768
netbox_token: "{{ lookup('file', '/tmp/.netbox_test_token', errors='ignore') | default(lookup('env', 'NETBOX_TOKEN', default='0123456789abcdef0123456789abcdef01234567'), true) }}"
query_params:
- address
data:
address: 198.51.100.50/24
description: Moved via address identity
assigned_object:
name: GigabitEthernet2
device: test100
state: present
register: test_twentyone

- name: 21 - ASSERT
ansible.builtin.assert:
that:
- not test_twentyone['changed']
- test_twentyone['msg'] == "ip_address 198.51.100.50/24 already exists"
- test_twentyone['ip_address']['id'] == test_nineteen['ip_address']['id']
- test_twentyone['ip_address']['assigned_object_id'] == test_twenty['ip_address']['assigned_object_id']

- name: "22 - Delete 198.51.100.50/24 - State: absent"
netbox.netbox.netbox_ip_address:
netbox_url: http://localhost:32768
netbox_token: "{{ lookup('file', '/tmp/.netbox_test_token', errors='ignore') | default(lookup('env', 'NETBOX_TOKEN', default='0123456789abcdef0123456789abcdef01234567'), true) }}"
data:
address: 198.51.100.50/24
state: absent
register: test_twentytwo

- name: 22 - ASSERT
ansible.builtin.assert:
that:
- test_twentytwo is changed
- test_twentytwo['diff']['before']['state'] == "present"
- test_twentytwo['diff']['after']['state'] == "absent"
- test_twentytwo['msg'] == "ip_address 198.51.100.50/24 deleted"
Loading