Skip to content

Add module netbox_mac_address #1371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jan 20, 2025
Merged
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
3 changes: 3 additions & 0 deletions changelogs/fragments/netbox_mac_address.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
minor_changes:
- netbox_device_interface - Add primary_mac_address option for NetBox 4.2+
- netbox_vm_interface - Add primary_mac_address option for NetBox 4.2+
1 change: 1 addition & 0 deletions meta/runtime.yml
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@ action_groups:
- netbox_l2vpn
- netbox_l2vpn_termination
- netbox_location
- netbox_mac_address
- netbox_manufacturer
- netbox_module
- netbox_module_bay
3 changes: 3 additions & 0 deletions plugins/module_utils/netbox_dcim.py
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@
NB_SITES = "sites"
NB_SITE_GROUPS = "site_groups"
NB_VIRTUAL_CHASSIS = "virtual_chassis"
NB_MAC_ADDRESSES = "mac_addresses"

try:
from packaging.version import Version
@@ -141,6 +142,8 @@ def run(self):
name = self.module.params["data"]["master"]
elif data.get("slug"):
name = data["slug"]
elif data.get("mac_address"):
name = data["mac_address"]
elif endpoint_name == "cable":
if self.module.params["data"]["termination_a"].get("name"):
termination_a_name = self.module.params["data"]["termination_a"]["name"]
7 changes: 6 additions & 1 deletion plugins/module_utils/netbox_utils.py
Original file line number Diff line number Diff line change
@@ -78,6 +78,7 @@
"sites": {},
"site_groups": {},
"virtual_chassis": {},
"mac_addresses": {},
},
extras={
"config_contexts": {},
@@ -226,6 +227,7 @@
webhook="name",
wireless_lan="ssid",
wireless_lan_group="slug",
mac_address="mac_address",
)

# Specifies keys within data that need to be converted to ID and the endpoint to be used when queried
@@ -275,6 +277,7 @@
"ipsec_profile": "ipsec_profiles",
"location": "locations",
"lag": "interfaces",
"primary_mac_address": "mac_addresses",
"manufacturer": "manufacturers",
"master": "devices",
"module": "modules",
@@ -434,6 +437,7 @@
"wireless_lans": "wireless_lan",
"wireless_lan_groups": "wireless_lan_group",
"wireless_links": "wireless_link",
"mac_addresses": "mac_address",
}

ALLOWED_QUERY_PARAMS = {
@@ -516,6 +520,7 @@
),
"lag": set(["name"]),
"location": set(["name", "slug", "site"]),
"mac_address": set(["mac_address"]),
"module": set(["device", "module_bay", "module_type"]),
"module_bay": set(["device", "name"]),
"module_type": set(["model"]),
@@ -1414,7 +1419,7 @@ def _normalize_data(self, data):

# We need to assign the correct type for the assigned object so the user doesn't have to worry about this.
# We determine it by whether or not they pass in a device or virtual_machine
if data.get("assigned_object"):
if data.get("assigned_object") and isinstance(data["assigned_object"], dict):
if data["assigned_object"].get("device"):
data["assigned_object_type"] = "dcim.interface"
if data["assigned_object"].get("virtual_machine"):
6 changes: 6 additions & 0 deletions plugins/modules/netbox_device_interface.py
Original file line number Diff line number Diff line change
@@ -86,6 +86,11 @@
- The MAC address of the interface
required: false
type: str
primary_mac_address:
description:
- The primary MAC address of the interface (NetBox 4.2 and later)
required: false
type: str
wwn:
description:
- The WWN of the interface
@@ -335,6 +340,7 @@ def main():
bridge=dict(required=False, type="raw"),
mtu=dict(required=False, type="int"),
mac_address=dict(required=False, type="str"),
primary_mac_address=dict(required=False, type="str"),
wwn=dict(required=False, type="str"),
mgmt_only=dict(required=False, type="bool"),
poe_type=dict(required=False, type="raw"),
164 changes: 164 additions & 0 deletions plugins/modules/netbox_mac_address.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2025, Martin Rødvand (@rodvand) <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function

__metaclass__ = type

