From c7fccd10e1f69dfffeda1ca0e1a3e514d7180842 Mon Sep 17 00:00:00 2001 From: Vishruti Buddhadev Date: Thu, 9 Jan 2025 23:43:49 +0530 Subject: [PATCH] IPAM Host Support for Bloxone Ansible v2 (#52) * generated resources for ipam_host and added examples * written integration tests for ipam_host * deprecate b1_ipam_host and b1_ipam_host_gather * fixed sanity issues and added empty line at the end * removed instances of inherit * fixed documentation and added empty line at the end of file * refactored integration tests * addressed review comments * fixed sanity issues * modified examples * fixed sanity issues * addressed review comments * added new line at the end * modified examples * modified examples * added code for cleanup * addressed review comments * added new line * addressed review comments * addressed review comments --------- Co-authored-by: Ashish Mathew <54074687+mathewab@users.noreply.github.com> --- changelogs/fragments/52-ipam-host.yml | 3 + meta/runtime.yml | 11 + plugins/modules/b1_ipam_host.py | 4 + plugins/modules/b1_ipam_host_gather.py | 6 +- plugins/modules/ipam_host.py | 397 ++++++++++++++++++ plugins/modules/ipam_host_info.py | 269 ++++++++++++ .../targets/ipam_host/meta/main.yml | 2 + .../targets/ipam_host/tasks/main.yml | 208 +++++++++ .../targets/ipam_host_info/tasks/main.yml | 59 +++ .../targets/setup_auth_zone/tasks/cleanup.yml | 11 + .../targets/setup_ip_space/tasks/cleanup.yml | 11 + .../targets/setup_ip_space/tasks/main.yml | 15 + .../targets/setup_subnet/meta/main.yml | 2 + .../targets/setup_subnet/tasks/main.yml | 16 + .../targets/setup_view/tasks/cleanup.yml | 11 + .../targets/setup_view/tasks/main.yml | 4 +- 16 files changed, 1026 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/52-ipam-host.yml create mode 100644 plugins/modules/ipam_host.py create mode 100644 plugins/modules/ipam_host_info.py create mode 100644 tests/integration/targets/ipam_host/meta/main.yml create mode 100644 tests/integration/targets/ipam_host/tasks/main.yml create mode 100644 tests/integration/targets/ipam_host_info/tasks/main.yml create mode 100644 tests/integration/targets/setup_auth_zone/tasks/cleanup.yml create mode 100644 tests/integration/targets/setup_ip_space/tasks/cleanup.yml create mode 100644 tests/integration/targets/setup_ip_space/tasks/main.yml create mode 100644 tests/integration/targets/setup_subnet/meta/main.yml create mode 100644 tests/integration/targets/setup_subnet/tasks/main.yml create mode 100644 tests/integration/targets/setup_view/tasks/cleanup.yml diff --git a/changelogs/fragments/52-ipam-host.yml b/changelogs/fragments/52-ipam-host.yml new file mode 100644 index 00000000..3388f325 --- /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 953687e3..08ee4251 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -24,6 +24,8 @@ action_groups: - ipam_subnet_info - ipam_address_block - ipam_address_block_info + - ipam_host + - ipam_host_info infra: - infra_join_token @@ -75,3 +77,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 540304c0..7721c6a2 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 be5c4842..37f04287 100644 --- a/plugins/modules/b1_ipam_host_gather.py +++ b/plugins/modules/b1_ipam_host_gather.py @@ -7,9 +7,13 @@ DOCUMENTATION = """ --- -module: b1_ipam_ip_space +module: b1_ipam_host 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 00000000..02db3c0d --- /dev/null +++ b/plugins/modules/ipam_host.py @@ -0,0 +1,397 @@ +#!/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 IPAM host +description: + - Manage IPAM host +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 an IP space (required as parent)" + infoblox.bloxone.ipam_ip_space: + name: "example_ip_space" + state: "present" + + - name: "Create a Subnet (required as parent)" + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ ip_space.id }}" + state: "present" + + - name: "Create a Host" + infoblox.bloxone.ipam_host: + name: "example_host" + state: "present" + + - name: "Create a Host with Additional Fields" + infoblox.bloxone.ipam_host: + name: "example_host" + addresses: + - address: "10.0.0.1" + space: "{{ ip_space.id }}" + comment: "IPAM Host" + tags: + location: "site-1" + state: "present" + + - name: "Delete a host" + infoblox.bloxone.ipam_host: + name: "example_host" + state: "absent" +""" + +RETURN = r""" +id: + description: + - ID of the IPAM host object + type: str + returned: Always +item: + description: + - IPAM host 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 00000000..01c32aca --- /dev/null +++ b/plugins/modules/ipam_host_info.py @@ -0,0 +1,269 @@ +#!/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: Get Host information by ID + infoblox.bloxone.ipam_host_info: + id: "{{ host.id }}" + + - name: Get Host information by filters + infoblox.bloxone.ipam_host_info: + filters: + name: "example_host" + + - name: Get Host information by filter query + infoblox.bloxone.ipam_host_info: + filter_query: "name=='example_host'" + + - name: Get Host information by tag filters + infoblox.bloxone.ipam_host_info: + tag_filters: + location: "site-1" +""" + +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/meta/main.yml b/tests/integration/targets/ipam_host/meta/main.yml new file mode 100644 index 00000000..8ef5a4c8 --- /dev/null +++ b/tests/integration/targets/ipam_host/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [setup_ip_space, setup_subnet, setup_view, setup_auth_zone] 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 00000000..b4e1a96a --- /dev/null +++ b/tests/integration/targets/ipam_host/tasks/main.yml @@ -0,0 +1,208 @@ +--- +#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 }}" + tag_value: "site-{{ 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: 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: 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 auto generate records set to true + infoblox.bloxone.ipam_host: + name: "{{ name }}" + auto_generate_records: true + host_names: + - name: "example-host" + zone: "{{ _auth_zone.id }}" + 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].name == name + - host_info.objects[0].auto_generate_records == true + - host_info.objects[0].host_names[0].name == "example-host" + - host_info.objects[0].addresses[0].address == "10.0.0.1" + + - 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: + region: "{{ tag_value }}" + 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.region == tag_value + + - 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 + + - name: "Delete an Auth Zone" + ansible.builtin.include_role: + name: setup_auth_zone + tasks_from: cleanup.yml + + - name: "Delete DNS View" + ansible.builtin.include_role: + name: setup_view + tasks_from: cleanup.yml + + - name: "Delete IP Space" + ansible.builtin.include_role: + name: setup_ip_space + tasks_from: cleanup.yml 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 00000000..6533abfe --- /dev/null +++ b/tests/integration/targets/ipam_host_info/tasks/main.yml @@ -0,0 +1,59 @@ +--- +- 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 }}" + tag_value: "site-{{ 999999 | random | string }}" + + - name: Create a Host + infoblox.bloxone.ipam_host: + name: "{{ name }}" + tags: + region: "{{ tag_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: + region: "{{ tag_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 diff --git a/tests/integration/targets/setup_auth_zone/tasks/cleanup.yml b/tests/integration/targets/setup_auth_zone/tasks/cleanup.yml new file mode 100644 index 00000000..9cf77cbb --- /dev/null +++ b/tests/integration/targets/setup_auth_zone/tasks/cleanup.yml @@ -0,0 +1,11 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + - name: "Delete an Auth Zone" + infoblox.bloxone.dns_auth_zone: + fqdn: "{{ _fqdn_auth_zone }}" + state: "absent" + ignore_errors: true diff --git a/tests/integration/targets/setup_ip_space/tasks/cleanup.yml b/tests/integration/targets/setup_ip_space/tasks/cleanup.yml new file mode 100644 index 00000000..e9706626 --- /dev/null +++ b/tests/integration/targets/setup_ip_space/tasks/cleanup.yml @@ -0,0 +1,11 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + - name: "Delete IP space" + infoblox.bloxone.ipam_ip_space: + name: "{{ ip_space_name }}" + state: "absent" + ignore_errors: true diff --git a/tests/integration/targets/setup_ip_space/tasks/main.yml b/tests/integration/targets/setup_ip_space/tasks/main.yml new file mode 100644 index 00000000..ebc8497d --- /dev/null +++ b/tests/integration/targets/setup_ip_space/tasks/main.yml @@ -0,0 +1,15 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + # Create a random IP space name to avoid conflicts + - ansible.builtin.set_fact: + ip_space_name: "ip-space-{{ 999999 | random | string }}" + + - name: "Create an IP space" + infoblox.bloxone.ipam_ip_space: + name: "{{ ip_space_name }}" + state: "present" + register: _ip_space diff --git a/tests/integration/targets/setup_subnet/meta/main.yml b/tests/integration/targets/setup_subnet/meta/main.yml new file mode 100644 index 00000000..32c7cbc6 --- /dev/null +++ b/tests/integration/targets/setup_subnet/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [setup_ip_space] diff --git a/tests/integration/targets/setup_subnet/tasks/main.yml b/tests/integration/targets/setup_subnet/tasks/main.yml new file mode 100644 index 00000000..02c2c375 --- /dev/null +++ b/tests/integration/targets/setup_subnet/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + # Create a random Subnet name to avoid conflicts + - ansible.builtin.set_fact: + name: "subnet-{{ 999999 | random | string }}" + + - name: "Create a Subnet" + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ _ip_space.id }}" + state: "present" + register: _subnet diff --git a/tests/integration/targets/setup_view/tasks/cleanup.yml b/tests/integration/targets/setup_view/tasks/cleanup.yml new file mode 100644 index 00000000..383dd675 --- /dev/null +++ b/tests/integration/targets/setup_view/tasks/cleanup.yml @@ -0,0 +1,11 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + - name: "Delete DNS View" + infoblox.bloxone.dns_view: + name: "{{ dns_view_name }}" + state: absent + ignore_errors: true diff --git a/tests/integration/targets/setup_view/tasks/main.yml b/tests/integration/targets/setup_view/tasks/main.yml index ca18609e..c440af8d 100644 --- a/tests/integration/targets/setup_view/tasks/main.yml +++ b/tests/integration/targets/setup_view/tasks/main.yml @@ -6,10 +6,10 @@ block: # Create a random View name to avoid conflicts - ansible.builtin.set_fact: - name: "test-view-{{ 999999 | random | string }}" + dns_view_name: "test-view-{{ 999999 | random | string }}" - name: Create a View infoblox.bloxone.dns_view: - name: "{{ name }}" + name: "{{ dns_view_name }}" state: present register: _view \ No newline at end of file