diff --git a/meta/runtime.yml b/meta/runtime.yml index ec38fe3e..065089f2 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -38,6 +38,10 @@ action_groups: infra: - infra_join_token - infra_join_token_info + - infra_host + - infra_host_info + - infra_service + - infra_service_info plugin_routing: modules: diff --git a/plugins/modules/infra_host.py b/plugins/modules/infra_host.py new file mode 100644 index 00000000..f1f794ef --- /dev/null +++ b/plugins/modules/infra_host.py @@ -0,0 +1,432 @@ +#!/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: infra_host +short_description: Manage Infrastructure Hosts +description: + - Manage Infrastructure Hosts +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 + description: + description: + - "The description of the Host (optional)." + type: str + display_name: + description: + - "The name of the Host (unique)." + type: str + ip_space: + description: + - "The IP Space of the Host." + type: str + location_id: + description: + - "The resource identifier." + type: str + maintenance_mode: + description: "The flag to indicate if the Host is in maintenance mode." + choices: + - enabled + - disabled + type: str + pool_id: + description: + - "The resource identifier." + type: str + serial_number: + description: + - "The unique serial number of the Host." + type: str + tags: + description: + - "Tags associated with this Host." + type: dict + +extends_documentation_fragment: + - infoblox.bloxone.common +""" # noqa: E501 + +EXAMPLES = r""" + - name: Create a host + infoblox.bloxone.infra_host: + display_name: "example_host" + state: "present" + + - name: Create a host with Additional Fields + infoblox.bloxone.infra_host: + name: "example_host" + description: "Example Infra Host" + maintenance_mode: enabled + state: "present" + tags: + location: "my-location" + + - name: Delete the host + infoblox.bloxone.infra_host: + display_name: "example_host" + state: "absent" +""" + +RETURN = r""" +id: + description: + - ID of the Hosts object + type: str + returned: Always +item: + description: + - Hosts object + type: complex + returned: Always + contains: + configs: + description: + - "The list of Host-specific configurations for each Service deployed on this Host." + type: list + returned: Always + elements: dict + contains: + current_version: + description: + - "The current version of the Service deployed on the Host." + type: str + returned: Always + extra_data: + description: + - "The field to carry any extra data specific to this configuration." + type: str + returned: Always + host_id: + description: + - "The resource identifier." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + service_id: + description: + - "The resource identifier." + type: str + returned: Always + service_type: + description: + - "The type of the Service deployed on the Host (C(\"dns\"), C(\"cdc\"), etc.)." + type: str + returned: Always + upgraded_at: + description: + - "The timestamp of the latest upgrade of the Host-specific Service configuration." + type: str + returned: Always + connectivity_monitor: + description: + - "Represents the connectivity monitor properties of a Host, to enable/disable connectivity monitoring for redundant network interfaces." + - "The \"endpoint_type\" is:" + - "- C(\"\"csp\"\") for enabling monitoring" + - "- C(\"\"\"\") for disabling monitoring (default)" + - "Note: Currently, all fields except \"endpoint_type\" are read-only, and will be overridden to default values in case they are edited." + - "Example:" + - "{" + - "\"connectivity_monitor\": {" + - "\"cost\":1000000," + - "\"endpoint_type\":\"csp\"," + - "\"endpoint\":\"http://csp.infoblox.com\"," + - "\"interval\":15," + - "\"failure_threshold\":1," + - "\"success_threshold\":2" + - "}" + - "}" + type: dict + returned: Always + created_at: + description: + - "The timestamp of creation of Host." + type: str + returned: Always + created_by: + description: + - "The creator of the Host (internal use only)." + type: str + returned: Always + description: + description: + - "The description of the Host (optional)." + type: str + returned: Always + display_name: + description: + - "The name of the Host (unique)." + type: str + returned: Always + host_subtype: + description: + - "The sub-type of a specific Host type." + - "Example: For Host type BloxOne Appliance, sub-type could be \"B105\" or \"VEP1425\"" + type: str + returned: Always + host_type: + description: + - "The type of Host." + - "Should be one of: 1. NIOS , 2. NIOS HA, 3. BloxOne VM , 4. BloxOne Appliance, 5. BloxOne Container, 6. CNIOS" + type: str + returned: Always + host_version: + description: + - "The version of the Host platform services." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + ip_address: + description: + - "The IP address of the Host." + type: str + returned: Always + ip_space: + description: + - "The IP Space of the Host." + type: str + returned: Always + legacy_id: + description: + - "The legacy Host object identifier." + type: str + returned: Always + location_id: + description: + - "The resource identifier." + type: str + returned: Always + mac_address: + description: + - "The MAC address of the Host." + type: str + returned: Always + maintenance_mode: + description: "The flag to indicate if the Host is in maintenance mode." + type: str + returned: Always + nat_ip: + description: + - "The NAT IP address of the Host." + type: str + returned: Always + noa_cluster: + description: + - "The CSP cluster identifier (internal use only)." + type: str + returned: Always + ophid: + description: + - "The unique On-Prem Host ID generated by the On-Prem device and assigned to the Host once it is registered and logged into the Infoblox Cloud." + type: str + returned: Always + pool_id: + description: + - "The resource identifier." + type: str + returned: Always + serial_number: + description: + - "The unique serial number of the Host." + type: str + returned: Always + tags: + description: + - "Tags associated with this Host." + type: dict + returned: Always + timezone: + description: + - "The timezone of the Host." + type: str + returned: Always + updated_at: + description: + - "The timestamp of the latest update on Host." + 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 infra_mgmt import Host, HostsApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class InfraHostModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(InfraHostModule, 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 = Host.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 = HostsApi(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"display_name=='{self.params['display_name']}'" + resp = HostsApi(self.client).list(filter=filter) + + # If no results, set results to empty list + if not resp.results: + resp.results = [] + + if len(resp.results) == 1: + return resp.results[0] + if len(resp.results) > 1: + self.fail_json(msg=f"Found multiple Hosts: {resp.results}") + if len(resp.results) == 0: + return None + + def create(self): + if self.check_mode: + return None + + resp = HostsApi(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 + + updated_body = self.payload + updated_body.pool_id = self.existing.pool_id + + resp = HostsApi(self.client).update(id=self.existing.id, body=updated_body) + return resp.result.model_dump(by_alias=True, exclude_none=True) + + def delete(self): + if self.check_mode: + return + + HostsApi(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"] = "Hosts created" + elif self.params["state"] == "present" and self.existing is not None: + if self.payload_changed(): + item = self.update() + result["changed"] = True + result["msg"] = "Hosts updated" + elif self.params["state"] == "absent" and self.existing is not None: + self.delete() + result["changed"] = True + result["msg"] = "Hosts 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"), + description=dict(type="str"), + display_name=dict(type="str"), + ip_space=dict(type="str"), + location_id=dict(type="str"), + maintenance_mode=dict(type="str", choices=["enabled", "disabled"]), + pool_id=dict(type="str"), + serial_number=dict(type="str"), + tags=dict(type="dict"), + ) + + module = InfraHostModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[("state", "present", ["display_name"])], + ) + + module.run_command() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/infra_host_info.py b/plugins/modules/infra_host_info.py new file mode 100644 index 00000000..f0fe92e4 --- /dev/null +++ b/plugins/modules/infra_host_info.py @@ -0,0 +1,366 @@ +#!/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: infra_host_info +short_description: Retrieve Infrastructure Hosts +description: + - Retrieve Infrastructure Hosts +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.infra_host_info: + id: "{{ host_id }}" + + - name: Get Host information by filters (e.g. display name) + infoblox.bloxone.infra_host_info: + filters: + display_name: "example_host" + + - name: Get Host information by raw filter query + infoblox.bloxone.infra_host_info: + filter_query: "display_name=='example_host'" + + - name: Get Host information by tag filters + infoblox.bloxone.infra_host_info: + tag_filters: + location: "site-1" + + - name: Get Host Information with retries + infoblox.bloxone.infra_host_info: + filters: + display_name: "example_host" + timeout: 10 + retries: 5 + delay: 1 + until: "infra_host_info.objects | length == 1" +""" + +RETURN = r""" +id: + description: + - ID of the Hosts object + type: str + returned: Always +objects: + description: + - Hosts object + type: list + elements: dict + returned: Always + contains: + configs: + description: + - "The list of Host-specific configurations for each Service deployed on this Host." + type: list + returned: Always + elements: dict + contains: + current_version: + description: + - "The current version of the Service deployed on the Host." + type: str + returned: Always + extra_data: + description: + - "The field to carry any extra data specific to this configuration." + type: str + returned: Always + host_id: + description: + - "The resource identifier." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + service_id: + description: + - "The resource identifier." + type: str + returned: Always + service_type: + description: + - "The type of the Service deployed on the Host (C(\"dns\"), C(\"cdc\"), etc.)." + type: str + returned: Always + upgraded_at: + description: + - "The timestamp of the latest upgrade of the Host-specific Service configuration." + type: str + returned: Always + connectivity_monitor: + description: + - "Represents the connectivity monitor properties of a Host, to enable/disable connectivity monitoring for redundant network interfaces." + - "The \"endpoint_type\" is:" + - "- C(\"\"csp\"\") for enabling monitoring" + - "- C(\"\"\"\") for disabling monitoring (default)" + - "Note: Currently, all fields except \"endpoint_type\" are read-only, and will be overridden to default values in case they are edited." + - "Example:" + - "{" + - "\"connectivity_monitor\": {" + - "\"cost\":1000000," + - "\"endpoint_type\":\"csp\"," + - "\"endpoint\":\"http://csp.infoblox.com\"," + - "\"interval\":15," + - "\"failure_threshold\":1," + - "\"success_threshold\":2" + - "}" + - "}" + type: dict + returned: Always + created_at: + description: + - "The timestamp of creation of Host." + type: str + returned: Always + created_by: + description: + - "The creator of the Host (internal use only)." + type: str + returned: Always + description: + description: + - "The description of the Host (optional)." + type: str + returned: Always + display_name: + description: + - "The name of the Host (unique)." + type: str + returned: Always + host_subtype: + description: + - "The sub-type of a specific Host type." + - "Example: For Host type BloxOne Appliance, sub-type could be \"B105\" or \"VEP1425\"" + type: str + returned: Always + host_type: + description: + - "The type of Host." + - "Should be one of: 1. NIOS , 2. NIOS HA, 3. BloxOne VM , 4. BloxOne Appliance, 5. BloxOne Container, 6. CNIOS" + type: str + returned: Always + host_version: + description: + - "The version of the Host platform services." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + ip_address: + description: + - "The IP address of the Host." + type: str + returned: Always + ip_space: + description: + - "The IP Space of the Host." + type: str + returned: Always + legacy_id: + description: + - "The legacy Host object identifier." + type: str + returned: Always + location_id: + description: + - "The resource identifier." + type: str + returned: Always + mac_address: + description: + - "The MAC address of the Host." + type: str + returned: Always + maintenance_mode: + description: "" + type: str + returned: Always + nat_ip: + description: + - "The NAT IP address of the Host." + type: str + returned: Always + noa_cluster: + description: + - "The CSP cluster identifier (internal use only)." + type: str + returned: Always + ophid: + description: + - "The unique On-Prem Host ID generated by the On-Prem device and assigned to the Host once it is registered and logged into the Infoblox Cloud." + type: str + returned: Always + pool_id: + description: + - "The resource identifier." + type: str + returned: Always + serial_number: + description: + - "The unique serial number of the Host." + type: str + returned: Always + tags: + description: + - "Tags associated with this Host." + type: dict + returned: Always + timezone: + description: + - "The timezone of the Host." + type: str + returned: Always + updated_at: + description: + - "The timestamp of the latest update on Host." + 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 infra_mgmt import HostsApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class HostsInfoModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(HostsInfoModule, self).__init__(*args, **kwargs) + self._existing = None + self._limit = 1000 + + def find_by_id(self): + try: + resp = HostsApi(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 = HostsApi(self.client).list( + offset=offset, limit=self._limit, filter=filter_str, tfilter=tag_filter_str + ) + + # If no results, set results to empty list + if not resp.results: + resp.results = [] + + 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 = HostsInfoModule( + 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/plugins/modules/infra_service.py b/plugins/modules/infra_service.py new file mode 100644 index 00000000..75a1c739 --- /dev/null +++ b/plugins/modules/infra_service.py @@ -0,0 +1,374 @@ +#!/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: infra_service +short_description: Manage Infrastructure Services +description: + - Manage Infrastructure Services +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 + description: + description: + - "The description of the Service (optional)." + type: str + desired_state: + description: + - "The desired state of the Service." + type: str + choices: + - start + - stop + default: stop + desired_version: + description: + - "The desired version of the Service." + type: str + interface_labels: + description: + - "List of interfaces on which this Service can operate. Note: The list can contain custom interface labels (Example: C(\"[\"WAN\",\"LAN\",\"label1\",\"label2\"]\"))" + type: list + elements: str + name: + description: + - "The name of the Service (unique)." + type: str + pool_id: + description: + - "The resource identifier." + type: str + service_type: + description: + - "The type of the Service deployed on the Host (C(\"dns\"), C(\"cdc\"), etc.)." + type: str + tags: + description: + - "Tags associated with this Service." + type: dict + +extends_documentation_fragment: + - infoblox.bloxone.common +""" # noqa: E501 + +EXAMPLES = r""" + - name: Create a host + infoblox.bloxone.infra_host: + display_name: "example_host" + state: "present" + register: infra_host + + - name: Create a service + infoblox.bloxone.infra_service: + name: "example_service" + service_type: "dns" + pool_id: "{{ infra_host.object.pool_id }}" + state: "present" + + - name: Create a Service with Additional Fields + infoblox.bloxone.infra_service: + name: "example_service" + description: "Example Infra Service" + service_type: "dns" + pool_id: " {{ infra_host.object.pool_id }}" + desired_version: "3.4.0" + desired_state: "start" + interface_labels: + - "WAN" + - "label1" + state: "present" + tags: + location: "site-1" + + - name: Delete the Service + infoblox.bloxone.infra_service: + name: "example_service" + service_type: "dns" + pool_id: "{{ infra_host.object.pool_id }}" + state: "absent" +""" + +RETURN = r""" +id: + description: + - ID of the Services object + type: str + returned: Always +item: + description: + - Services object + type: complex + returned: Always + contains: + configs: + description: + - "List of Host-specific configurations of this Service." + type: list + returned: Always + elements: dict + contains: + current_version: + description: + - "The current version of the Service deployed on the Host." + type: str + returned: Always + extra_data: + description: + - "The field to carry any extra data specific to this configuration." + type: str + returned: Always + host_id: + description: + - "The resource identifier." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + service_id: + description: + - "The resource identifier." + type: str + returned: Always + service_type: + description: + - "The type of the Service deployed on the Host (C(\"dns\"), C(\"cdc\"), etc.)." + type: str + returned: Always + upgraded_at: + description: + - "The timestamp of the latest upgrade of the Host-specific Service configuration." + type: str + returned: Always + created_at: + description: + - "Timestamp of creation of Service." + type: str + returned: Always + description: + description: + - "The description of the Service (optional)." + type: str + returned: Always + desired_state: + description: + - "The desired state of the Service. Should either be C(\"\"start\"\") or C(\"\"stop\"\")." + type: str + returned: Always + desired_version: + description: + - "The desired version of the Service." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + interface_labels: + description: + - "List of interfaces on which this Service can operate. Note: The list can contain custom interface labels (Example: C(\"[\"WAN\",\"LAN\",\"label1\",\"label2\"]\"))" + type: list + returned: Always + name: + description: + - "The name of the Service (unique)." + type: str + returned: Always + pool_id: + description: + - "The resource identifier." + type: str + returned: Always + service_type: + description: + - "The type of the Service deployed on the Host (C(\"dns\"), C(\"cdc\"), etc.)." + type: str + returned: Always + tags: + description: + - "Tags associated with this Service." + type: dict + returned: Always + updated_at: + description: + - "Timestamp of the latest update on Service." + 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 infra_mgmt import Service, ServicesApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class InfraServiceModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(InfraServiceModule, 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 = Service.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 = ServicesApi(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 = ServicesApi(self.client).list(filter=filter) + + # If no results, set results to empty list + if not resp.results: + resp.results = [] + + if len(resp.results) == 1: + return resp.results[0] + if len(resp.results) > 1: + self.fail_json(msg=f"Found multiple Services: {resp.results}") + if len(resp.results) == 0: + return None + + def create(self): + if self.check_mode: + return None + + resp = ServicesApi(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 + + update_body = self.payload + + resp = ServicesApi(self.client).update(id=self.existing.id, body=update_body) + return resp.result.model_dump(by_alias=True, exclude_none=True) + + def delete(self): + if self.check_mode: + return + + ServicesApi(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"] = "Services created" + elif self.params["state"] == "present" and self.existing is not None: + if self.payload_changed(): + item = self.update() + result["changed"] = True + result["msg"] = "Services updated" + elif self.params["state"] == "absent" and self.existing is not None: + self.delete() + result["changed"] = True + result["msg"] = "Services 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"), + description=dict(type="str"), + desired_state=dict(type="str", choices=["start", "stop"], default="stop"), + desired_version=dict(type="str"), + interface_labels=dict(type="list", elements="str"), + name=dict(type="str"), + pool_id=dict(type="str"), + service_type=dict(type="str"), + tags=dict(type="dict"), + ) + + module = InfraServiceModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[("state", "present", ["name", "pool_id", "service_type"])], + ) + + module.run_command() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/infra_service_info.py b/plugins/modules/infra_service_info.py new file mode 100644 index 00000000..282d4890 --- /dev/null +++ b/plugins/modules/infra_service_info.py @@ -0,0 +1,281 @@ +#!/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: infra_service_info +short_description: Retrieve Infrastructure Services +description: + - Retrieve Infrastructure Services +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 Service Information by ID + infoblox.bloxone.infra_service_info: + id: "{{ service.id }}" + + - name: Get Service information by filters (Display Name) + infoblox.bloxone.infra_service_info: + filters: + service_name: "example_service" + + - name: Get Service information by filter query + infoblox.bloxone.infra_service_info: + filter_query: "service_name=='example_service'" + + - name: Get Service information by tag filters + infoblox.bloxone.infra_service_info: + tag_filters: + location: "site-1" +""" + +RETURN = r""" +id: + description: + - ID of the Services object + type: str + returned: Always +objects: + description: + - Services object + type: list + elements: dict + returned: Always + contains: + configs: + description: + - "List of Host-specific configurations of this Service." + type: list + returned: Always + elements: dict + contains: + current_version: + description: + - "The current version of the Service deployed on the Host." + type: str + returned: Always + extra_data: + description: + - "The field to carry any extra data specific to this configuration." + type: str + returned: Always + host_id: + description: + - "The resource identifier." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + service_id: + description: + - "The resource identifier." + type: str + returned: Always + service_type: + description: + - "The type of the Service deployed on the Host (C(\"dns\"), C(\"cdc\"), etc.)." + type: str + returned: Always + upgraded_at: + description: + - "The timestamp of the latest upgrade of the Host-specific Service configuration." + type: str + returned: Always + created_at: + description: + - "Timestamp of creation of Service." + type: str + returned: Always + description: + description: + - "The description of the Service (optional)." + type: str + returned: Always + desired_state: + description: + - "The desired state of the Service. Should either be C(\"\"start\"\") or C(\"\"stop\"\")." + type: str + returned: Always + desired_version: + description: + - "The desired version of the Service." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + interface_labels: + description: + - "List of interfaces on which this Service can operate. Note: The list can contain custom interface labels (Example: C(\"[\"WAN\",\"LAN\",\"label1\",\"label2\"]\"))" + type: list + returned: Always + name: + description: + - "The name of the Service (unique)." + type: str + returned: Always + pool_id: + description: + - "The resource identifier." + type: str + returned: Always + service_type: + description: + - "The type of the Service deployed on the Host (C(\"dns\"), C(\"cdc\"), etc.)." + type: str + returned: Always + tags: + description: + - "Tags associated with this Service." + type: dict + returned: Always + updated_at: + description: + - "Timestamp of the latest update on Service." + 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 infra_mgmt import ServicesApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class ServicesInfoModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(ServicesInfoModule, self).__init__(*args, **kwargs) + self._existing = None + self._limit = 1000 + + def find_by_id(self): + try: + resp = ServicesApi(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 = ServicesApi(self.client).list( + offset=offset, limit=self._limit, filter=filter_str, tfilter=tag_filter_str + ) + + # If no results, set results to empty list + if not resp.results: + resp.results = [] + + 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 = ServicesInfoModule( + 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/infra_host/meta/main.yml b/tests/integration/targets/infra_host/meta/main.yml new file mode 100644 index 00000000..32c7cbc6 --- /dev/null +++ b/tests/integration/targets/infra_host/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [setup_ip_space] diff --git a/tests/integration/targets/infra_host/tasks/main.yml b/tests/integration/targets/infra_host/tasks/main.yml new file mode 100644 index 00000000..72acfe08 --- /dev/null +++ b/tests/integration/targets/infra_host/tasks/main.yml @@ -0,0 +1,200 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + # Create a random Host name to avoid conflicts + - ansible.builtin.set_fact: + display_name: "test-infra-host-{{ 999999 | random | string }}" + serial_number: "serial{{ 999999 | random | string }}" + + - name: Create a Host (check mode) + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + state: present + check_mode: true + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 0 + + - name: Create a Host + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + state: present + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 1 + + - name: Create a Host (idempotent) + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + state: present + register: infra_host + - assert: + that: + - infra_host is not changed + - infra_host is not failed + + - name: Delete the Host (check mode) + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + state: absent + check_mode: true + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 1 + + - name: Delete the Host + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + state: absent + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 0 + + - name: Delete the Host (idempotent) + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + state: absent + register: infra_host + - assert: + that: + - infra_host is not changed + - infra_host is not failed + + - name: Create a Host with Description + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + description: "Test Host" + state: present + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].description == "Test Host" + + - name: Create a Host with Maintenance Mode enabled + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + maintenance_mode: enabled + state: present + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].maintenance_mode == "enabled" + + - name: Create a Host with an IP Space + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + ip_space: "{{ _ip_space.id }}" + state: present + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].ip_space == infra_host.object.ip_space + + - name: Create a Host with a Serial Number + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + serial_number: "{{ serial_number }}" + state: present + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].serial_number == infra_host.object.serial_number + + - name: Create a Host with Tags + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + tags: + location: "site-1" + state: present + register: infra_host + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host is changed + - infra_host_info is not failed + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].tags.location == "site-1" + + always: + # Cleanup if the test fails + - name: "Delete Host" + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + state: "absent" + ignore_errors: true + + - name: "Delete IP Space" + ansible.builtin.include_role: + name: setup_ip_space + tasks_from: cleanup.yml diff --git a/tests/integration/targets/infra_host_info/tasks/main.yml b/tests/integration/targets/infra_host_info/tasks/main.yml new file mode 100644 index 00000000..af4ddbc3 --- /dev/null +++ b/tests/integration/targets/infra_host_info/tasks/main.yml @@ -0,0 +1,80 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + # Create a random Host name to avoid conflicts + - ansible.builtin.set_fact: + display_name: "test-infra-host-{{ 999999 | random | string }}" + tag_value: "site-{{ 999999 | random | string }}" + + # Basic Test for Host + - name: Create a Host + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + tags: + location: "{{ tag_value }}" + state: present + register: infra_host + + - name: Get Information about the Host + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].display_name == infra_host.object.display_name + + - name: Get Infra Host information by filters (Display Name) + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}" + register: infra_host_info + - assert: + that: + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].id == infra_host.id + + - name: Get Host information by filter query + infoblox.bloxone.infra_host_info: + filter_query: "display_name=='{{ display_name }}'" + - assert: + that: + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].id == infra_host.id + + - name: Get Host information by tag filters + infoblox.bloxone.infra_host_info: + tag_filters: + location: "{{ tag_value }}" + - assert: + that: + - infra_host_info.objects | length == 1 + - infra_host_info.objects[0].id == infra_host.id + + # The following test will not retrieve any result as the display_name is incorrect + # This test demonstrates to retry the call maximum 5 times with a delay of 1 second between each call + - name: Get Information about the Host by using Retry if not found and Timeout + infoblox.bloxone.infra_host_info: + filters: + display_name: "{{ display_name }}+incorrect_name" + timeout: "10" # Timeout for each call is set to 10 seconds + retries: 5 # Retry 5 times + delay: 1 # Delay between each retry is set to 1 second + until: "infra_host_info.objects | length == 1" + register: infra_host_info + ignore_errors: true + - assert: + that: + - infra_host_info.objects | length == 0 + + always: + # Cleanup if the test fails + - name: "Delete Host" + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + state: "absent" + ignore_errors: true diff --git a/tests/integration/targets/infra_service/meta/main.yml b/tests/integration/targets/infra_service/meta/main.yml new file mode 100644 index 00000000..d4648ee0 --- /dev/null +++ b/tests/integration/targets/infra_service/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [setup_infra_host] diff --git a/tests/integration/targets/infra_service/tasks/main.yml b/tests/integration/targets/infra_service/tasks/main.yml new file mode 100644 index 00000000..e0d5b45d --- /dev/null +++ b/tests/integration/targets/infra_service/tasks/main.yml @@ -0,0 +1,225 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + # Create a random Service name to avoid conflicts + - ansible.builtin.set_fact: + service_name: "test-infra-service-{{ 999999 | random | string }}" + + - name: Create a Service (check mode) + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dhcp" + state: present + check_mode: true + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 0 + + - name: Create a Service + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dhcp" + state: present + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 1 + + - name: Create a Service (idempotent) + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dhcp" + state: present + register: infra_service + - assert: + that: + - infra_service is not changed + - infra_service is not failed + + - name: Delete the Service (check mode) + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dhcp" + state: absent + check_mode: true + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 1 + + - name: Delete the Service + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dhcp" + state: absent + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 0 + + - name: Delete the Service (idempotent) + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dhcp" + state: absent + register: infra_service + - assert: + that: + - infra_service is not changed + - infra_service is not failed + + - name: Create a Service with Description + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dns" + description: "Test Service" + state: present + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 1 + - infra_service_info.objects[0].description == "Test Service" + + - name: Create a Service with Desired State + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dns" + desired_state: "start" + state: present + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 1 + + - name: Create a Service with Desired Version + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dns" + desired_version: "3.4.0" + state: present + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 1 + + - name: Create a Service with Interface Labels + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dns" + interface_labels: + - "WAN" + - "LAN" + - "label1" + - "label2" + state: present + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 1 + + + - name: Create a Service with Tags + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dns" + tags: + location: "site-1" + state: present + register: infra_service + - name: Get Information about the Service + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service is changed + - infra_service_info is not failed + - infra_service_info.objects | length == 1 + - infra_service_info.objects[0].tags.location == "site-1" + + always: + # Cleanup if the test fails + - name: "Delete the Service" + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dns" + state: "absent" + ignore_errors: true + + - name: "Delete the Host" + ansible.builtin.include_role: + name: setup_infra_host + tasks_from: cleanup.yml diff --git a/tests/integration/targets/infra_service_info/meta/main.yml b/tests/integration/targets/infra_service_info/meta/main.yml new file mode 100644 index 00000000..d4648ee0 --- /dev/null +++ b/tests/integration/targets/infra_service_info/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [setup_infra_host] diff --git a/tests/integration/targets/infra_service_info/tasks/main.yml b/tests/integration/targets/infra_service_info/tasks/main.yml new file mode 100644 index 00000000..86e99560 --- /dev/null +++ b/tests/integration/targets/infra_service_info/tasks/main.yml @@ -0,0 +1,66 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + # Create a random Service name to avoid conflicts + - ansible.builtin.set_fact: + service_name: "test-infra-service-{{ 999999 | random | string }}" + tag_value: "site-{{ 999999 | random | string }}" + + - name: Create a Service + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dns" + tags: + location: "{{ tag_value }}" + state: present + register: infra_service + + - name: Get Information about the Service by Id + infoblox.bloxone.infra_service_info: + id: "{{ infra_service.id }}" + register: infra_service_info + - assert: + that: + - infra_service_info.objects | length == 1 + - infra_service_info.objects[0].name == infra_service.object.name + + - name: Get Service information by filters (Display Name) + infoblox.bloxone.infra_service_info: + filters: + name: "{{ service_name }}" + register: infra_service_info + - assert: + that: + - infra_service_info.objects | length == 1 + - infra_service_info.objects[0].id == infra_service.id + + - name: Get Service information by filter query + infoblox.bloxone.infra_service_info: + filter_query: "name=='{{ service_name }}'" + - assert: + that: + - infra_service_info.objects | length == 1 + - infra_service_info.objects[0].id == infra_service.id + + - name: Get Service information by tag filters + infoblox.bloxone.infra_service_info: + tag_filters: + location: "{{ tag_value }}" + - assert: + that: + - infra_service_info.objects | length == 1 + - infra_service_info.objects[0].id == infra_service.id + + always: + # Cleanup if the test fails + - name: "Delete Service" + infoblox.bloxone.infra_service: + name: "{{ service_name }}" + pool_id: "{{ _infra_host.object.pool_id }}" + service_type: "dns" + state: "absent" + ignore_errors: true diff --git a/tests/integration/targets/setup_infra_host/tasks/cleanup.yml b/tests/integration/targets/setup_infra_host/tasks/cleanup.yml new file mode 100644 index 00000000..150fb0ce --- /dev/null +++ b/tests/integration/targets/setup_infra_host/tasks/cleanup.yml @@ -0,0 +1,12 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + - name: "Delete Infra Host" + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + serial_number: "{{ serial_number }}" + state: "absent" + ignore_errors: true \ No newline at end of file diff --git a/tests/integration/targets/setup_infra_host/tasks/main.yml b/tests/integration/targets/setup_infra_host/tasks/main.yml new file mode 100644 index 00000000..6061e1e1 --- /dev/null +++ b/tests/integration/targets/setup_infra_host/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- module_defaults: + group/infoblox.bloxone.all: + csp_url: "{{ csp_url }}" + api_key: "{{ api_key }}" + block: + # Create a random Host name to avoid conflicts + - ansible.builtin.set_fact: + display_name: "test-host-{{ 999999 | random | string }}" + serial_number: "serial{{ 999999 | random | string }}" + + - name: Create a Host + infoblox.bloxone.infra_host: + display_name: "{{ display_name }}" + serial_number: "{{ serial_number }}" + state: present + register: _infra_host