DOCUMENTATION = r"""
---
module: netbox_mac_address
short_description: Create, update or delete MAC addresses within NetBox
description:
- Creates, updates or removes MAC addresses from NetBox
notes:
- Tags should be defined as a YAML list
- This should be ran with connection C(local) and hosts C(localhost)
author:
- Martin Rødvand (@rodvand)
requirements:
- pynetbox
version_added: "3.21.0"
extends_documentation_fragment:
- netbox.netbox.common
options:
data:
type: dict
description:
- Defines the MAC address configuration
suboptions:
mac_address:
description:
- The MAC address
required: true
type: str
assigned_object:
description:
- The object to assign this MAC address to
required: false
type: dict
description:
description:
- Description of the MAC address
required: false
type: str
comments:
description:
- Comments for the MAC address
required: false
type: str
tags:
description:
- Any tags that the MAC address may need to be associated with
required: false
type: list
elements: raw
custom_fields:
description:
- Must exist in NetBox and in key/value format
required: false
type: dict
required: true
"""

EXAMPLES = r"""
- name: "Test NetBox MAC address module"
connection: local
hosts: localhost
gather_facts: false

tasks:
- name: Create MAC address within NetBox with only required information
netbox.netbox.netbox_mac_address:
netbox_url: http://netbox.local
netbox_token: thisIsMyToken
data:
mac_address: "00:11:22:33:44:55"
state: present

- name: Create MAC address with interface assignment
netbox.netbox.netbox_mac_address:
netbox_url: http://netbox.local
netbox_token: thisIsMyToken
data:
mac_address: "AA:BB:CC:DD:EE:FF"
assigned_object:
device: Test Nexus One
name: Ethernet1/1
description: "MAC address for eth1/1"
tags:
- Network
state: present

- name: Delete MAC address within netbox
netbox.netbox.netbox_mac_address:
netbox_url: http://netbox.local
netbox_token: thisIsMyToken
data:
mac_address: "00:11:22:33:44:55"
state: absent
"""

RETURN = r"""
mac_address:
description: Serialized object as created or already existent within NetBox
returned: success (when I(state=present))
type: dict
msg:
description: Message indicating failure or info about what has been achieved
returned: always
type: str
"""

from ansible_collections.netbox.netbox.plugins.module_utils.netbox_utils import (
NetboxAnsibleModule,
NETBOX_ARG_SPEC,
)
from ansible_collections.netbox.netbox.plugins.module_utils.netbox_dcim import (
NetboxDcimModule,
NB_MAC_ADDRESSES,
)
from copy import deepcopy


def main():
"""
Main entry point for module execution
"""
argument_spec = deepcopy(NETBOX_ARG_SPEC)
argument_spec.update(
dict(
data=dict(
type="dict",
required=True,
options=dict(
mac_address=dict(required=True, type="str"),
assigned_object=dict(required=False, type="dict"),
description=dict(required=False, type="str"),
comments=dict(required=False, type="str"),
tags=dict(required=False, type="list", elements="raw"),
custom_fields=dict(required=False, type="dict"),
),
),
)
)

required_if = [
("state", "present", ["mac_address"]),
("state", "absent", ["mac_address"]),
]

module = NetboxAnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=required_if,
)

netbox_mac_address = NetboxDcimModule(module, NB_MAC_ADDRESSES)
netbox_mac_address.run()


if __name__ == "__main__": # pragma: no cover
main()
6 changes: 6 additions & 0 deletions plugins/modules/netbox_vm_interface.py
Original file line number Diff line number Diff line change
@@ -53,6 +53,11 @@
- The MAC address of the interface
required: false
type: str
primary_mac_address:
description:
- The primary MAC address of the interface (NetBox 4.2 and later)
required: false
type: str
description:
description:
- The description of the interface
@@ -209,6 +214,7 @@ def main():
enabled=dict(required=False, type="bool"),
mtu=dict(required=False, type="int"),
mac_address=dict(required=False, type="str"),
primary_mac_address=dict(required=False, type="str"),
description=dict(required=False, type="str"),
mode=dict(required=False, type="raw"),
vm_bridge=dict(required=False, type="raw"),
18 changes: 18 additions & 0 deletions tests/integration/netbox-deploy.py
Original file line number Diff line number Diff line change
@@ -372,6 +372,24 @@ def make_netbox_calls(endpoint, payload):
]
created_interfaces = make_netbox_calls(nb.dcim.interfaces, dev_interfaces)

