diff --git a/changelogs/fragments/1323-fixing-bug-#975.yml b/changelogs/fragments/1323-fixing-bug-#975.yml new file mode 100644 index 000000000..c0ac8b423 --- /dev/null +++ b/changelogs/fragments/1323-fixing-bug-#975.yml @@ -0,0 +1,2 @@ +bugfixes: + - netbox_ip_address - Fixed the problem preventing assignment of an IP address to a network interface diff --git a/changelogs/fragments/1327-add-custom-headers.yml b/changelogs/fragments/1327-add-custom-headers.yml new file mode 100644 index 000000000..b13f56c89 --- /dev/null +++ b/changelogs/fragments/1327-add-custom-headers.yml @@ -0,0 +1,2 @@ +minor_changes: + - Add support for custom headers diff --git a/changelogs/fragments/1332-add-hostname_field-option.yml b/changelogs/fragments/1332-add-hostname_field-option.yml new file mode 100644 index 000000000..5efcba69f --- /dev/null +++ b/changelogs/fragments/1332-add-hostname_field-option.yml @@ -0,0 +1,2 @@ +minor_changes: + - Add example for using ansible variables in lookup diff --git a/changelogs/fragments/1335-api-status-page-not-found.yml b/changelogs/fragments/1335-api-status-page-not-found.yml new file mode 100644 index 000000000..14c90a600 --- /dev/null +++ b/changelogs/fragments/1335-api-status-page-not-found.yml @@ -0,0 +1,2 @@ +bugfixes: + - fix call /api/status/ instead /api/status in nb_inventory plugin. (https://github.com/netbox-community/ansible_modules/issues/1335). diff --git a/plugins/inventory/nb_inventory.py b/plugins/inventory/nb_inventory.py index 678a41798..b06d0fa7f 100644 --- a/plugins/inventory/nb_inventory.py +++ b/plugins/inventory/nb_inventory.py @@ -264,6 +264,11 @@ - By default, the inventory hostname is the netbox device name - If set, sets the inventory hostname from this field in custom_fields instead default: False + headers: + description: Dictionary of headers to be passed to the NetBox API. + default: {} + env: + - name: NETBOX_HEADERS """ EXAMPLES = """ @@ -281,6 +286,8 @@ device_query_filters: - has_primary_ip: 'true' - tenant__n: internal +headers: + Cookie: "{{ auth_cookie }}" # has_primary_ip is a useful way to filter out patch panels and other passive devices # Adding '__n' to a field searches for the negation of the value. @@ -1600,7 +1607,7 @@ def fetch_api_docs(self): cached_api_version = None cache = None - status = self._fetch_information(self.api_endpoint + "/api/status") + status = self._fetch_information(self.api_endpoint + "/api/status/") netbox_api_version = ".".join(status["netbox-version"].split(".")[:2]) if version.parse(netbox_api_version) >= version.parse("3.5.0"): @@ -2115,6 +2122,12 @@ def _set_authorization(self): ) else: self.headers.update({"Authorization": "Token %s" % token}) + headers = self.get_option("headers") + if headers: + if isinstance(headers, str): + headers = json.loads(headers) + if isinstance(headers, dict): + self.headers.update(headers) def parse(self, inventory, loader, path, cache=True): super(InventoryModule, self).parse(inventory, loader, path) diff --git a/plugins/lookup/nb_lookup.py b/plugins/lookup/nb_lookup.py index 99de7323a..a3bd0422f 100644 --- a/plugins/lookup/nb_lookup.py +++ b/plugins/lookup/nb_lookup.py @@ -51,6 +51,11 @@ - name: NETBOX_TOKEN - name: NETBOX_API_TOKEN required: false + headers: + description: Dictionary of headers to be passed to the NetBox API. + default: {} + env: + - name: NETBOX_HEADERS validate_certs: description: - Whether or not to validate SSL of the NetBox instance @@ -97,6 +102,20 @@ api_endpoint='http://localhost/', api_filter='role=management tag=Dell'), token='') }}" + # This example uses an API Filter with a variable and jinja concatenation + - name: Set hostname fact + set_fact: + hostname: "my-server" + - name: Obtain details of a single device from NetBox + debug: + msg: > + "Device {{item.0.value.display}} (ID: {{item.0.key}}) was + manufactured by {{ item.0.value.device_type.manufacturer.name }}" + loop: + - '{{ query("netbox.netbox.nb_lookup", "devices", + api_endpoint="http://localhost/", + api_filter="name=" ~hostname, + token="") }}' """ RETURN = """ @@ -108,6 +127,7 @@ import os import functools +import json from pprint import pformat from ansible.errors import AnsibleError @@ -411,6 +431,7 @@ def run(self, terms, variables=None, **kwargs): or os.getenv("NETBOX_API") or os.getenv("NETBOX_URL") ) + netbox_headers = kwargs.get("headers") or os.getenv("NETBOX_HEADERS") or {} netbox_ssl_verify = kwargs.get("validate_certs", True) netbox_private_key = kwargs.get("private_key") netbox_private_key_file = kwargs.get("key_file") @@ -421,8 +442,12 @@ def run(self, terms, variables=None, **kwargs): if not isinstance(terms, list): terms = [terms] + if isinstance(netbox_headers, str): + netbox_headers = json.loads(netbox_headers) + try: session = requests.Session() + session.headers = netbox_headers session.verify = netbox_ssl_verify if Version(version("pynetbox")) < Version("7.0.0"): diff --git a/plugins/module_utils/netbox_ipam.py b/plugins/module_utils/netbox_ipam.py index f8a0131dc..8d1b731f4 100644 --- a/plugins/module_utils/netbox_ipam.py +++ b/plugins/module_utils/netbox_ipam.py @@ -205,6 +205,11 @@ def run(self): else: name = data.get("name") + if self.endpoint == "ip_addresses": + if "interface" in data: + data["assigned_object_id"] = data["interface"] + data["assigned_object_type"] = "dcim.interface" + if self.endpoint in SLUG_REQUIRED: if not data.get("slug"): data["slug"] = self._to_slug(name) diff --git a/tests/integration/targets/v3.5/tasks/netbox_ip_address.yml b/tests/integration/targets/v3.5/tasks/netbox_ip_address.yml index ce2b125a4..c73fb9dfb 100644 --- a/tests/integration/targets/v3.5/tasks/netbox_ip_address.yml +++ b/tests/integration/targets/v3.5/tasks/netbox_ip_address.yml @@ -348,3 +348,52 @@ - test_sixteen['diff']['after']['state'] == "present" - test_sixteen['msg'] == "ip_address 10.120.10.1/32 created" - test_sixteen['ip_address']['address'] == "10.120.10.1/32" + +- name: "17 - Create IP address on GigabitEthernet2 - test100 with interface value - State: present" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + address: 10.10.200.31/16 + interface: + device: test100 + name: GigabitEthernet2 + register: test_seventeen + +- name: 17 - ASSERT + ansible.builtin.assert: + that: + - test_seventeen is changed + - test_seventeen['diff']['before']['state'] == "absent" + - test_seventeen['diff']['after']['state'] == "present" + - test_seventeen['msg'] == "ip_address 10.10.200.31/16 created" + - test_seventeen['ip_address']['address'] == "10.10.200.31/16" + - test_seventeen['ip_address']['family'] == 4 + - test_seventeen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_seventeen['ip_address']['assigned_object_id'] == 4 + +- name: "18 - Create IP address on GigabitEthernet2 - test100 with interface value - State: new" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + prefix: 10.10.0.0/16 + interface: + name: GigabitEthernet2 + device: test100 + state: new + register: test_eighteen + +- name: 18 - ASSERT + ansible.builtin.assert: + that: + - test_eighteen is changed + - test_eighteen['diff']['before']['state'] == "absent" + - test_eighteen['diff']['after']['state'] == "present" + - test_eighteen['msg'] == "ip_address 10.10.0.2/16 created" + - test_eighteen['ip_address']['address'] == "10.10.0.2/16" + - test_eighteen['ip_address']['family'] == 4 + - test_eighteen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_eighteen['ip_address']['assigned_object_id'] == 4 diff --git a/tests/integration/targets/v3.5/tasks/netbox_lookup.yml b/tests/integration/targets/v3.5/tasks/netbox_lookup.yml index 3d54e02b1..046f327eb 100644 --- a/tests/integration/targets/v3.5/tasks/netbox_lookup.yml +++ b/tests/integration/targets/v3.5/tasks/netbox_lookup.yml @@ -88,3 +88,18 @@ vars: query_result: "{{ query('netbox.netbox.nb_lookup', 'devices', api_filter='id=1', api_endpoint='http://localhost:32768', token='0123456789abcdef0123456789abcdef01234567') }}" + +- name: "NETBOX_LOOKUP 11: Device query by ansible variable" + ansible.builtin.set_fact: + hostname: "L2" + +- name: "NETBOX LOOKUP 11.1: Obtain details of a single device from NetBox" + ansible.builtin.debug: + msg: > + "Device {{item.0.value.display}} (ID: {{item.0.key}}) was + manufactured by {{ item.0.value.device_type.manufacturer.name }}" + loop: + - '{{ query("netbox.netbox.nb_lookup", "devices", + api_filter="name=" ~hostname, + api_endpoint="http://localhost:32768", + token="0123456789abcdef0123456789abcdef01234567") }}' diff --git a/tests/integration/targets/v3.6/tasks/netbox_ip_address.yml b/tests/integration/targets/v3.6/tasks/netbox_ip_address.yml index ce2b125a4..c73fb9dfb 100644 --- a/tests/integration/targets/v3.6/tasks/netbox_ip_address.yml +++ b/tests/integration/targets/v3.6/tasks/netbox_ip_address.yml @@ -348,3 +348,52 @@ - test_sixteen['diff']['after']['state'] == "present" - test_sixteen['msg'] == "ip_address 10.120.10.1/32 created" - test_sixteen['ip_address']['address'] == "10.120.10.1/32" + +- name: "17 - Create IP address on GigabitEthernet2 - test100 with interface value - State: present" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + address: 10.10.200.31/16 + interface: + device: test100 + name: GigabitEthernet2 + register: test_seventeen + +- name: 17 - ASSERT + ansible.builtin.assert: + that: + - test_seventeen is changed + - test_seventeen['diff']['before']['state'] == "absent" + - test_seventeen['diff']['after']['state'] == "present" + - test_seventeen['msg'] == "ip_address 10.10.200.31/16 created" + - test_seventeen['ip_address']['address'] == "10.10.200.31/16" + - test_seventeen['ip_address']['family'] == 4 + - test_seventeen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_seventeen['ip_address']['assigned_object_id'] == 4 + +- name: "18 - Create IP address on GigabitEthernet2 - test100 with interface value - State: new" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + prefix: 10.10.0.0/16 + interface: + name: GigabitEthernet2 + device: test100 + state: new + register: test_eighteen + +- name: 18 - ASSERT + ansible.builtin.assert: + that: + - test_eighteen is changed + - test_eighteen['diff']['before']['state'] == "absent" + - test_eighteen['diff']['after']['state'] == "present" + - test_eighteen['msg'] == "ip_address 10.10.0.2/16 created" + - test_eighteen['ip_address']['address'] == "10.10.0.2/16" + - test_eighteen['ip_address']['family'] == 4 + - test_eighteen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_eighteen['ip_address']['assigned_object_id'] == 4 diff --git a/tests/integration/targets/v3.6/tasks/netbox_lookup.yml b/tests/integration/targets/v3.6/tasks/netbox_lookup.yml index 3d54e02b1..046f327eb 100644 --- a/tests/integration/targets/v3.6/tasks/netbox_lookup.yml +++ b/tests/integration/targets/v3.6/tasks/netbox_lookup.yml @@ -88,3 +88,18 @@ vars: query_result: "{{ query('netbox.netbox.nb_lookup', 'devices', api_filter='id=1', api_endpoint='http://localhost:32768', token='0123456789abcdef0123456789abcdef01234567') }}" + +- name: "NETBOX_LOOKUP 11: Device query by ansible variable" + ansible.builtin.set_fact: + hostname: "L2" + +- name: "NETBOX LOOKUP 11.1: Obtain details of a single device from NetBox" + ansible.builtin.debug: + msg: > + "Device {{item.0.value.display}} (ID: {{item.0.key}}) was + manufactured by {{ item.0.value.device_type.manufacturer.name }}" + loop: + - '{{ query("netbox.netbox.nb_lookup", "devices", + api_filter="name=" ~hostname, + api_endpoint="http://localhost:32768", + token="0123456789abcdef0123456789abcdef01234567") }}' diff --git a/tests/integration/targets/v3.7/tasks/netbox_ip_address.yml b/tests/integration/targets/v3.7/tasks/netbox_ip_address.yml index 8813f8dcb..328cdfc35 100644 --- a/tests/integration/targets/v3.7/tasks/netbox_ip_address.yml +++ b/tests/integration/targets/v3.7/tasks/netbox_ip_address.yml @@ -350,3 +350,52 @@ - test_sixteen['diff']['after']['state'] == "present" - test_sixteen['msg'] == "ip_address 10.120.10.1/32 created" - test_sixteen['ip_address']['address'] == "10.120.10.1/32" + +- name: "17 - Create IP address on GigabitEthernet2 - test100 with interface value - State: present" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + address: 10.10.200.31/16 + interface: + device: test100 + name: GigabitEthernet2 + register: test_seventeen + +- name: 17 - ASSERT + ansible.builtin.assert: + that: + - test_seventeen is changed + - test_seventeen['diff']['before']['state'] == "absent" + - test_seventeen['diff']['after']['state'] == "present" + - test_seventeen['msg'] == "ip_address 10.10.200.31/16 created" + - test_seventeen['ip_address']['address'] == "10.10.200.31/16" + - test_seventeen['ip_address']['family'] == 4 + - test_seventeen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_seventeen['ip_address']['assigned_object_id'] == 4 + +- name: "18 - Create IP address on GigabitEthernet2 - test100 with interface value - State: new" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + prefix: 10.10.0.0/16 + interface: + name: GigabitEthernet2 + device: test100 + state: new + register: test_eighteen + +- name: 18 - ASSERT + ansible.builtin.assert: + that: + - test_eighteen is changed + - test_eighteen['diff']['before']['state'] == "absent" + - test_eighteen['diff']['after']['state'] == "present" + - test_eighteen['msg'] == "ip_address 10.10.0.2/16 created" + - test_eighteen['ip_address']['address'] == "10.10.0.2/16" + - test_eighteen['ip_address']['family'] == 4 + - test_eighteen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_eighteen['ip_address']['assigned_object_id'] == 4 diff --git a/tests/integration/targets/v3.7/tasks/netbox_lookup.yml b/tests/integration/targets/v3.7/tasks/netbox_lookup.yml index 3d54e02b1..046f327eb 100644 --- a/tests/integration/targets/v3.7/tasks/netbox_lookup.yml +++ b/tests/integration/targets/v3.7/tasks/netbox_lookup.yml @@ -88,3 +88,18 @@ vars: query_result: "{{ query('netbox.netbox.nb_lookup', 'devices', api_filter='id=1', api_endpoint='http://localhost:32768', token='0123456789abcdef0123456789abcdef01234567') }}" + +- name: "NETBOX_LOOKUP 11: Device query by ansible variable" + ansible.builtin.set_fact: + hostname: "L2" + +- name: "NETBOX LOOKUP 11.1: Obtain details of a single device from NetBox" + ansible.builtin.debug: + msg: > + "Device {{item.0.value.display}} (ID: {{item.0.key}}) was + manufactured by {{ item.0.value.device_type.manufacturer.name }}" + loop: + - '{{ query("netbox.netbox.nb_lookup", "devices", + api_filter="name=" ~hostname, + api_endpoint="http://localhost:32768", + token="0123456789abcdef0123456789abcdef01234567") }}' diff --git a/tests/integration/targets/v4.0/tasks/netbox_ip_address.yml b/tests/integration/targets/v4.0/tasks/netbox_ip_address.yml index 8813f8dcb..328cdfc35 100644 --- a/tests/integration/targets/v4.0/tasks/netbox_ip_address.yml +++ b/tests/integration/targets/v4.0/tasks/netbox_ip_address.yml @@ -350,3 +350,52 @@ - test_sixteen['diff']['after']['state'] == "present" - test_sixteen['msg'] == "ip_address 10.120.10.1/32 created" - test_sixteen['ip_address']['address'] == "10.120.10.1/32" + +- name: "17 - Create IP address on GigabitEthernet2 - test100 with interface value - State: present" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + address: 10.10.200.31/16 + interface: + device: test100 + name: GigabitEthernet2 + register: test_seventeen + +- name: 17 - ASSERT + ansible.builtin.assert: + that: + - test_seventeen is changed + - test_seventeen['diff']['before']['state'] == "absent" + - test_seventeen['diff']['after']['state'] == "present" + - test_seventeen['msg'] == "ip_address 10.10.200.31/16 created" + - test_seventeen['ip_address']['address'] == "10.10.200.31/16" + - test_seventeen['ip_address']['family'] == 4 + - test_seventeen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_seventeen['ip_address']['assigned_object_id'] == 4 + +- name: "18 - Create IP address on GigabitEthernet2 - test100 with interface value - State: new" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + prefix: 10.10.0.0/16 + interface: + name: GigabitEthernet2 + device: test100 + state: new + register: test_eighteen + +- name: 18 - ASSERT + ansible.builtin.assert: + that: + - test_eighteen is changed + - test_eighteen['diff']['before']['state'] == "absent" + - test_eighteen['diff']['after']['state'] == "present" + - test_eighteen['msg'] == "ip_address 10.10.0.2/16 created" + - test_eighteen['ip_address']['address'] == "10.10.0.2/16" + - test_eighteen['ip_address']['family'] == 4 + - test_eighteen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_eighteen['ip_address']['assigned_object_id'] == 4 diff --git a/tests/integration/targets/v4.0/tasks/netbox_lookup.yml b/tests/integration/targets/v4.0/tasks/netbox_lookup.yml index 3d54e02b1..046f327eb 100644 --- a/tests/integration/targets/v4.0/tasks/netbox_lookup.yml +++ b/tests/integration/targets/v4.0/tasks/netbox_lookup.yml @@ -88,3 +88,18 @@ vars: query_result: "{{ query('netbox.netbox.nb_lookup', 'devices', api_filter='id=1', api_endpoint='http://localhost:32768', token='0123456789abcdef0123456789abcdef01234567') }}" + +- name: "NETBOX_LOOKUP 11: Device query by ansible variable" + ansible.builtin.set_fact: + hostname: "L2" + +- name: "NETBOX LOOKUP 11.1: Obtain details of a single device from NetBox" + ansible.builtin.debug: + msg: > + "Device {{item.0.value.display}} (ID: {{item.0.key}}) was + manufactured by {{ item.0.value.device_type.manufacturer.name }}" + loop: + - '{{ query("netbox.netbox.nb_lookup", "devices", + api_filter="name=" ~hostname, + api_endpoint="http://localhost:32768", + token="0123456789abcdef0123456789abcdef01234567") }}' diff --git a/tests/integration/targets/v4.1/tasks/netbox_ip_address.yml b/tests/integration/targets/v4.1/tasks/netbox_ip_address.yml index 8813f8dcb..328cdfc35 100644 --- a/tests/integration/targets/v4.1/tasks/netbox_ip_address.yml +++ b/tests/integration/targets/v4.1/tasks/netbox_ip_address.yml @@ -350,3 +350,52 @@ - test_sixteen['diff']['after']['state'] == "present" - test_sixteen['msg'] == "ip_address 10.120.10.1/32 created" - test_sixteen['ip_address']['address'] == "10.120.10.1/32" + +- name: "17 - Create IP address on GigabitEthernet2 - test100 with interface value - State: present" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + address: 10.10.200.31/16 + interface: + device: test100 + name: GigabitEthernet2 + register: test_seventeen + +- name: 17 - ASSERT + ansible.builtin.assert: + that: + - test_seventeen is changed + - test_seventeen['diff']['before']['state'] == "absent" + - test_seventeen['diff']['after']['state'] == "present" + - test_seventeen['msg'] == "ip_address 10.10.200.31/16 created" + - test_seventeen['ip_address']['address'] == "10.10.200.31/16" + - test_seventeen['ip_address']['family'] == 4 + - test_seventeen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_seventeen['ip_address']['assigned_object_id'] == 4 + +- name: "18 - Create IP address on GigabitEthernet2 - test100 with interface value - State: new" + netbox.netbox.netbox_ip_address: + netbox_url: http://localhost:32768 + netbox_token: "0123456789abcdef0123456789abcdef01234567" + data: + family: 4 + prefix: 10.10.0.0/16 + interface: + name: GigabitEthernet2 + device: test100 + state: new + register: test_eighteen + +- name: 18 - ASSERT + ansible.builtin.assert: + that: + - test_eighteen is changed + - test_eighteen['diff']['before']['state'] == "absent" + - test_eighteen['diff']['after']['state'] == "present" + - test_eighteen['msg'] == "ip_address 10.10.0.2/16 created" + - test_eighteen['ip_address']['address'] == "10.10.0.2/16" + - test_eighteen['ip_address']['family'] == 4 + - test_eighteen['ip_address']['assigned_object_type'] == "dcim.interface" + - test_eighteen['ip_address']['assigned_object_id'] == 4 diff --git a/tests/integration/targets/v4.1/tasks/netbox_lookup.yml b/tests/integration/targets/v4.1/tasks/netbox_lookup.yml index 3d54e02b1..046f327eb 100644 --- a/tests/integration/targets/v4.1/tasks/netbox_lookup.yml +++ b/tests/integration/targets/v4.1/tasks/netbox_lookup.yml @@ -88,3 +88,18 @@ vars: query_result: "{{ query('netbox.netbox.nb_lookup', 'devices', api_filter='id=1', api_endpoint='http://localhost:32768', token='0123456789abcdef0123456789abcdef01234567') }}" + +- name: "NETBOX_LOOKUP 11: Device query by ansible variable" + ansible.builtin.set_fact: + hostname: "L2" + +- name: "NETBOX LOOKUP 11.1: Obtain details of a single device from NetBox" + ansible.builtin.debug: + msg: > + "Device {{item.0.value.display}} (ID: {{item.0.key}}) was + manufactured by {{ item.0.value.device_type.manufacturer.name }}" + loop: + - '{{ query("netbox.netbox.nb_lookup", "devices", + api_filter="name=" ~hostname, + api_endpoint="http://localhost:32768", + token="0123456789abcdef0123456789abcdef01234567") }}'