diff --git a/changelogs/fragments/52-ipam-host.yml b/changelogs/fragments/52-ipam-host.yml new file mode 100644 index 0000000..3388f32 --- /dev/null +++ b/changelogs/fragments/52-ipam-host.yml @@ -0,0 +1,3 @@ +deprecated_features: + - b1_ipam_host - is deprecated in favor of 'ipam_host'. + - b1_ipam_host_gather - is deprecated in favor of 'ipam_host_info'. diff --git a/meta/runtime.yml b/meta/runtime.yml index 93a4ed9..3b513b4 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -20,6 +20,8 @@ action_groups: - ipam_subnet_info - ipam_address_block - ipam_address_block_info + - ipam_host + - ipam_host_info plugin_routing: modules: @@ -67,3 +69,12 @@ plugin_routing: deprecation: removal_version: 3.0.0 warning_text: Use infoblox.bloxone.dns_auth_zone_info instead. + + b1_ipam_host: + deprecation: + removal_version: 3.0.0 + warning_text: Use infoblox.bloxone.ipam_host instead. + b1_ipam_host_gather: + deprecation: + removal_version: 3.0.0 + warning_text: Use infoblox.bloxone.ipam_host_info instead. diff --git a/plugins/modules/b1_ipam_host.py b/plugins/modules/b1_ipam_host.py index 540304c..7721c6a 100644 --- a/plugins/modules/b1_ipam_host.py +++ b/plugins/modules/b1_ipam_host.py @@ -13,6 +13,10 @@ author: "Akhilesh Kabade (@akhilesh-kabade-infoblox), Sriram Kannan(@kannans)" short_description: Configure Host on Infoblox BloxOne DDI version_added: "1.0.1" +deprecated: + removed_in: 3.0.0 + why: This module is deprecated and will be removed in version 3.0.0. Use M(ipam_host) instead. + alternative: Use M(ipam_host) instead. description: - Create, Update and Delete Hosts on Infoblox BloxOne DDI. This module manages the IPAM Host object using BloxOne REST APIs. requirements: diff --git a/plugins/modules/b1_ipam_host_gather.py b/plugins/modules/b1_ipam_host_gather.py index be5c484..2e3a828 100644 --- a/plugins/modules/b1_ipam_host_gather.py +++ b/plugins/modules/b1_ipam_host_gather.py @@ -10,6 +10,10 @@ module: b1_ipam_ip_space author: "Akhilesh Kabade (@akhilesh-kabade-infoblox)" short_description: Gather IPAM host facts +deprecated: + removed_in: 3.0.0 + why: This module is deprecated and will be removed in version 3.0.0. Use M(ipam_host_info) instead. + alternative: Use M(ipam_host_info) instead. description: - Gather facts about IPAM hosts in Infoblox BloxOne DDI. This module gathers facts of IPAM Hosts object using BloxOne REST APIs. requirements: diff --git a/plugins/modules/ipam_host.py b/plugins/modules/ipam_host.py new file mode 100644 index 0000000..16f0733 --- /dev/null +++ b/plugins/modules/ipam_host.py @@ -0,0 +1,399 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Infoblox Inc. +# 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: ipam_host +short_description: Manage IpamHost +description: + - Manage IpamHost +version_added: 2.0.0 +author: Infoblox Inc. (@infobloxopen) +options: + id: + description: + - ID of the object + type: str + required: false + state: + description: + - Indicate desired state of the object + type: str + required: false + choices: + - present + - absent + default: present + addresses: + description: + - "The list of all addresses associated with the IPAM host, which may be in different IP spaces." + type: list + elements: dict + suboptions: + address: + description: + - "Field usage depends on the operation:" + - "* For read operation, I(address) of the I(Address) corresponding to the I(ref) resource." + - "* For write operation, I(address) to be created if the I(Address) does not exist. Required if I(ref) is not set on write:" + - "* If the I(Address) already exists and is already pointing to the right I(Host), the operation proceeds." + - "* If the I(Address) already exists and is pointing to a different _Host, the operation must abort." + - "* If the I(Address) already exists and is not pointing to any I(Host), it is linked to the I(Host)." + type: str + ref: + description: + - "The resource identifier." + type: str + space: + description: + - "The resource identifier." + type: str + auto_generate_records: + description: + - "This flag specifies if resource records have to be auto generated for the host." + type: bool + comment: + description: + - "The description for the IPAM host. May contain 0 to 1024 characters. Can include UTF-8." + type: str + host_names: + description: + - "The name records to be generated for the host." + - "This field is required if I(auto_generate_records) is true." + type: list + elements: dict + suboptions: + alias: + description: + - "When I(true), the name is treated as an alias." + type: bool + name: + description: + - "A name for the host." + type: str + primary_name: + description: + - "When I(true), the name field is treated as primary name. There must be one and only one primary name in the list of host names. The primary name will be treated as the canonical name for all the aliases. PTR record will be generated only for the primary name." + type: bool + zone: + description: + - "The resource identifier." + type: str + name: + description: + - "The name of the IPAM host. Must contain 1 to 256 characters. Can include UTF-8." + type: str + tags: + description: + - "The tags for the IPAM host in JSON format." + type: dict + +extends_documentation_fragment: + - infoblox.bloxone.common +""" # noqa: E501 + +EXAMPLES = r""" + - name: "Create a Host" + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "present" + register: host + + - name: "Create an IP space" + infoblox.bloxone.ipam_ip_space: + name: "{{ name }}" + state: "present" + register: ip_space + + - name: "Create a Subnet" + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ ip_space.id }}" + state: "present" + register: subnet + + - name: "Create a Host with Addresses" + infoblox.bloxone.ipam_host: + name: "{{ name }}" + addresses: + - address: "10.0.0.1" + space: "{{ ip_space.id }}" + state: "present" + register: host + + - name: "Delete a host" + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "absent" + register: host +""" + +RETURN = r""" +id: + description: + - ID of the IpamHost object + type: str + returned: Always +item: + description: + - IpamHost object + type: complex + returned: Always + contains: + addresses: + description: + - "The list of all addresses associated with the IPAM host, which may be in different IP spaces." + type: list + returned: Always + elements: dict + contains: + address: + description: + - "Field usage depends on the operation:" + - "* For read operation, I(address) of the I(Address) corresponding to the I(ref) resource." + - "* For write operation, I(address) to be created if the I(Address) does not exist. Required if I(ref) is not set on write:" + - "* If the I(Address) already exists and is already pointing to the right I(Host), the operation proceeds." + - "* If the I(Address) already exists and is pointing to a different _Host, the operation must abort." + - "* If the I(Address) already exists and is not pointing to any I(Host), it is linked to the I(Host)." + type: str + returned: Always + ref: + description: + - "The resource identifier." + type: str + returned: Always + space: + description: + - "The resource identifier." + type: str + returned: Always + auto_generate_records: + description: + - "This flag specifies if resource records have to be auto generated for the host." + type: bool + returned: Always + comment: + description: + - "The description for the IPAM host. May contain 0 to 1024 characters. Can include UTF-8." + type: str + returned: Always + created_at: + description: + - "Time when the object has been created." + type: str + returned: Always + host_names: + description: + - "The name records to be generated for the host." + - "This field is required if I(auto_generate_records) is true." + type: list + returned: Always + elements: dict + contains: + alias: + description: + - "When I(true), the name is treated as an alias." + type: bool + returned: Always + name: + description: + - "A name for the host." + type: str + returned: Always + primary_name: + description: + - "When I(true), the name field is treated as primary name. There must be one and only one primary name in the list of host names. The primary name will be treated as the canonical name for all the aliases. PTR record will be generated only for the primary name." + type: bool + returned: Always + zone: + description: + - "The resource identifier." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + name: + description: + - "The name of the IPAM host. Must contain 1 to 256 characters. Can include UTF-8." + type: str + returned: Always + tags: + description: + - "The tags for the IPAM host in JSON format." + type: dict + returned: Always + updated_at: + description: + - "Time when the object has been updated. Equals to I(created_at) if not updated after creation." + type: str + returned: Always +""" # noqa: E501 + +from ansible_collections.infoblox.bloxone.plugins.module_utils.modules import BloxoneAnsibleModule + +try: + from bloxone_client import ApiException, NotFoundException + from ipam import IpamHost, IpamHostApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class IpamHostModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(IpamHostModule, self).__init__(*args, **kwargs) + + exclude = ["state", "csp_url", "api_key", "id"] + self._payload_params = {k: v for k, v in self.params.items() if v is not None and k not in exclude} + self._payload = IpamHost.from_dict(self._payload_params) + self._existing = None + + @property + def existing(self): + return self._existing + + @existing.setter + def existing(self, value): + self._existing = value + + @property + def payload_params(self): + return self._payload_params + + @property + def payload(self): + return self._payload + + def payload_changed(self): + if self.existing is None: + # if existing is None, then it is a create operation + return True + + return self.is_changed(self.existing.model_dump(by_alias=True, exclude_none=True), self.payload_params) + + def find(self): + if self.params["id"] is not None: + try: + resp = IpamHostApi(self.client).read(self.params["id"]) + return resp.result + except NotFoundException as e: + if self.params["state"] == "absent": + return None + raise e + else: + filter = f"name=='{self.params['name']}'" + resp = IpamHostApi(self.client).list(filter=filter) + if len(resp.results) == 1: + return resp.results[0] + if len(resp.results) > 1: + self.fail_json(msg=f"Found multiple IpamHost: {resp.results}") + if len(resp.results) == 0: + return None + + def create(self): + if self.check_mode: + return None + + resp = IpamHostApi(self.client).create(body=self.payload) + return resp.result.model_dump(by_alias=True, exclude_none=True) + + def update(self): + if self.check_mode: + return None + + resp = IpamHostApi(self.client).update(id=self.existing.id, body=self.payload) + return resp.result.model_dump(by_alias=True, exclude_none=True) + + def delete(self): + if self.check_mode: + return + + IpamHostApi(self.client).delete(self.existing.id) + + def run_command(self): + result = dict(changed=False, object={}, id=None) + + # based on the state that is passed in, we will execute the appropriate + # functions + try: + self.existing = self.find() + item = {} + if self.params["state"] == "present" and self.existing is None: + item = self.create() + result["changed"] = True + result["msg"] = "IpamHost created" + elif self.params["state"] == "present" and self.existing is not None: + if self.payload_changed(): + item = self.update() + result["changed"] = True + result["msg"] = "IpamHost updated" + elif self.params["state"] == "absent" and self.existing is not None: + self.delete() + result["changed"] = True + result["msg"] = "IpamHost deleted" + + if self.check_mode: + # if in check mode, do not update the result or the diff, just return the changed state + self.exit_json(**result) + + result["diff"] = dict( + before=self.existing.model_dump(by_alias=True, exclude_none=True) if self.existing is not None else {}, + after=item, + ) + result["object"] = item + result["id"] = ( + self.existing.id if self.existing is not None else item["id"] if (item and "id" in item) else None + ) + except ApiException as e: + self.fail_json(msg=f"Failed to execute command: {e.status} {e.reason} {e.body}") + + self.exit_json(**result) + + +def main(): + module_args = dict( + id=dict(type="str", required=False), + state=dict(type="str", required=False, choices=["present", "absent"], default="present"), + addresses=dict( + type="list", + elements="dict", + options=dict( + address=dict(type="str"), + ref=dict(type="str"), + space=dict(type="str"), + ), + ), + auto_generate_records=dict(type="bool"), + comment=dict(type="str"), + host_names=dict( + type="list", + elements="dict", + options=dict( + alias=dict(type="bool"), + name=dict(type="str"), + primary_name=dict(type="bool"), + zone=dict(type="str"), + ), + ), + name=dict(type="str"), + tags=dict(type="dict"), + ) + + module = IpamHostModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[("state", "present", ["name"])], + ) + + module.run_command() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/ipam_host_info.py b/plugins/modules/ipam_host_info.py new file mode 100644 index 0000000..dbf8084 --- /dev/null +++ b/plugins/modules/ipam_host_info.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Infoblox Inc. +# 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: ipam_host_info +short_description: Manage IpamHost +description: + - Manage IpamHost +version_added: 2.0.0 +author: Infoblox Inc. (@infobloxopen) +options: + id: + description: + - ID of the object + type: str + required: false + filters: + description: + - Filter dict to filter objects + type: dict + required: false + filter_query: + description: + - Filter query to filter objects + type: str + required: false + tag_filters: + description: + - Filter dict to filter objects by tags + type: dict + required: false + tag_filter_query: + description: + - Filter query to filter objects by tags + type: str + required: false + +extends_documentation_fragment: + - infoblox.bloxone.common +""" # noqa: E501 + +EXAMPLES = r""" + - name: "Create a Host" + infoblox.bloxone.ipam_host: + name: "{{ name }}" + tags: + tag1: "{{ tag1_value }}" + tag2: "{{ tag1_value }}" + state: "present" + register: host + + - name: Get Host information by ID + infoblox.bloxone.ipam_host_info: + id: "{{ host.id }}" + register: host_info + - assert: + that: + - host_info.objects | length == 1 + + - name: Get Host information by filters + infoblox.bloxone.ipam_host_info: + filters: + name: "{{ name }}" + register: host_info + - assert: + that: + - host_info.objects | length == 1 + + - name: Get IPAM Host information by filter query + infoblox.bloxone.ipam_host_info: + filter_query: "name=='{{ name }}'" + - assert: + that: + - host_info.objects | length == 1 + - host_info.objects[0].id == host.id + + - name: Get IPAM Host information by tag filters + infoblox.bloxone.ipam_host_info: + tag_filters: + tag1: "{ { tag1_value } }" + tag2: "{ { tag2_value } }" + - assert: + that: + - host_info.objects | length == 1 + - host_info.objects[0].id == host.id + +""" + +RETURN = r""" +id: + description: + - ID of the IpamHost object + type: str + returned: Always +objects: + description: + - IpamHost object + type: list + elements: dict + returned: Always + contains: + addresses: + description: + - "The list of all addresses associated with the IPAM host, which may be in different IP spaces." + type: list + returned: Always + elements: dict + contains: + address: + description: + - "Field usage depends on the operation:" + - "* For read operation, I(address) of the I(Address) corresponding to the I(ref) resource." + - "* For write operation, I(address) to be created if the I(Address) does not exist. Required if I(ref) is not set on write:" + - "* If the I(Address) already exists and is already pointing to the right I(Host), the operation proceeds." + - "* If the I(Address) already exists and is pointing to a different _Host, the operation must abort." + - "* If the I(Address) already exists and is not pointing to any I(Host), it is linked to the I(Host)." + type: str + returned: Always + ref: + description: + - "The resource identifier." + type: str + returned: Always + space: + description: + - "The resource identifier." + type: str + returned: Always + auto_generate_records: + description: + - "This flag specifies if resource records have to be auto generated for the host." + type: bool + returned: Always + comment: + description: + - "The description for the IPAM host. May contain 0 to 1024 characters. Can include UTF-8." + type: str + returned: Always + created_at: + description: + - "Time when the object has been created." + type: str + returned: Always + host_names: + description: + - "The name records to be generated for the host." + - "This field is required if I(auto_generate_records) is true." + type: list + returned: Always + elements: dict + contains: + alias: + description: + - "When I(true), the name is treated as an alias." + type: bool + returned: Always + name: + description: + - "A name for the host." + type: str + returned: Always + primary_name: + description: + - "When I(true), the name field is treated as primary name. There must be one and only one primary name in the list of host names. The primary name will be treated as the canonical name for all the aliases. PTR record will be generated only for the primary name." + type: bool + returned: Always + zone: + description: + - "The resource identifier." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + name: + description: + - "The name of the IPAM host. Must contain 1 to 256 characters. Can include UTF-8." + type: str + returned: Always + tags: + description: + - "The tags for the IPAM host in JSON format." + type: dict + returned: Always + updated_at: + description: + - "Time when the object has been updated. Equals to I(created_at) if not updated after creation." + type: str + returned: Always +""" # noqa: E501 + +from ansible_collections.infoblox.bloxone.plugins.module_utils.modules import BloxoneAnsibleModule + +try: + from bloxone_client import ApiException, NotFoundException + from ipam import IpamHostApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class IpamHostInfoModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(IpamHostInfoModule, self).__init__(*args, **kwargs) + self._existing = None + self._limit = 1000 + + def find_by_id(self): + try: + resp = IpamHostApi(self.client).read(self.params["id"]) + return [resp.result] + except NotFoundException as e: + return None + + def find(self): + if self.params["id"] is not None: + return self.find_by_id() + + filter_str = None + if self.params["filters"] is not None: + filter_str = " and ".join([f"{k}=='{v}'" for k, v in self.params["filters"].items()]) + elif self.params["filter_query"] is not None: + filter_str = self.params["filter_query"] + + tag_filter_str = None + if self.params["tag_filters"] is not None: + tag_filter_str = " and ".join([f"{k}=='{v}'" for k, v in self.params["tag_filters"].items()]) + elif self.params["tag_filter_query"] is not None: + tag_filter_str = self.params["tag_filter_query"] + + all_results = [] + offset = 0 + + while True: + try: + resp = IpamHostApi(self.client).list( + offset=offset, limit=self._limit, filter=filter_str, tfilter=tag_filter_str + ) + all_results.extend(resp.results) + + if len(resp.results) < self._limit: + break + offset += self._limit + + except ApiException as e: + self.fail_json(msg=f"Failed to execute command: {e.status} {e.reason} {e.body}") + + return all_results + + def run_command(self): + result = dict(objects=[]) + + if self.check_mode: + self.exit_json(**result) + + find_results = self.find() + + all_results = [] + for r in find_results: + all_results.append(r.model_dump(by_alias=True, exclude_none=True)) + + result["objects"] = all_results + self.exit_json(**result) + + +def main(): + # define available arguments/parameters a user can pass to the module + module_args = dict( + id=dict(type="str", required=False), + filters=dict(type="dict", required=False), + filter_query=dict(type="str", required=False), + tag_filters=dict(type="dict", required=False), + tag_filter_query=dict(type="str", required=False), + ) + + module = IpamHostInfoModule( + argument_spec=module_args, + supports_check_mode=True, + mutually_exclusive=[ + ["id", "filters", "filter_query"], + ["id", "tag_filters", "tag_filter_query"], + ], + ) + module.run_command() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/ipam_host/tasks/main.yml b/tests/integration/targets/ipam_host/tasks/main.yml new file mode 100644 index 0000000..16da6b7 --- /dev/null +++ b/tests/integration/targets/ipam_host/tasks/main.yml @@ -0,0 +1,172 @@ +--- +#TODO: add tests +# The following test require next_available_id to be supported. +# - Create IPAM Host with next available ip + +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + # Create an random Host name to avoid conflicts + - ansible.builtin.set_fact: + name: "test-host-{{ 999999 | random | string }}" + + # Basic tests for Ipam Host + - name: Create a Host (check mode) + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "present" + check_mode: true + register: host + + - name: Create a Host + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "present" + register: host + - name: Get information about the host + infoblox.bloxone.ipam_host_info: + filters: + name: "{{ name }}" + register: host_info + - assert: + that: + - host is not failed + - host_info.objects | length == 1 + - host_info.objects[0].id == host.id + + - name: Create a Host (idempotent) + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "present" + register: host + - assert: + that: + - host is not changed + - host is not failed + + - name: Delete a Host (check mode) + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "absent" + check_mode: true + register: host + - name: Get information about the Host + infoblox.bloxone.ipam_host_info: + filters: + name: "{{ name }}" + register: host_info + - assert: + that: + - host is changed + - host is not failed + - host_info.objects | length == 1 + + - name: Delete a host + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "absent" + register: host + - name: Get information about the host + infoblox.bloxone.ipam_host_info: + filters: + name: "{{ name }}" + register: host_info + - assert: + that: + - host is changed + - host is not failed + - host_info.objects | length == 0 + + - name: Delete a host (idempotent) + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "absent" + register: host + - assert: + that: + - host is not changed + - host is not failed + + - name: Create a Host with comment + infoblox.bloxone.ipam_host: + name: "{{ name }}" + comment: "test comment" + state: "present" + register: host + - name: Get information about the Host + infoblox.bloxone.ipam_host_info: + filters: + name: "{{ name }}" + register: host_info + - assert: + that: + - host is not failed + - host_info.objects | length == 1 + - host_info.objects[0].id == host.id + - host_info.objects[0].comment == "test comment" + + - name: Create a Host with tags + infoblox.bloxone.ipam_host: + name: "{{ name }}" + tags: + tag1: "value1" + tag2: "value2" + state: "present" + register: host + + - name: Get information about the Host + infoblox.bloxone.ipam_host_info: + filters: + name: "{{ name }}" + register: host_info + - assert: + that: + - host is not failed + - host_info.objects | length == 1 + - host_info.objects[0].id == host.id + - host_info.objects[0].tags.tag1 == "value1" + - host_info.objects[0].tags.tag2 == "value2" + + - name: Create an IP space + infoblox.bloxone.ipam_ip_space: + name: "{{ name }}" + state: "present" + register: ip_space + + - name: Create a Subnet + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ ip_space.id }}" + state: "present" + register: subnet + + - name: Create a Host with Addresses + infoblox.bloxone.ipam_host: + name: "{{ name }}" + addresses: + - address: "10.0.0.1" + space: "{{ ip_space.id }}" + state: "present" + register: host + + - name: Get information about the Host + infoblox.bloxone.ipam_host_info: + filters: + name: "{{ name }}" + register: host_info + - assert: + that: + - host is not failed + - host_info.objects | length == 1 + - host_info.objects[0].id == host.id + - host_info.objects[0].addresses[0].address == "10.0.0.1" + + always: + # Cleanup if the test fails + - name: "Delete IPAM Host" + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "absent" + ignore_errors: true diff --git a/tests/integration/targets/ipam_host_info/tasks/main.yml b/tests/integration/targets/ipam_host_info/tasks/main.yml new file mode 100644 index 0000000..29999d8 --- /dev/null +++ b/tests/integration/targets/ipam_host_info/tasks/main.yml @@ -0,0 +1,62 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + - ansible.builtin.set_fact: + name: "test-host-{{ 999999 | random | string }}" + tag1_value: "site-{{ 999999 | random | string }}" + tag2_value: "site-{{ 999999 | random | string }}" + + - name: Create a Host + infoblox.bloxone.ipam_host: + name: "{{ name }}" + tags: + tag1: "{{ tag1_value }}" + tag2: "{{ tag1_value }}" + state: "present" + register: host + + - name: Get Host information by ID + infoblox.bloxone.ipam_host_info: + id: "{{ host.id }}" + register: host_info + - assert: + that: + - host_info.objects | length == 1 + + - name: Get Host information by filters + infoblox.bloxone.ipam_host_info: + filters: + name: "{{ name }}" + register: host_info + - assert: + that: + - host_info.objects | length == 1 + + - name: Get Host information by filter query + infoblox.bloxone.ipam_host_info: + filter_query: "name=='{{ name }}'" + - assert: + that: + - host_info.objects | length == 1 + - host_info.objects[0].id == host.id + + - name: Get Host information by tag filters + infoblox.bloxone.ipam_host_info: + tag_filters: + tag1: "{ { tag1_value } }" + tag2: "{ { tag2_value } }" + - assert: + that: + - host_info.objects | length == 1 + - host_info.objects[0].id == host.id + + always: + # Cleanup if the test fails + - name: "Delete IPAM Host" + infoblox.bloxone.ipam_host: + name: "{{ name }}" + state: "absent" + ignore_errors: true