# Create MAC addresses
if nb_version >= version.parse("4.2"):
test100_gi1 = nb.dcim.interfaces.get(name="GigabitEthernet1", device_id=1)
test100_gi2 = nb.dcim.interfaces.get(name="GigabitEthernet2", device_id=1)
mac_addresses = [
{
"mac_address": "AA:BB:CC:DD:EE:FF",
"assigned_object_id": test100_gi1.id,
"assigned_object_type": "dcim.interface",
},
{
"mac_address": "AA:AB:CC:DD:EE:FF",
"assigned_object_id": test100_gi2.id,
"assigned_object_type": "dcim.interface",
},
]
created_mac_addresses = make_netbox_calls(nb.dcim.mac_addresses, mac_addresses)

# Wireless Interfaces
if nb_version >= version.parse("3.1"):
wlink_interfaces = [
27 changes: 18 additions & 9 deletions tests/integration/targets/v4.2/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -332,15 +332,6 @@
tags:
- netbox_circuit

- name: NETBOX_CIRCUIT_TERMINATION TESTS
ansible.builtin.include_tasks:
file: netbox_circuit_termination.yml
apply:
tags:
- netbox_circuit_termination
tags:
- netbox_circuit_termination

- name: NETBOX_REAR_PORT TESTS
ansible.builtin.include_tasks:
file: netbox_rear_port.yml
@@ -732,3 +723,21 @@
- netbox_tunnel_group
tags:
- netbox_tunnel_group

- name: NETBOX_MAC_ADDRESS TESTS
ansible.builtin.include_tasks:
file: netbox_mac_address.yml
apply:
tags:
- netbox_mac_address
tags:
- netbox_mac_address

- name: NETBOX_CIRCUIT_TERMINATION TESTS
ansible.builtin.include_tasks:
file: netbox_circuit_termination.yml
apply:
tags:
- netbox_circuit_termination
tags:
- netbox_circuit_termination
20 changes: 20 additions & 0 deletions tests/integration/targets/v4.2/tasks/netbox_device_interface.yml
Original file line number Diff line number Diff line change
@@ -310,3 +310,23 @@
- test_twelve['interface']['name'] == "GigabitEthernet5"
- test_twelve['interface']['device'] == 1
- test_twelve['interface']['mark_connected'] == true

- name: "13 - Update interface primary MAC address"
netbox.netbox.netbox_device_interface:
netbox_url: http://localhost:32768
netbox_token: "0123456789abcdef0123456789abcdef01234567"
data:
device: test100
name: GigabitEthernet2
primary_mac_address: "AA:AB:CC:DD:EE:FF"
state: present
register: test_thirteen

- name: "13 - ASSERT"
ansible.builtin.assert:
that:
- test_thirteen is changed
- test_thirteen['msg'] == "interface GigabitEthernet2 updated"
- test_thirteen['interface']['name'] == "GigabitEthernet2"
- test_thirteen['interface']['device'] == 1
- test_thirteen['interface']['primary_mac_address'] == 2
70 changes: 70 additions & 0 deletions tests/integration/targets/v4.2/tasks/netbox_mac_address.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
##
##
### NETBOX_MAC_ADDRESS
##
##
- name: "MAC 1: Create MAC address with required parameters"
netbox.netbox.netbox_mac_address:
netbox_url: http://localhost:32768
netbox_token: "0123456789abcdef0123456789abcdef01234567"
data:
mac_address: "00:11:22:33:44:55"
state: present
register: test_one

- name: "MAC 1: ASSERT - Create MAC address"
ansible.builtin.assert:
that:
- test_one is changed
- test_one['diff']['before']['state'] == "absent"
- test_one['diff']['after']['state'] == "present"
- test_one['mac_address']['mac_address'] == "00:11:22:33:44:55"
- test_one['msg'] == "mac_address 00:11:22:33:44:55 created"

- name: "MAC 2: Create MAC address with all parameters"
netbox.netbox.netbox_mac_address:
netbox_url: http://localhost:32768
netbox_token: "0123456789abcdef0123456789abcdef01234567"
data:
mac_address: "AA:BB:CC:DD:EE:F0"
assigned_object:
device: Test Nexus One
name: Ethernet1/1
description: "Test MAC address"
comments: "Test MAC address comment"
tags:
- Schnozzberry
state: present
register: test_two

- name: "MAC 2: ASSERT - Create MAC address with all parameters"
ansible.builtin.assert:
that:
- test_two is changed
- test_two['diff']['before']['state'] == "absent"
- test_two['diff']['after']['state'] == "present"
- test_two['mac_address']['mac_address'] == "AA:BB:CC:DD:EE:F0"
- test_two['mac_address']['assigned_object_type'] == "dcim.interface"
- test_two['mac_address']['assigned_object_id'] == 1
- test_two['mac_address']['description'] == "Test MAC address"
- test_two['mac_address']['comments'] == "Test MAC address comment"
- test_two['mac_address']['tags'][0] == 4
- test_two['msg'] == "mac_address AA:BB:CC:DD:EE:F0 created"

- name: "MAC 3: Delete MAC address"
netbox.netbox.netbox_mac_address:
netbox_url: http://localhost:32768
netbox_token: "0123456789abcdef0123456789abcdef01234567"
data:
mac_address: "00:11:22:33:44:55"
state: absent
register: test_three

- name: "MAC 3: ASSERT - Delete MAC address"
ansible.builtin.assert:
that:
- test_three is changed
- test_three['diff']['before']['state'] == "present"
- test_three['diff']['after']['state'] == "absent"
- test_three['msg'] == "mac_address 00:11:22:33:44:55 deleted"

Unchanged files with check annotations Beta

0.1.0:
changes:
breaking_changes:
- Changed ``group`` to ``tenant_group`` in ``netbox_tenant.py`` (https://github.com/netbox-community/ansible_modules/issues/9)

Check warning on line 6 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 8 but found 6
- Changed ``role`` to ``prefix_role`` in ``netbox_prefix.py`` (https://github.com/netbox-community/ansible_modules/issues/9)
- Module failures when required fields arent provided (https://github.com/netbox-community/ansible_modules/issues/24)
- Renamed ``netbox_interface`` to ``netbox_device_interface`` (https://github.com/netbox-community/ansible_modules/issues/9)
minor_changes:
- Add ``primary_ip4/6`` to ``netbox_ip_address`` (https://github.com/netbox-community/ansible_modules/issues/10)

Check warning on line 11 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 8 but found 6
modules:
- description: Creates or removes aggregates from NetBox

Check warning on line 13 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 6 but found 4
name: netbox_aggregate
namespace: ''
- description: Create, update or delete circuits within NetBox
0.1.1:
changes:
bugfixes:
- Fixed issue with netbox_vm_interface where it would fail if different virtual

Check warning on line 97 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 8 but found 6
machine had the same interface name (https://github.com/netbox-community/ansible_modules/issues/40)
- Updated netbox_ip_address to find interfaces on virtual machines correctly
(https://github.com/netbox-community/ansible_modules/issues/40)
0.1.10:
changes:
bugfixes:
- Updated inventory plugin name from netbox.netbox.netbox to netbox.netbox.nb_inventory

Check warning on line 104 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 8 but found 6
(https://github.com/netbox-community/ansible_modules/pull/129)
0.1.2:
changes:
bugfixes:
- Allow endpoint choices to be an integer of the choice rather than attempting

Check warning on line 109 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 8 but found 6
to dynamically determine the choice ID (https://github.com/netbox-community/ansible_modules/issues/47)
0.1.3:
changes:
bugfixes:
- Add error handling for invalid key_file for lookup plugin (https://github.com/netbox-community/ansible_modules/issues/52)

Check warning on line 114 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 8 but found 6
0.1.5:
changes:
bugfixes:
- Add argument specs for every module to validate data passed in. Fixes some

Check warning on line 118 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 8 but found 6
idempotency issues. POSSIBLE BREAKING CHANGE (https://github.com/netbox-community/ansible_modules/issues/68)
- Allow name updates to manufacturers (https://github.com/netbox-community/ansible_modules/issues/76)
- Builds slug for netbox_device_type from model which is now required and slug
a /32 and pass to NetBox. Fixes idempotency cidr notation is not provided
(https://github.com/netbox-community/ansible_modules/issues/78)
modules:
- description: Creates or removes service from NetBox

Check warning on line 132 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 6 but found 4
name: netbox_service
namespace: ''
0.1.6:
changes:
bugfixes:
- Fixed vlan searching with vlan_group for netbox_prefix (https://github.com/netbox-community/ansible_modules/issues/85)

Check warning on line 138 in changelogs/changelog.yaml

GitHub Actions / ansible-lint / Ansible Lint

yaml[indentation]

Wrong indentation: expected 8 but found 6
- Removed static choices from netbox_utils and now pulls the choices for each
endpoint from the NetBox API at call time (https://github.com/netbox-community/ansible_modules/issues/67)
minor_changes: