From 6c87e07c92375e3a7609c3d29bde81c252039ae1 Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Mon, 4 Nov 2024 16:03:03 +0530 Subject: [PATCH 1/9] Fixed address Support for Bloxone Ansible. --- changelogs/fragments/38-fixed-address.yml | 3 + meta/runtime.yml | 11 + plugins/modules/b1_ipam_fixed_address.py | 9 + .../modules/b1_ipam_fixed_address_gather.py | 9 + plugins/modules/ipam_fixed_address.py | 783 ++++++++++++++++++ plugins/modules/ipam_fixed_address_info.py | 497 +++++++++++ .../targets/ipam_fixed_address/tasks/main.yml | 212 +++++ .../ipam_fixed_address_info/tasks/main.yml | 107 +++ 8 files changed, 1631 insertions(+) create mode 100644 changelogs/fragments/38-fixed-address.yml create mode 100644 plugins/modules/ipam_fixed_address.py create mode 100644 plugins/modules/ipam_fixed_address_info.py create mode 100644 tests/integration/targets/ipam_fixed_address/tasks/main.yml create mode 100644 tests/integration/targets/ipam_fixed_address_info/tasks/main.yml diff --git a/changelogs/fragments/38-fixed-address.yml b/changelogs/fragments/38-fixed-address.yml new file mode 100644 index 0000000..c5c20be --- /dev/null +++ b/changelogs/fragments/38-fixed-address.yml @@ -0,0 +1,3 @@ +deprecated_features: + - b1_ipam_fixed_address - is deprecated in favor of `ipam_fixed_address`. + - b1_ipam_fixed_address_gather - is deprecated in favor of `ipam_fixed_address_info`. \ No newline at end of file diff --git a/meta/runtime.yml b/meta/runtime.yml index 55f479e..0f43ccb 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -18,6 +18,8 @@ action_groups: - ipam_subnet_info - ipam_address_block - ipam_address_block_info + - ipam_fixed_address + - ipam_fixed_address_info plugin_routing: modules: @@ -65,3 +67,12 @@ plugin_routing: deprecation: removal_version: 3.0.0 warning_text: Use infoblox.bloxone.dns_auth_zone_info instead. + + b1_ipam_fixed_address: + deprecation: + removal_version: 3.0.0 + warning_text: Use infoblox.bloxone.ipam_fixed_address instead. + b1_ipam_fixed_address_gather: + deprecation: + removal_version: 3.0.0 + warning_text: Use infoblox.bloxone.ipam_fixed_address_info instead. diff --git a/plugins/modules/b1_ipam_fixed_address.py b/plugins/modules/b1_ipam_fixed_address.py index a102325..82386fa 100644 --- a/plugins/modules/b1_ipam_fixed_address.py +++ b/plugins/modules/b1_ipam_fixed_address.py @@ -15,6 +15,10 @@ contributor: "Chris Marrison (@ccmarris)" short_description: Configure fixed address on Infoblox BloxOne DDI version_added: "1.1.2" +deprecated: + removed_in: "3.0.0" + why: "This module is being deprecated and will be removed in version 3.0.0." + alternative: "Use the M(ipam_fixed_address) module instead." description: - Get, Create, Update and Delete fixed address on Infoblox BloxOne DDI. This module manages the fixed address object using BloxOne REST APIs. requirements: @@ -432,6 +436,11 @@ def main(): } module = AnsibleModule(argument_spec=argument_spec) + module.deprecate( + "This module is being deprecated and will be removed in version 3.0.0. Use the M(ipam_fixed_address) module instead.", + version="3.0.0", + collection_name="infoblox.bloxone" + ) (is_error, has_changed, result) = choice_map.get(module.params["state"])(module.params) if not is_error: diff --git a/plugins/modules/b1_ipam_fixed_address_gather.py b/plugins/modules/b1_ipam_fixed_address_gather.py index 84ed35e..f35dbc3 100644 --- a/plugins/modules/b1_ipam_fixed_address_gather.py +++ b/plugins/modules/b1_ipam_fixed_address_gather.py @@ -13,6 +13,10 @@ author: "Amit Mishra (@amishra)" short_description: Configure IP space on Infoblox BloxOne DDI version_added: "1.0.1" +deprecated: + removed_in: "3.0.0" + why: "This module is being deprecated and will be removed in version 3.0.0." + alternative: "Use the M(ipam_fixed_address_info) module instead." description: - Gather information about a fixed address object on Infoblox BloxOne DDI. This module gathers the fixed_address object using BloxOne REST APIs. requirements: @@ -119,6 +123,11 @@ def main(): choice_map = {"gather": get_fixed_address} module = AnsibleModule(argument_spec=argument_spec) + module.deprecate( + "This module is being deprecated and will be removed in version 3.0.0. Use the M(ipam_fixed_address_info) module instead.", + version="3.0.0", + collection_name="infoblox.bloxone" + ) (is_error, has_changed, result) = choice_map.get(module.params["state"])(module.params) if not is_error: diff --git a/plugins/modules/ipam_fixed_address.py b/plugins/modules/ipam_fixed_address.py new file mode 100644 index 0000000..a5ec828 --- /dev/null +++ b/plugins/modules/ipam_fixed_address.py @@ -0,0 +1,783 @@ +#!/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_fixed_address +short_description: Manage FixedAddress +description: + - Manage FixedAddress +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 + address: + description: + - "The reserved address." + type: str + comment: + description: + - "The description for the fixed address. May contain 0 to 1024 characters. Can include UTF-8." + type: str + dhcp_options: + description: + - "The list of DHCP options. May be either a specific option or a group of options." + type: list + elements: dict + suboptions: + group: + description: + - "The resource identifier." + type: str + option_code: + description: + - "The resource identifier." + type: str + option_value: + description: + - "The option value." + type: str + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + disable_dhcp: + description: + - "Optional. I(true) to disable object. The fixed address is converted to an exclusion when generating configuration." + - "Defaults to I(false)." + type: bool + header_option_filename: + description: + - "The configuration for header option filename field." + type: str + header_option_server_address: + description: + - "The configuration for header option server address field." + type: str + header_option_server_name: + description: + - "The configuration for header option server name field." + type: str + hostname: + description: + - "The DHCP host name associated with this fixed address. It is of FQDN type and it defaults to empty." + type: str + inheritance_parent: + description: + - "The resource identifier." + type: str + inheritance_sources: + description: + - "The inheritance configuration." + type: dict + suboptions: + dhcp_options: + description: + - "The inheritance configuration for I(dhcp_options) field." + type: dict + suboptions: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + value: + description: + - "The inherited DHCP option values." + type: list + elements: dict + suboptions: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + header_option_filename: + description: + - "The inheritance configuration for I(header_option_filename) field." + type: dict + suboptions: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + header_option_server_address: + description: + - "The inheritance configuration for I(header_option_server_address) field." + type: dict + suboptions: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + header_option_server_name: + description: + - "The inheritance configuration for I(header_option_server_name) field." + type: dict + suboptions: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + ip_space: + description: + - "The resource identifier." + type: str + match_type: + description: + - "Indicates how to match the client:" + - "* I(mac): match the client MAC address for both IPv4 and IPv6," + - "* I(client_text) or I(client_hex): match the client identifier for IPv4 only," + - "* I(relay_text) or I(relay_hex): match the circuit ID or remote ID in the DHCP relay agent option (82) for IPv4 only," + - "* I(duid): match the DHCP unique identifier, currently match only for IPv6 protocol." + type: str + match_value: + description: + - "The value to match." + type: str + name: + description: + - "The name of the fixed address. May contain 1 to 256 characters. Can include UTF-8." + type: str + parent: + description: + - "The resource identifier." + type: str + tags: + description: + - "The tags for the fixed address in JSON format." + type: dict + +extends_documentation_fragment: + - infoblox.bloxone.common +""" # noqa: E501 + +EXAMPLES = r""" +- name: Create a fixed address + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + ip_space: "example_ip_space" + match_type: "mac" + match_value: "00:00:00:00:00:00" + state: "present" + +- name: Create a fixed address with all parameters + infoblox.bloxone.ipam_fixed_address: + id: "fixed_address_id" + state: "present" + address: "10.0.0.1" + comment: "This is a fixed address" + dhcp_options: + - group: "group1" + option_code: "code1" + option_value: "value1" + type: "option" + disable_dhcp: false + header_option_filename: "filename" + header_option_server_address: "server_address" + header_option_server_name: "server_name" + hostname: "hostname.example.com" + inheritance_parent: "parent_id" + inheritance_sources: + dhcp_options: + action: "inherit" + value: + - action: "inherit" + header_option_filename: + action: "inherit" + header_option_server_address: + action: "inherit" + header_option_server_name: + action: "inherit" + ip_space: "example_ip_space" + match_type: "mac" + match_value: "00:00:00:00:00:00" + name: "fixed_address_name" + parent: "parent_id" + tags: + key1: "value1" + key2: "value2" + +- name: Delete a fixed address + infoblox.bloxone.ipam_fixed_address: + id: "fixed_address_id" + match_type: "mac" + match_value: "00:00:00:00:00:00" + state: "absent" +""" + +RETURN = r""" +id: + description: + - ID of the FixedAddress object + type: str + returned: Always +item: + description: + - FixedAddress object + type: complex + returned: Always + contains: + address: + description: + - "The reserved address." + type: str + returned: Always + comment: + description: + - "The description for the fixed address. May contain 0 to 1024 characters. Can include UTF-8." + type: str + returned: Always + compartment_id: + description: + - "The compartment associated with the object. If no compartment is associated with the object, the value defaults to empty." + type: str + returned: Always + created_at: + description: + - "Time when the object has been created." + type: str + returned: Always + dhcp_options: + description: + - "The list of DHCP options. May be either a specific option or a group of options." + type: list + returned: Always + elements: dict + contains: + group: + description: + - "The resource identifier." + type: str + returned: Always + option_code: + description: + - "The resource identifier." + type: str + returned: Always + option_value: + description: + - "The option value." + type: str + returned: Always + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + returned: Always + disable_dhcp: + description: + - "Optional. I(true) to disable object. The fixed address is converted to an exclusion when generating configuration." + - "Defaults to I(false)." + type: bool + returned: Always + header_option_filename: + description: + - "The configuration for header option filename field." + type: str + returned: Always + header_option_server_address: + description: + - "The configuration for header option server address field." + type: str + returned: Always + header_option_server_name: + description: + - "The configuration for header option server name field." + type: str + returned: Always + hostname: + description: + - "The DHCP host name associated with this fixed address. It is of FQDN type and it defaults to empty." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + inheritance_assigned_hosts: + description: + - "The list of the inheritance assigned hosts of the object." + type: list + returned: Always + elements: dict + contains: + display_name: + description: + - "The human-readable display name for the host referred to by I(ophid)." + type: str + returned: Always + host: + description: + - "The resource identifier." + type: str + returned: Always + ophid: + description: + - "The on-prem host ID." + type: str + returned: Always + inheritance_parent: + description: + - "The resource identifier." + type: str + returned: Always + inheritance_sources: + description: + - "The inheritance configuration." + type: dict + returned: Always + contains: + dhcp_options: + description: + - "The inheritance configuration for I(dhcp_options) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + returned: Always + value: + description: + - "The inherited DHCP option values." + type: list + returned: Always + elements: dict + contains: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value for the DHCP option." + type: dict + returned: Always + contains: + option: + description: + - "Option inherited from the ancestor." + type: dict + returned: Always + contains: + group: + description: + - "The resource identifier." + type: str + returned: Always + option_code: + description: + - "The resource identifier." + type: str + returned: Always + option_value: + description: + - "The option value." + type: str + returned: Always + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + returned: Always + overriding_group: + description: + - "The resource identifier." + type: str + returned: Always + header_option_filename: + description: + - "The inheritance configuration for I(header_option_filename) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value." + type: str + returned: Always + header_option_server_address: + description: + - "The inheritance configuration for I(header_option_server_address) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value." + type: str + returned: Always + header_option_server_name: + description: + - "The inheritance configuration for I(header_option_server_name) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value." + type: str + returned: Always + ip_space: + description: + - "The resource identifier." + type: str + returned: Always + match_type: + description: + - "Indicates how to match the client:" + - "* I(mac): match the client MAC address for both IPv4 and IPv6," + - "* I(client_text) or I(client_hex): match the client identifier for IPv4 only," + - "* I(relay_text) or I(relay_hex): match the circuit ID or remote ID in the DHCP relay agent option (82) for IPv4 only," + - "* I(duid): match the DHCP unique identifier, currently match only for IPv6 protocol." + type: str + returned: Always + match_value: + description: + - "The value to match." + type: str + returned: Always + name: + description: + - "The name of the fixed address. May contain 1 to 256 characters. Can include UTF-8." + type: str + returned: Always + parent: + description: + - "The resource identifier." + type: str + returned: Always + tags: + description: + - "The tags for the fixed address 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 FixedAddress, FixedAddressApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class FixedAddressModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(FixedAddressModule, 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 = FixedAddress.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 = FixedAddressApi(self.client).read(self.params["id"], inherit="full") + return resp.result + except NotFoundException as e: + if self.params["state"] == "absent": + return None + raise e + else: + filter = f"address=='{self.params['address']}' and ip_space=='{self.params['ip_space']}'" + resp = FixedAddressApi(self.client).list(filter=filter, inherit="full") + if len(resp.results) == 1: + return resp.results[0] + if len(resp.results) > 1: + self.fail_json(msg=f"Found multiple FixedAddress: {resp.results}") + if len(resp.results) == 0: + return None + + def create(self): + if self.check_mode: + return None + + resp = FixedAddressApi(self.client).create(body=self.payload, inherit="full") + return resp.result.model_dump(by_alias=True, exclude_none=True) + + def update(self): + if self.check_mode: + return None + + resp = FixedAddressApi(self.client).update(id=self.existing.id, body=self.payload, inherit="full") + return resp.result.model_dump(by_alias=True, exclude_none=True) + + def delete(self): + if self.check_mode: + return + + FixedAddressApi(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"] = "FixedAddress created" + elif self.params["state"] == "present" and self.existing is not None: + if self.payload_changed(): + item = self.update() + result["changed"] = True + result["msg"] = "FixedAddress updated" + elif self.params["state"] == "absent" and self.existing is not None: + self.delete() + result["changed"] = True + result["msg"] = "FixedAddress 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"), + address=dict(type="str"), + comment=dict(type="str"), + dhcp_options=dict( + type="list", + elements="dict", + options=dict( + group=dict(type="str"), + option_code=dict(type="str"), + option_value=dict(type="str"), + type=dict(type="str"), + ), + ), + disable_dhcp=dict(type="bool"), + header_option_filename=dict(type="str"), + header_option_server_address=dict(type="str"), + header_option_server_name=dict(type="str"), + hostname=dict(type="str"), + inheritance_parent=dict(type="str"), + inheritance_sources=dict( + type="dict", + options=dict( + dhcp_options=dict( + type="dict", + options=dict( + action=dict(type="str"), + value=dict( + type="list", + elements="dict", + options=dict( + action=dict(type="str"), + ), + ), + ), + ), + header_option_filename=dict( + type="dict", + options=dict( + action=dict(type="str"), + ), + ), + header_option_server_address=dict( + type="dict", + options=dict( + action=dict(type="str"), + ), + ), + header_option_server_name=dict( + type="dict", + options=dict( + action=dict(type="str"), + ), + ), + ), + ), + ip_space=dict(type="str"), + match_type=dict(type="str"), + match_value=dict(type="str"), + name=dict(type="str"), + parent=dict(type="str"), + tags=dict(type="dict"), + ) + + module = FixedAddressModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[("state", "present", ["address", "ip_space", "match_type", "match_value"])], + ) + + module.run_command() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/ipam_fixed_address_info.py b/plugins/modules/ipam_fixed_address_info.py new file mode 100644 index 0000000..1873ab3 --- /dev/null +++ b/plugins/modules/ipam_fixed_address_info.py @@ -0,0 +1,497 @@ +#!/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_fixed_address_info +short_description: Manage FixedAddress +description: + - Manage FixedAddress +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 + inherit: + description: + - Return inheritance information + type: str + required: false + choices: + - full + - partial + - none + default: full + 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""" +""" + +RETURN = r""" +id: + description: + - ID of the FixedAddress object + type: str + returned: Always +objects: + description: + - FixedAddress object + type: list + elements: dict + returned: Always + contains: + address: + description: + - "The reserved address." + type: str + returned: Always + comment: + description: + - "The description for the fixed address. May contain 0 to 1024 characters. Can include UTF-8." + type: str + returned: Always + compartment_id: + description: + - "The compartment associated with the object. If no compartment is associated with the object, the value defaults to empty." + type: str + returned: Always + created_at: + description: + - "Time when the object has been created." + type: str + returned: Always + dhcp_options: + description: + - "The list of DHCP options. May be either a specific option or a group of options." + type: list + returned: Always + elements: dict + contains: + group: + description: + - "The resource identifier." + type: str + returned: Always + option_code: + description: + - "The resource identifier." + type: str + returned: Always + option_value: + description: + - "The option value." + type: str + returned: Always + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + returned: Always + disable_dhcp: + description: + - "Optional. I(true) to disable object. The fixed address is converted to an exclusion when generating configuration." + - "Defaults to I(false)." + type: bool + returned: Always + header_option_filename: + description: + - "The configuration for header option filename field." + type: str + returned: Always + header_option_server_address: + description: + - "The configuration for header option server address field." + type: str + returned: Always + header_option_server_name: + description: + - "The configuration for header option server name field." + type: str + returned: Always + hostname: + description: + - "The DHCP host name associated with this fixed address. It is of FQDN type and it defaults to empty." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + inheritance_assigned_hosts: + description: + - "The list of the inheritance assigned hosts of the object." + type: list + returned: Always + elements: dict + contains: + display_name: + description: + - "The human-readable display name for the host referred to by I(ophid)." + type: str + returned: Always + host: + description: + - "The resource identifier." + type: str + returned: Always + ophid: + description: + - "The on-prem host ID." + type: str + returned: Always + inheritance_parent: + description: + - "The resource identifier." + type: str + returned: Always + inheritance_sources: + description: + - "The inheritance configuration." + type: dict + returned: Always + contains: + dhcp_options: + description: + - "The inheritance configuration for I(dhcp_options) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + returned: Always + value: + description: + - "The inherited DHCP option values." + type: list + returned: Always + elements: dict + contains: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value for the DHCP option." + type: dict + returned: Always + contains: + option: + description: + - "Option inherited from the ancestor." + type: dict + returned: Always + contains: + group: + description: + - "The resource identifier." + type: str + returned: Always + option_code: + description: + - "The resource identifier." + type: str + returned: Always + option_value: + description: + - "The option value." + type: str + returned: Always + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + returned: Always + overriding_group: + description: + - "The resource identifier." + type: str + returned: Always + header_option_filename: + description: + - "The inheritance configuration for I(header_option_filename) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value." + type: str + returned: Always + header_option_server_address: + description: + - "The inheritance configuration for I(header_option_server_address) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value." + type: str + returned: Always + header_option_server_name: + description: + - "The inheritance configuration for I(header_option_server_name) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting for a field." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(override): Use the value set in the object." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value." + type: str + returned: Always + ip_space: + description: + - "The resource identifier." + type: str + returned: Always + match_type: + description: + - "Indicates how to match the client:" + - "* I(mac): match the client MAC address for both IPv4 and IPv6," + - "* I(client_text) or I(client_hex): match the client identifier for IPv4 only," + - "* I(relay_text) or I(relay_hex): match the circuit ID or remote ID in the DHCP relay agent option (82) for IPv4 only," + - "* I(duid): match the DHCP unique identifier, currently match only for IPv6 protocol." + type: str + returned: Always + match_value: + description: + - "The value to match." + type: str + returned: Always + name: + description: + - "The name of the fixed address. May contain 1 to 256 characters. Can include UTF-8." + type: str + returned: Always + parent: + description: + - "The resource identifier." + type: str + returned: Always + tags: + description: + - "The tags for the fixed address 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 FixedAddressApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class FixedAddressInfoModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(FixedAddressInfoModule, self).__init__(*args, **kwargs) + self._existing = None + self._limit = 1000 + + def find_by_id(self): + try: + resp = FixedAddressApi(self.client).read(self.params["id"], inherit="full") + 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 = FixedAddressApi(self.client).list( + offset=offset, limit=self._limit, filter=filter_str, tfilter=tag_filter_str, inherit="full" + ) + 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), + inherit=dict(type="str", required=False, choices=["full", "partial", "none"], default="full"), + tag_filters=dict(type="dict", required=False), + tag_filter_query=dict(type="str", required=False), + ) + + module = FixedAddressInfoModule( + 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_fixed_address/tasks/main.yml b/tests/integration/targets/ipam_fixed_address/tasks/main.yml new file mode 100644 index 0000000..cfb1ba6 --- /dev/null +++ b/tests/integration/targets/ipam_fixed_address/tasks/main.yml @@ -0,0 +1,212 @@ +--- +- 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: + name: "test-ip-space-{{ 999999 | random | string }}" + tag_value: "site-{{ 999999 | random | string }}" + + # Basic tests for Subnet + - 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 Fixed Address" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + name: "test_fixed_address_ansible" + match_type: "mac" + match_value: "00:00:00:00:00:00" + ip_space: "{{ ip_space.id }}" + state: "present" + register: fixed_address + + - name: Get information about the Fixed Address + infoblox.bloxone.ipam_fixed_address_info: + filters: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + register: fixed_address_info + + - assert: + that: + - fixed_address is not failed + - fixed_address_info.objects | length == 1 + - fixed_address_info.objects[0].id == fixed_address.id + - fixed_address_info.objects[0].address == fixed_address.object.address + + - name: "Create a Fixed Address (idempotent)" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + match_type: "mac" + match_value: "00:00:00:00:00:00" + ip_space: "{{ ip_space.id }}" + state: "present" + register: fixed_address + - assert: + that: + - fixed_address is not changed + - fixed_address is not failed + + - name: "Delete a Fixed Address" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:00" + state: "absent" + register: fixed_address + - assert: + that: + - fixed_address is changed + - fixed_address is not failed + + - name: Get information about the Fixed Address + infoblox.bloxone.ipam_fixed_address_info: + filters: + address: "10.0.0.10" + ip_space: "{{ ip_space.id }}" + register: fixed_address_info + - assert: + that: + - fixed_address is changed + - fixed_address is not failed + - fixed_address_info.objects | length == 0 + + - name: "Delete a Fixed Address (idempotent)" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:00" + state: "absent" + register: fixed_address + - assert: + that: + - fixed_address is not changed + - fixed_address is not failed + + - name: "Create a Fixed Address with Comment" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.2" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:01" + comment: "test comment" + state: "present" + + register: fixed_address_comment + - assert: + that: + - fixed_address_comment is changed + - fixed_address_comment is not failed + - fixed_address_comment.object.comment == "test comment" + - fixed_address_comment.object.address == "10.0.0.2" + - fixed_address_comment.object.match_type == "mac" + - fixed_address_comment.object.match_value == "00:00:00:00:00:01" + - fixed_address_comment.object.ip_space == ip_space.id + + - name: "Delete a Fixed Address with Comment" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.2" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:01" + state: "absent" + + - name: "Create a Fixed Address with disable dhcp" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.2" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:01" + comment: "test comment" + disable_dhcp: true + state: "present" + + register: fixed_address_disable_dhcp + - assert: + that: + - fixed_address_disable_dhcp is changed + - fixed_address_disable_dhcp is not failed + - fixed_address_disable_dhcp.object.comment == "test comment" + - fixed_address_disable_dhcp.object.address == "10.0.0.2" + - fixed_address_disable_dhcp.object.match_type == "mac" + - fixed_address_disable_dhcp.object.match_value == "00:00:00:00:00:01" + - fixed_address_disable_dhcp.object.ip_space == ip_space.id + - fixed_address_disable_dhcp.object.disable_dhcp == true + + - name: "Delete a Fixed Address with disable dhcp" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.2" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:01" + state: "absent" + + - name: "Create a Fixed Address with host name" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.2" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:01" + comment: "test comment" + disable_dhcp: true + hostname: "test-host-name" + state: "present" + register: fixed_address_hostname + - assert: + that: + - fixed_address_hostname is changed + - fixed_address_hostname is not failed + - fixed_address_hostname.object.comment == "test comment" + - fixed_address_hostname.object.address == "10.0.0.2" + - fixed_address_hostname.object.match_type == "mac" + - fixed_address_hostname.object.match_value == "00:00:00:00:00:01" + - fixed_address_hostname.object.ip_space == ip_space.id + - fixed_address_hostname.object.disable_dhcp == true + - fixed_address_hostname.object.hostname == "test-host-name" + + - name: "Delete a Fixed Address with host name" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.2" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:01" + state: "absent" + + always: + - name: "Delete Fixed Address" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:00" + state: "present" + ignore_errors: true + + - name: "Delete Subnet" + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ ip_space.id }}" + state: "absent" + ignore_errors: true + + - name: "Delete IP Space" + infoblox.bloxone.ipam_ip_space: + name: "{{ name }}" + state: "absent" + ignore_errors: true diff --git a/tests/integration/targets/ipam_fixed_address_info/tasks/main.yml b/tests/integration/targets/ipam_fixed_address_info/tasks/main.yml new file mode 100644 index 0000000..0573598 --- /dev/null +++ b/tests/integration/targets/ipam_fixed_address_info/tasks/main.yml @@ -0,0 +1,107 @@ +--- +- 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: + name: "test-ip-space-{{ 999999 | random | string }}" + tag_value: "site-{{ 999999 | random | string }}" + + # Basic tests for IP Space and Subnet + - 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 + + # Create a Fixed Address + - name: "Create a Fixed Address" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + name: "test_fixed_address_ansible" + match_type: "mac" + match_value: "00:00:00:00:00:00" + ip_space: "{{ ip_space.id }}" + state: "present" + register: fixed_address + + # Get information about the Fixed Address + - name: Get information about the Fixed Address + infoblox.bloxone.ipam_fixed_address_info: + filters: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + register: fixed_address_info + + - assert: + that: + - fixed_address_info.objects | length == 1 + - fixed_address_info.objects[0].id == fixed_address.id + - fixed_address_info.objects[0].address == fixed_address.object.address + + # Test idempotency of Fixed Address Info + - name: "Get Fixed Address Info (idempotent)" + infoblox.bloxone.ipam_fixed_address_info: + filters: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + register: fixed_address_info + - assert: + that: + - fixed_address_info.objects | length == 1 + - fixed_address_info.objects[0].id == fixed_address.id + - fixed_address_info.objects[0].address == fixed_address.object.address + + # Delete the Fixed Address + - name: "Delete a Fixed Address" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:00" + state: "absent" + register: fixed_address + + # Verify the Fixed Address is deleted + - name: Get information about the Fixed Address + infoblox.bloxone.ipam_fixed_address_info: + filters: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + register: fixed_address_info + - assert: + that: + - fixed_address_info.objects | length == 0 + + always: + - name: "Delete Fixed Address" + infoblox.bloxone.ipam_fixed_address: + address: "10.0.0.1" + ip_space: "{{ ip_space.id }}" + match_type: "mac" + match_value: "00:00:00:00:00:00" + state: "absent" + ignore_errors: true + + - name: "Delete Subnet" + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ ip_space.id }}" + state: "absent" + ignore_errors: true + + - name: "Delete IP Space" + infoblox.bloxone.ipam_ip_space: + name: "{{ name }}" + state: "absent" + ignore_errors: true From a0ae5cbb39a3a396669ae4e1027d234fd3cc34b9 Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Mon, 4 Nov 2024 16:17:14 +0530 Subject: [PATCH 2/9] Fixed linter error --- plugins/modules/b1_ipam_fixed_address.py | 2 +- plugins/modules/b1_ipam_fixed_address_gather.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/b1_ipam_fixed_address.py b/plugins/modules/b1_ipam_fixed_address.py index 82386fa..cc91c88 100644 --- a/plugins/modules/b1_ipam_fixed_address.py +++ b/plugins/modules/b1_ipam_fixed_address.py @@ -439,7 +439,7 @@ def main(): module.deprecate( "This module is being deprecated and will be removed in version 3.0.0. Use the M(ipam_fixed_address) module instead.", version="3.0.0", - collection_name="infoblox.bloxone" + collection_name="infoblox.bloxone", ) (is_error, has_changed, result) = choice_map.get(module.params["state"])(module.params) diff --git a/plugins/modules/b1_ipam_fixed_address_gather.py b/plugins/modules/b1_ipam_fixed_address_gather.py index f35dbc3..1931c79 100644 --- a/plugins/modules/b1_ipam_fixed_address_gather.py +++ b/plugins/modules/b1_ipam_fixed_address_gather.py @@ -126,7 +126,7 @@ def main(): module.deprecate( "This module is being deprecated and will be removed in version 3.0.0. Use the M(ipam_fixed_address_info) module instead.", version="3.0.0", - collection_name="infoblox.bloxone" + collection_name="infoblox.bloxone", ) (is_error, has_changed, result) = choice_map.get(module.params["state"])(module.params) From 6fe3c43e92bd6ab006195f16de5939954009ed36 Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Tue, 5 Nov 2024 11:14:35 +0530 Subject: [PATCH 3/9] removed module deprecation from main. --- plugins/modules/b1_ipam_fixed_address.py | 5 ----- plugins/modules/b1_ipam_fixed_address_gather.py | 5 ----- 2 files changed, 10 deletions(-) diff --git a/plugins/modules/b1_ipam_fixed_address.py b/plugins/modules/b1_ipam_fixed_address.py index cc91c88..f8c9c0e 100644 --- a/plugins/modules/b1_ipam_fixed_address.py +++ b/plugins/modules/b1_ipam_fixed_address.py @@ -436,11 +436,6 @@ def main(): } module = AnsibleModule(argument_spec=argument_spec) - module.deprecate( - "This module is being deprecated and will be removed in version 3.0.0. Use the M(ipam_fixed_address) module instead.", - version="3.0.0", - collection_name="infoblox.bloxone", - ) (is_error, has_changed, result) = choice_map.get(module.params["state"])(module.params) if not is_error: diff --git a/plugins/modules/b1_ipam_fixed_address_gather.py b/plugins/modules/b1_ipam_fixed_address_gather.py index 1931c79..a71a335 100644 --- a/plugins/modules/b1_ipam_fixed_address_gather.py +++ b/plugins/modules/b1_ipam_fixed_address_gather.py @@ -123,11 +123,6 @@ def main(): choice_map = {"gather": get_fixed_address} module = AnsibleModule(argument_spec=argument_spec) - module.deprecate( - "This module is being deprecated and will be removed in version 3.0.0. Use the M(ipam_fixed_address_info) module instead.", - version="3.0.0", - collection_name="infoblox.bloxone", - ) (is_error, has_changed, result) = choice_map.get(module.params["state"])(module.params) if not is_error: From 2dc90b06dc800b8161900fba00c10f54fa92e75e Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Wed, 6 Nov 2024 11:42:08 +0530 Subject: [PATCH 4/9] Separated dependency for integration test case. --- .../targets/ipam_fixed_address/meta/main.yml | 2 + .../targets/ipam_fixed_address/tasks/main.yml | 48 +++++++------------ .../targets/setup_ip_space/tasks/main.yml | 16 +++++++ .../targets/setup_subnet/tasks/main.yml | 17 +++++++ 4 files changed, 52 insertions(+), 31 deletions(-) create mode 100644 tests/integration/targets/ipam_fixed_address/meta/main.yml create mode 100644 tests/integration/targets/setup_ip_space/tasks/main.yml create mode 100644 tests/integration/targets/setup_subnet/tasks/main.yml diff --git a/tests/integration/targets/ipam_fixed_address/meta/main.yml b/tests/integration/targets/ipam_fixed_address/meta/main.yml new file mode 100644 index 0000000..33e5eb9 --- /dev/null +++ b/tests/integration/targets/ipam_fixed_address/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [setup_ip_space, setup_subnet] \ No newline at end of file diff --git a/tests/integration/targets/ipam_fixed_address/tasks/main.yml b/tests/integration/targets/ipam_fixed_address/tasks/main.yml index cfb1ba6..43f2215 100644 --- a/tests/integration/targets/ipam_fixed_address/tasks/main.yml +++ b/tests/integration/targets/ipam_fixed_address/tasks/main.yml @@ -10,27 +10,13 @@ name: "test-ip-space-{{ 999999 | random | string }}" tag_value: "site-{{ 999999 | random | string }}" - # Basic tests for Subnet - - 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 Fixed Address" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.1" name: "test_fixed_address_ansible" match_type: "mac" match_value: "00:00:00:00:00:00" - ip_space: "{{ ip_space.id }}" + ip_space: "{{_ip_space.id }}" state: "present" register: fixed_address @@ -38,7 +24,7 @@ infoblox.bloxone.ipam_fixed_address_info: filters: address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" register: fixed_address_info - assert: @@ -53,7 +39,7 @@ address: "10.0.0.1" match_type: "mac" match_value: "00:00:00:00:00:00" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" state: "present" register: fixed_address - assert: @@ -64,7 +50,7 @@ - name: "Delete a Fixed Address" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:00" state: "absent" @@ -78,7 +64,7 @@ infoblox.bloxone.ipam_fixed_address_info: filters: address: "10.0.0.10" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" register: fixed_address_info - assert: that: @@ -89,7 +75,7 @@ - name: "Delete a Fixed Address (idempotent)" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:00" state: "absent" @@ -102,7 +88,7 @@ - name: "Create a Fixed Address with Comment" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.2" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:01" comment: "test comment" @@ -117,12 +103,12 @@ - fixed_address_comment.object.address == "10.0.0.2" - fixed_address_comment.object.match_type == "mac" - fixed_address_comment.object.match_value == "00:00:00:00:00:01" - - fixed_address_comment.object.ip_space == ip_space.id + - fixed_address_comment.object.ip_space == _ip_space.id - name: "Delete a Fixed Address with Comment" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.2" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:01" state: "absent" @@ -130,7 +116,7 @@ - name: "Create a Fixed Address with disable dhcp" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.2" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:01" comment: "test comment" @@ -146,13 +132,13 @@ - fixed_address_disable_dhcp.object.address == "10.0.0.2" - fixed_address_disable_dhcp.object.match_type == "mac" - fixed_address_disable_dhcp.object.match_value == "00:00:00:00:00:01" - - fixed_address_disable_dhcp.object.ip_space == ip_space.id + - fixed_address_disable_dhcp.object.ip_space == _ip_space.id - fixed_address_disable_dhcp.object.disable_dhcp == true - name: "Delete a Fixed Address with disable dhcp" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.2" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:01" state: "absent" @@ -160,7 +146,7 @@ - name: "Create a Fixed Address with host name" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.2" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:01" comment: "test comment" @@ -176,14 +162,14 @@ - fixed_address_hostname.object.address == "10.0.0.2" - fixed_address_hostname.object.match_type == "mac" - fixed_address_hostname.object.match_value == "00:00:00:00:00:01" - - fixed_address_hostname.object.ip_space == ip_space.id + - fixed_address_hostname.object.ip_space == _ip_space.id - fixed_address_hostname.object.disable_dhcp == true - fixed_address_hostname.object.hostname == "test-host-name" - name: "Delete a Fixed Address with host name" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.2" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:01" state: "absent" @@ -192,7 +178,7 @@ - name: "Delete Fixed Address" infoblox.bloxone.ipam_fixed_address: address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" + ip_space: "{{ _ip_space.id }}" match_type: "mac" match_value: "00:00:00:00:00:00" state: "present" @@ -201,7 +187,7 @@ - name: "Delete Subnet" infoblox.bloxone.ipam_subnet: address: "10.0.0.0/24" - space: "{{ ip_space.id }}" + space: "{{ _ip_space.id }}" 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 0000000..cda303b --- /dev/null +++ b/tests/integration/targets/setup_ip_space/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 View name to avoid conflicts + - ansible.builtin.set_fact: + name: "test-view-{{ 999999 | random | string }}" + + # Create an Ip Space + - name: "Create an IP space" + infoblox.bloxone.ipam_ip_space: + name: "{{ name }}" + state: "present" + register: _ip_space \ No newline at end of file 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 0000000..25e7962 --- /dev/null +++ b/tests/integration/targets/setup_subnet/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 View name to avoid conflicts + - ansible.builtin.set_fact: + name: "test-view-{{ 999999 | random | string }}" + + # Create a subnet + - name: "Create a Subnet" + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ _ip_space.id }}" + state: "present" + register: _subnet From 3c8711f2867bb81ed662c5f824af704f210affce Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Tue, 3 Dec 2024 10:47:21 +0530 Subject: [PATCH 5/9] Implemented IPAM range. --- meta/runtime.yml | 2 + plugins/modules/ipam_range.py | 779 ++++++++++++++++++ plugins/modules/ipam_range_info.py | 544 ++++++++++++ .../targets/ipam_range/meta/main.yml | 2 + .../targets/ipam_range/tasks/main.yml | 283 +++++++ .../targets/ipam_range_info/meta/main.yml | 2 + .../targets/ipam_range_info/tasks/main.yml | 84 ++ .../targets/setup_ip_space/tasks/main.yml | 2 +- .../targets/setup_subnet/tasks/main.yml | 4 - 9 files changed, 1697 insertions(+), 5 deletions(-) create mode 100644 plugins/modules/ipam_range.py create mode 100644 plugins/modules/ipam_range_info.py create mode 100644 tests/integration/targets/ipam_range/meta/main.yml create mode 100644 tests/integration/targets/ipam_range/tasks/main.yml create mode 100644 tests/integration/targets/ipam_range_info/meta/main.yml create mode 100644 tests/integration/targets/ipam_range_info/tasks/main.yml diff --git a/meta/runtime.yml b/meta/runtime.yml index 0f43ccb..76f9573 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -20,6 +20,8 @@ action_groups: - ipam_address_block_info - ipam_fixed_address - ipam_fixed_address_info + - ipam_range + - ipam_range_info plugin_routing: modules: diff --git a/plugins/modules/ipam_range.py b/plugins/modules/ipam_range.py new file mode 100644 index 0000000..41fd902 --- /dev/null +++ b/plugins/modules/ipam_range.py @@ -0,0 +1,779 @@ +#!/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_range +short_description: Manage Range +description: + - Manage Range +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 + comment: + description: + - "The description for the range. May contain 0 to 1024 characters. Can include UTF-8." + type: str + dhcp_host: + description: + - "The resource identifier." + type: str + dhcp_options: + description: + - "The list of DHCP options. May be either a specific option or a group of options." + type: list + elements: dict + suboptions: + group: + description: + - "The resource identifier." + type: str + option_code: + description: + - "The resource identifier." + type: str + option_value: + description: + - "The option value." + type: str + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + disable_dhcp: + description: + - "Optional. I(true) to disable object. A disabled object is effectively non-existent when generating configuration." + - "Defaults to I(false)." + type: bool + end: + description: + - "The end IP address of the range." + type: str + exclusion_ranges: + description: + - "The list of all exclusion ranges in the scope of the range." + type: list + elements: dict + suboptions: + comment: + description: + - "The description for the exclusion range. May contain 0 to 1024 characters. Can include UTF-8." + type: str + end: + description: + - "The end address of the exclusion range." + type: str + start: + description: + - "The start address of the exclusion range." + type: str + filters: + description: + - "The list of all allow/deny filters of the range." + type: list + elements: dict + suboptions: + access: + description: + - "The access type of DHCP filter (I(allow) or I(deny))." + - "Defaults to I(allow)." + type: str + hardware_filter_id: + description: + - "The resource identifier." + type: str + option_filter_id: + description: + - "The resource identifier." + type: str + inheritance_parent: + description: + - "The resource identifier." + type: str + inheritance_sources: + description: + - "The DHCP inheritance configuration for the range." + type: dict + suboptions: + dhcp_options: + description: + - "The inheritance configuration for the I(dhcp_options) field." + type: dict + suboptions: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + value: + description: + - "The inherited DHCP option values." + type: list + elements: dict + suboptions: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + name: + description: + - "The name of the range. May contain 1 to 256 characters. Can include UTF-8." + type: str + parent: + description: + - "The resource identifier." + type: str + space: + description: + - "The resource identifier." + type: str + start: + description: + - "The start IP address of the range." + type: str + tags: + description: + - "The tags for the range in JSON format." + type: dict + threshold: + description: + - "The utilization threshold settings for the range." + type: dict + suboptions: + enabled: + description: + - "Indicates whether the utilization threshold for IP addresses is enabled or not." + type: bool + high: + description: + - "The high threshold value for the percentage of used IP addresses relative to the total IP addresses available in the scope of the object. + Thresholds are inclusive in the comparison test." + type: int + low: + description: + - "The low threshold value for the percentage of used IP addresses relative to the total IP addresses available in the scope of the object. + Thresholds are inclusive in the comparison test." + type: int + +extends_documentation_fragment: + - infoblox.bloxone.common +""" +EXAMPLES = r""" + - name: "Create an ip space" + infoblox.bloxone.ipam_ip_space: + name: "my-ip-space" + 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" + + - name: "Create a Range" + infoblox.bloxone.ipam_range: + start: "10.0.0.1" + end: "10.0.0.100" + space: "{{ ip_space.id }}" + disable_dhcp: "true" + tags: + location: "some where on earth" + name: "Example Range" + exclusion_ranges: + - start: "10.0.0.10" + end: "10.0.0.20" + comment: "Exclude this range" + threshold: + enabled: true + high: 90 + low: 10 + state: "present" + + - name: "Delete a Range" + infoblox.bloxone.ipam_range: + start: "10.0.0.1" + end: "10.0.0.100" + space: "{{ _ip_space.id }}" + state: "absent" +""" + +RETURN = r""" +id: + description: + - ID of the Range object + type: str + returned: Always +item: + description: + - Range object + type: complex + returned: Always + contains: + comment: + description: + - "The description for the range. May contain 0 to 1024 characters. Can include UTF-8." + type: str + returned: Always + compartment_id: + description: + - "The compartment associated with the object. If no compartment is associated with the object, the value defaults to empty." + type: str + returned: Always + created_at: + description: + - "Time when the object has been created." + type: str + returned: Always + dhcp_host: + description: + - "The resource identifier." + type: str + returned: Always + dhcp_options: + description: + - "The list of DHCP options. May be either a specific option or a group of options." + type: list + returned: Always + elements: dict + contains: + group: + description: + - "The resource identifier." + type: str + returned: Always + option_code: + description: + - "The resource identifier." + type: str + returned: Always + option_value: + description: + - "The option value." + type: str + returned: Always + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + returned: Always + disable_dhcp: + description: + - "Optional. I(true) to disable object. A disabled object is effectively non-existent when generating configuration." + - "Defaults to I(false)." + type: bool + returned: Always + end: + description: + - "The end IP address of the range." + type: str + returned: Always + exclusion_ranges: + description: + - "The list of all exclusion ranges in the scope of the range." + type: list + returned: Always + elements: dict + contains: + comment: + description: + - "The description for the exclusion range. May contain 0 to 1024 characters. Can include UTF-8." + type: str + returned: Always + end: + description: + - "The end address of the exclusion range." + type: str + returned: Always + start: + description: + - "The start address of the exclusion range." + type: str + returned: Always + filters: + description: + - "The list of all allow/deny filters of the range." + type: list + returned: Always + elements: dict + contains: + access: + description: + - "The access type of DHCP filter (I(allow) or I(deny))." + - "Defaults to I(allow)." + type: str + returned: Always + hardware_filter_id: + description: + - "The resource identifier." + type: str + returned: Always + option_filter_id: + description: + - "The resource identifier." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + inheritance_assigned_hosts: + description: + - "The list of the inheritance assigned hosts of the object." + type: list + returned: Always + elements: dict + contains: + display_name: + description: + - "The human-readable display name for the host referred to by I(ophid)." + type: str + returned: Always + host: + description: + - "The resource identifier." + type: str + returned: Always + ophid: + description: + - "The on-prem host ID." + type: str + returned: Always + inheritance_parent: + description: + - "The resource identifier." + type: str + returned: Always + inheritance_sources: + description: + - "The DHCP inheritance configuration for the range." + type: dict + returned: Always + contains: + dhcp_options: + description: + - "The inheritance configuration for the I(dhcp_options) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + returned: Always + value: + description: + - "The inherited DHCP option values." + type: list + returned: Always + elements: dict + contains: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value for the DHCP option." + type: dict + returned: Always + contains: + option: + description: + - "Option inherited from the ancestor." + type: dict + returned: Always + contains: + group: + description: + - "The resource identifier." + type: str + returned: Always + option_code: + description: + - "The resource identifier." + type: str + returned: Always + option_value: + description: + - "The option value." + type: str + returned: Always + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + returned: Always + overriding_group: + description: + - "The resource identifier." + type: str + returned: Always + name: + description: + - "The name of the range. May contain 1 to 256 characters. Can include UTF-8." + type: str + returned: Always + parent: + description: + - "The resource identifier." + type: str + returned: Always + protocol: + description: + - "The type of protocol (I(ip4) or I(ip6))." + type: str + returned: Always + space: + description: + - "The resource identifier." + type: str + returned: Always + space_name: + description: + - "The name of the IP Space the range belongs to." + type: str + returned: Always + start: + description: + - "The start IP address of the range." + type: str + returned: Always + tags: + description: + - "The tags for the range in JSON format." + type: dict + returned: Always + threshold: + description: + - "The utilization threshold settings for the range." + type: dict + returned: Always + contains: + enabled: + description: + - "Indicates whether the utilization threshold for IP addresses is enabled or not." + type: bool + returned: Always + high: + description: + - "The high threshold value for the percentage of used IP addresses relative to the total IP addresses available in the scope of the object. Thresholds are inclusive in the comparison test." + type: int + returned: Always + low: + description: + - "The low threshold value for the percentage of used IP addresses relative to the total IP addresses available in the scope of the object. Thresholds are inclusive in the comparison test." + type: int + 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 + utilization: + description: + - "The utilization statistics of IPV4 addresses for the range." + type: dict + returned: Always + contains: + abandon_utilization: + description: + - "The percentage of abandoned IP addresses relative to the total IP addresses available in the scope of the object." + type: int + returned: Always + abandoned: + description: + - "The number of IP addresses in the scope of the object which are in the abandoned state (issued by a DHCP server and then declined by the client)." + type: str + returned: Always + dynamic: + description: + - "The number of IP addresses handed out by DHCP in the scope of the object. This includes all leased addresses, fixed addresses that are defined but not currently leased and abandoned leases." + type: str + returned: Always + free: + description: + - "The number of IP addresses available in the scope of the object." + type: str + returned: Always + static: + description: + - "The number of defined IP addresses such as reservations or DNS records. It can be computed as I(static) = I(used) - I(dynamic)." + type: str + returned: Always + total: + description: + - "The total number of IP addresses available in the scope of the object." + type: str + returned: Always + used: + description: + - "The number of IP addresses used in the scope of the object." + type: str + returned: Always + utilization: + description: + - "The percentage of used IP addresses relative to the total IP addresses available in the scope of the object." + type: int + returned: Always + utilization_v6: + description: + - "The utilization of IPV6 addresses in the range." + type: dict + returned: Always + contains: + abandoned: + description: "" + type: str + returned: Always + dynamic: + description: "" + type: str + returned: Always + static: + description: "" + type: str + returned: Always + total: + description: "" + type: str + returned: Always + used: + description: "" + 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 Range, RangeApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class RangeModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(RangeModule, 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 = Range.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 = RangeApi(self.client).read(self.params["id"], inherit="full") + return resp.result + except NotFoundException as e: + if self.params["state"] == "absent": + return None + raise e + else: + filter = f"start=='{self.params['start']}' and end=='{self.params['end']}' and space=='{self.params['space']}'" + resp = RangeApi(self.client).list(filter=filter, inherit="full") + if len(resp.results) == 1: + return resp.results[0] + if len(resp.results) > 1: + self.fail_json(msg=f"Found multiple Range: {resp.results}") + if len(resp.results) == 0: + return None + + def create(self): + if self.check_mode: + return None + + resp = RangeApi(self.client).create(body=self.payload, inherit="full") + return resp.result.model_dump(by_alias=True, exclude_none=True) + + def update(self): + if self.check_mode: + return None + + update_body = self.validate_readonly_on_update(self.existing, self.payload, ["space"]) + + resp = RangeApi(self.client).update(id=self.existing.id, body=update_body, inherit="full") + return resp.result.model_dump(by_alias=True, exclude_none=True) + + def delete(self): + if self.check_mode: + return + + RangeApi(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"] = "Range created" + elif self.params["state"] == "present" and self.existing is not None: + if self.payload_changed(): + item = self.update() + result["changed"] = True + result["msg"] = "Range updated" + elif self.params["state"] == "absent" and self.existing is not None: + self.delete() + result["changed"] = True + result["msg"] = "Range 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"), + comment=dict(type="str"), + dhcp_host=dict(type="str"), + dhcp_options=dict(type="list", elements="dict", options=dict( + group=dict(type="str"), + option_code=dict(type="str"), + option_value=dict(type="str"), + type=dict(type="str"), + )), + disable_dhcp=dict(type="bool"), + end=dict(type="str"), + exclusion_ranges=dict(type="list", elements="dict", options=dict( + comment=dict(type="str"), + end=dict(type="str"), + start=dict(type="str"), + )), + filters=dict(type="list", elements="dict", options=dict( + access=dict(type="str"), + hardware_filter_id=dict(type="str"), + option_filter_id=dict(type="str"), + )), + inheritance_parent=dict(type="str"), + inheritance_sources=dict(type="dict", options=dict( + dhcp_options=dict(type="dict", options=dict( + action=dict(type="str"), + value=dict(type="list", elements="dict", options=dict( + action=dict(type="str"), + )), + )), + )), + name=dict(type="str"), + parent=dict(type="str"), + space=dict(type="str"), + start=dict(type="str"), + tags=dict(type="dict"), + threshold=dict(type="dict", options=dict( + enabled=dict(type="bool"), + high=dict(type="int"), + low=dict(type="int"), + )), + + ) + + module = RangeModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[("state", "present", ["start", "end", "space"])], + ) + + module.run_command() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/ipam_range_info.py b/plugins/modules/ipam_range_info.py new file mode 100644 index 0000000..10dd5b1 --- /dev/null +++ b/plugins/modules/ipam_range_info.py @@ -0,0 +1,544 @@ +#!/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_range_info +short_description: Manage Range +description: + - Manage Range +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 + inherit: + description: + - Return inheritance information + type: str + required: false + choices: + - full + - partial + - none + default: full + 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 +""" + +EXAMPLES = r""" +- name: Retrieve range information by ID + ipam_range_info: + id: "range-id-12345" + +- name: Retrieve range information with filters + ipam_range_info: + filters: + start: "192.168.1.0" + end: "192.168.1.255" + +- name: Retrieve range information with filter query + ipam_range_info: + filter_query: "start=='10.0.0.1' and end=='10.0.0.100'" +""" + +RETURN = r""" +id: + description: + - ID of the Range object + type: str + returned: Always +objects: + description: + - Range object + type: list + elements: dict + returned: Always + contains: + comment: + description: + - "The description for the range. May contain 0 to 1024 characters. Can include UTF-8." + type: str + returned: Always + compartment_id: + description: + - "The compartment associated with the object. If no compartment is associated with the object, the value defaults to empty." + type: str + returned: Always + created_at: + description: + - "Time when the object has been created." + type: str + returned: Always + dhcp_host: + description: + - "The resource identifier." + type: str + returned: Always + dhcp_options: + description: + - "The list of DHCP options. May be either a specific option or a group of options." + type: list + returned: Always + elements: dict + contains: + group: + description: + - "The resource identifier." + type: str + returned: Always + option_code: + description: + - "The resource identifier." + type: str + returned: Always + option_value: + description: + - "The option value." + type: str + returned: Always + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + returned: Always + disable_dhcp: + description: + - "Optional. I(true) to disable object. A disabled object is effectively non-existent when generating configuration." + - "Defaults to I(false)." + type: bool + returned: Always + end: + description: + - "The end IP address of the range." + type: str + returned: Always + exclusion_ranges: + description: + - "The list of all exclusion ranges in the scope of the range." + type: list + returned: Always + elements: dict + contains: + comment: + description: + - "The description for the exclusion range. May contain 0 to 1024 characters. Can include UTF-8." + type: str + returned: Always + end: + description: + - "The end address of the exclusion range." + type: str + returned: Always + start: + description: + - "The start address of the exclusion range." + type: str + returned: Always + filters: + description: + - "The list of all allow/deny filters of the range." + type: list + returned: Always + elements: dict + contains: + access: + description: + - "The access type of DHCP filter (I(allow) or I(deny))." + - "Defaults to I(allow)." + type: str + returned: Always + hardware_filter_id: + description: + - "The resource identifier." + type: str + returned: Always + option_filter_id: + description: + - "The resource identifier." + type: str + returned: Always + id: + description: + - "The resource identifier." + type: str + returned: Always + inheritance_assigned_hosts: + description: + - "The list of the inheritance assigned hosts of the object." + type: list + returned: Always + elements: dict + contains: + display_name: + description: + - "The human-readable display name for the host referred to by I(ophid)." + type: str + returned: Always + host: + description: + - "The resource identifier." + type: str + returned: Always + ophid: + description: + - "The on-prem host ID." + type: str + returned: Always + inheritance_parent: + description: + - "The resource identifier." + type: str + returned: Always + inheritance_sources: + description: + - "The DHCP inheritance configuration for the range." + type: dict + returned: Always + contains: + dhcp_options: + description: + - "The inheritance configuration for the I(dhcp_options) field." + type: dict + returned: Always + contains: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + returned: Always + value: + description: + - "The inherited DHCP option values." + type: list + returned: Always + elements: dict + contains: + action: + description: + - "The inheritance setting." + - "Valid values are:" + - "* I(inherit): Use the inherited value." + - "* I(block): Don't use the inherited value." + - "Defaults to I(inherit)." + type: str + returned: Always + display_name: + description: + - "The human-readable display name for the object referred to by I(source)." + type: str + returned: Always + source: + description: + - "The resource identifier." + type: str + returned: Always + value: + description: + - "The inherited value for the DHCP option." + type: dict + returned: Always + contains: + option: + description: + - "Option inherited from the ancestor." + type: dict + returned: Always + contains: + group: + description: + - "The resource identifier." + type: str + returned: Always + option_code: + description: + - "The resource identifier." + type: str + returned: Always + option_value: + description: + - "The option value." + type: str + returned: Always + type: + description: + - "The type of item." + - "Valid values are:" + - "* I(group)" + - "* I(option)" + type: str + returned: Always + overriding_group: + description: + - "The resource identifier." + type: str + returned: Always + name: + description: + - "The name of the range. May contain 1 to 256 characters. Can include UTF-8." + type: str + returned: Always + parent: + description: + - "The resource identifier." + type: str + returned: Always + protocol: + description: + - "The type of protocol (I(ip4) or I(ip6))." + type: str + returned: Always + space: + description: + - "The resource identifier." + type: str + returned: Always + space_name: + description: + - "The name of the IP Space the range belongs to." + type: str + returned: Always + start: + description: + - "The start IP address of the range." + type: str + returned: Always + tags: + description: + - "The tags for the range in JSON format." + type: dict + returned: Always + threshold: + description: + - "The utilization threshold settings for the range." + type: dict + returned: Always + contains: + enabled: + description: + - "Indicates whether the utilization threshold for IP addresses is enabled or not." + type: bool + returned: Always + high: + description: + - "The high threshold value for the percentage of used IP addresses relative to the total IP addresses available in the scope of the object. Thresholds are inclusive in the comparison test." + type: int + returned: Always + low: + description: + - "The low threshold value for the percentage of used IP addresses relative to the total IP addresses available in the scope of the object. Thresholds are inclusive in the comparison test." + type: int + 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 + utilization: + description: + - "The utilization statistics of IPV4 addresses for the range." + type: dict + returned: Always + contains: + abandon_utilization: + description: + - "The percentage of abandoned IP addresses relative to the total IP addresses available in the scope of the object." + type: int + returned: Always + abandoned: + description: + - "The number of IP addresses in the scope of the object which are in the abandoned state (issued by a DHCP server and then declined by the client)." + type: str + returned: Always + dynamic: + description: + - "The number of IP addresses handed out by DHCP in the scope of the object. This includes all leased addresses, fixed addresses that are defined but not currently leased and abandoned leases." + type: str + returned: Always + free: + description: + - "The number of IP addresses available in the scope of the object." + type: str + returned: Always + static: + description: + - "The number of defined IP addresses such as reservations or DNS records. It can be computed as I(static) = I(used) - I(dynamic)." + type: str + returned: Always + total: + description: + - "The total number of IP addresses available in the scope of the object." + type: str + returned: Always + used: + description: + - "The number of IP addresses used in the scope of the object." + type: str + returned: Always + utilization: + description: + - "The percentage of used IP addresses relative to the total IP addresses available in the scope of the object." + type: int + returned: Always + utilization_v6: + description: + - "The utilization of IPV6 addresses in the range." + type: dict + returned: Always + contains: + abandoned: + description: "" + type: str + returned: Always + dynamic: + description: "" + type: str + returned: Always + static: + description: "" + type: str + returned: Always + total: + description: "" + type: str + returned: Always + used: + description: "" + 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 RangeApi +except ImportError: + pass # Handled by BloxoneAnsibleModule + + +class RangeInfoModule(BloxoneAnsibleModule): + def __init__(self, *args, **kwargs): + super(RangeInfoModule, self).__init__(*args, **kwargs) + self._existing = None + self._limit = 1000 + + def find_by_id(self): + try: + resp = RangeApi(self.client).read(self.params["id"], inherit="full") + 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 = RangeApi(self.client).list( + offset=offset, limit=self._limit, filter=filter_str, tfilter=tag_filter_str, inherit="full" + ) + 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), + inherit=dict(type="str", required=False, choices=["full", "partial", "none"], default="full"), + tag_filters=dict(type="dict", required=False), + tag_filter_query=dict(type="str", required=False), + ) + + module = RangeInfoModule( + 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_range/meta/main.yml b/tests/integration/targets/ipam_range/meta/main.yml new file mode 100644 index 0000000..33e5eb9 --- /dev/null +++ b/tests/integration/targets/ipam_range/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [setup_ip_space, setup_subnet] \ No newline at end of file diff --git a/tests/integration/targets/ipam_range/tasks/main.yml b/tests/integration/targets/ipam_range/tasks/main.yml new file mode 100644 index 0000000..fff77bc --- /dev/null +++ b/tests/integration/targets/ipam_range/tasks/main.yml @@ -0,0 +1,283 @@ +--- +# TODO: Add tests for DHCP Options the ipam_range module +- 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: + tag_value: "site-{{ 999999 | random | string }}" + range_start: "10.0.0.1" + range_end: "10.0.0.254" + + - name: "Create a Range (check mode)" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "present" + check_mode: true + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is changed + - range is not failed + - range_info.objects | length == 0 + + - name: "Create a Range" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "present" + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is not failed + - range_info.objects | length == 1 + - range_info.objects[0].id == range.id + - range_info.objects[0].start == range.object.start + - range_info.objects[0].end == range.object.end + + - name: "Create a Range (idempotent)" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "present" + register: range + - assert: + that: + - range is not changed + - range is not failed + + - name: "Delete a Range (check mode)" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "absent" + check_mode: true + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is changed + - range is not failed + - range_info.objects | length == 1 + + - name: "Delete a Range" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "absent" + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is changed + - range is not failed + - range_info.objects | length == 0 + + - name: "Delete a Range (idempotent)" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "absent" + register: range + - assert: + that: + - range is not changed + - range is not failed + + - name: "Create a Range with comment" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "present" + comment: "Comment." + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is not failed + - range_info.objects | length == 1 + - range_info.objects[0].comment == range.object.comment + + - name: "Create a Range with DHCP disabled" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + disable_dhcp: "true" + state: "present" + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is not failed + - range_info.objects | length == 1 + - range_info.objects[0].disable_dhcp == true + + - name: "Create a Range with tags" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + tags: + location: "{{ tag_value }}" + state: "present" + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is not failed + - range_info.objects | length == 1 + - range_info.objects[0].tags.location == tag_value + + # Test case for creating a range with a name + - name: "Create a Range with name" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "present" + name: "Test Range" + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is not failed + - range_info.objects | length == 1 + - range_info.objects[0].name == "Test Range" + + # Test case for creating a range with exclusion ranges + - name: "Create a Range with exclusion ranges" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "present" + exclusion_ranges: + - start: "10.0.0.10" + end: "10.0.0.20" + comment: "Exclude this range" + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is not failed + - range_info.objects | length == 1 + - range_info.objects[0].exclusion_ranges[0].start == "10.0.0.10" + - range_info.objects[0].exclusion_ranges[0].end == "10.0.0.20" + - range_info.objects[0].exclusion_ranges[0].comment == "Exclude this range" + + # Test case for creating a range with a threshold + - name: "Create a Range with threshold" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "present" + threshold: + enabled: true + high: 90 + low: 10 + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is not failed + - range_info.objects | length == 1 + - range_info.objects[0].threshold.enabled == true + - range_info.objects[0].threshold.high == 90 + - range_info.objects[0].threshold.low == 10 + + always: + # Cleanup if the test fails + - name: "Delete a Range" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "absent" + + - name: "Delete Subnet" + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ _ip_space.id }}" + state: "absent" + ignore_errors: true + + - name: "Delete IP Space" + infoblox.bloxone.ipam_ip_space: + name: "{{ name }}" + state: "absent" + ignore_errors: true \ No newline at end of file diff --git a/tests/integration/targets/ipam_range_info/meta/main.yml b/tests/integration/targets/ipam_range_info/meta/main.yml new file mode 100644 index 0000000..33e5eb9 --- /dev/null +++ b/tests/integration/targets/ipam_range_info/meta/main.yml @@ -0,0 +1,2 @@ +--- +dependencies: [setup_ip_space, setup_subnet] \ No newline at end of file diff --git a/tests/integration/targets/ipam_range_info/tasks/main.yml b/tests/integration/targets/ipam_range_info/tasks/main.yml new file mode 100644 index 0000000..950031a --- /dev/null +++ b/tests/integration/targets/ipam_range_info/tasks/main.yml @@ -0,0 +1,84 @@ +--- +- 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: + tag_value: "site-{{ 999999 | random | string }}" + range_start: "10.0.0.1" + range_end: "10.0.0.254" + + - name: "Create an IP Range" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + tags: + location: "{{ tag_value }}" + state: "present" + register: ip_range + + - name: Get IP Range information by ID + infoblox.bloxone.ipam_range_info: + id: "{{ ip_range.id }}" + register: range_info + - assert: + that: + - range_info.objects | length == 1 + - range_info.objects[0].name == ip_range.object.name + + - name: Get IP Range information by filters + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range_info.objects | length == 1 + - range_info.objects[0].id == ip_range.id + + - name: Get IP Range information by filter query + infoblox.bloxone.ipam_range_info: + filter_query: "start=='{{ range_start }}' and end=='{{ range_end }}' and space=='{{ _ip_space.id }}'" + register: range_info + - assert: + that: + - range_info.objects | length == 1 + - range_info.objects[0].id == ip_range.id + + - name: Get IP Range information by tag filters + infoblox.bloxone.ipam_range_info: + tag_filters: + location: "{{ tag_value }}" + register: range_info + - assert: + that: + - range_info.objects | length == 1 + - range_info.objects[0].id == ip_range.id + + always: + # Cleanup if the test fails + - name: "Delete IP Range" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + state: "absent" + ignore_errors: true + + - name: "Delete Subnet" + infoblox.bloxone.ipam_subnet: + address: "10.0.0.0/24" + space: "{{ _ip_space.id }}" + state: "absent" + ignore_errors: true + + - name: "Delete IP Space" + infoblox.bloxone.ipam_ip_space: + name: "{{ name }}" + state: "absent" + ignore_errors: true \ No newline at end of file diff --git a/tests/integration/targets/setup_ip_space/tasks/main.yml b/tests/integration/targets/setup_ip_space/tasks/main.yml index cda303b..43ef9f3 100644 --- a/tests/integration/targets/setup_ip_space/tasks/main.yml +++ b/tests/integration/targets/setup_ip_space/tasks/main.yml @@ -6,7 +6,7 @@ block: # Create a random View name to avoid conflicts - ansible.builtin.set_fact: - name: "test-view-{{ 999999 | random | string }}" + name: "test-space-{{ 999999 | random | string }}" # Create an Ip Space - name: "Create an IP space" diff --git a/tests/integration/targets/setup_subnet/tasks/main.yml b/tests/integration/targets/setup_subnet/tasks/main.yml index 25e7962..c76f7ac 100644 --- a/tests/integration/targets/setup_subnet/tasks/main.yml +++ b/tests/integration/targets/setup_subnet/tasks/main.yml @@ -4,10 +4,6 @@ csp_url: "{{ csp_url }}" api_key: "{{ api_key }}" block: - # Create a random View name to avoid conflicts - - ansible.builtin.set_fact: - name: "test-view-{{ 999999 | random | string }}" - # Create a subnet - name: "Create a Subnet" infoblox.bloxone.ipam_subnet: From e71e5dfb571ec8b24c6104e8f9b095b14e5fb436 Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Tue, 3 Dec 2024 11:37:44 +0530 Subject: [PATCH 6/9] fixed lint issue and empty lines. --- meta/runtime.yml | 11 - plugins/modules/ipam_fixed_address.py | 783 ------------------ plugins/modules/ipam_fixed_address_info.py | 497 ----------- plugins/modules/ipam_range.py | 92 +- .../targets/ipam_fixed_address/meta/main.yml | 2 - .../targets/ipam_fixed_address/tasks/main.yml | 198 ----- .../ipam_fixed_address_info/tasks/main.yml | 107 --- .../targets/ipam_range/meta/main.yml | 2 +- .../targets/ipam_range/tasks/main.yml | 2 +- .../targets/ipam_range_info/meta/main.yml | 2 +- .../targets/ipam_range_info/tasks/main.yml | 2 +- .../targets/setup_ip_space/tasks/main.yml | 2 +- 12 files changed, 65 insertions(+), 1635 deletions(-) delete mode 100644 plugins/modules/ipam_fixed_address.py delete mode 100644 plugins/modules/ipam_fixed_address_info.py delete mode 100644 tests/integration/targets/ipam_fixed_address/meta/main.yml delete mode 100644 tests/integration/targets/ipam_fixed_address/tasks/main.yml delete mode 100644 tests/integration/targets/ipam_fixed_address_info/tasks/main.yml diff --git a/meta/runtime.yml b/meta/runtime.yml index 76f9573..c4b6916 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -18,8 +18,6 @@ action_groups: - ipam_subnet_info - ipam_address_block - ipam_address_block_info - - ipam_fixed_address - - ipam_fixed_address_info - ipam_range - ipam_range_info @@ -69,12 +67,3 @@ plugin_routing: deprecation: removal_version: 3.0.0 warning_text: Use infoblox.bloxone.dns_auth_zone_info instead. - - b1_ipam_fixed_address: - deprecation: - removal_version: 3.0.0 - warning_text: Use infoblox.bloxone.ipam_fixed_address instead. - b1_ipam_fixed_address_gather: - deprecation: - removal_version: 3.0.0 - warning_text: Use infoblox.bloxone.ipam_fixed_address_info instead. diff --git a/plugins/modules/ipam_fixed_address.py b/plugins/modules/ipam_fixed_address.py deleted file mode 100644 index a5ec828..0000000 --- a/plugins/modules/ipam_fixed_address.py +++ /dev/null @@ -1,783 +0,0 @@ -#!/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_fixed_address -short_description: Manage FixedAddress -description: - - Manage FixedAddress -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 - address: - description: - - "The reserved address." - type: str - comment: - description: - - "The description for the fixed address. May contain 0 to 1024 characters. Can include UTF-8." - type: str - dhcp_options: - description: - - "The list of DHCP options. May be either a specific option or a group of options." - type: list - elements: dict - suboptions: - group: - description: - - "The resource identifier." - type: str - option_code: - description: - - "The resource identifier." - type: str - option_value: - description: - - "The option value." - type: str - type: - description: - - "The type of item." - - "Valid values are:" - - "* I(group)" - - "* I(option)" - type: str - disable_dhcp: - description: - - "Optional. I(true) to disable object. The fixed address is converted to an exclusion when generating configuration." - - "Defaults to I(false)." - type: bool - header_option_filename: - description: - - "The configuration for header option filename field." - type: str - header_option_server_address: - description: - - "The configuration for header option server address field." - type: str - header_option_server_name: - description: - - "The configuration for header option server name field." - type: str - hostname: - description: - - "The DHCP host name associated with this fixed address. It is of FQDN type and it defaults to empty." - type: str - inheritance_parent: - description: - - "The resource identifier." - type: str - inheritance_sources: - description: - - "The inheritance configuration." - type: dict - suboptions: - dhcp_options: - description: - - "The inheritance configuration for I(dhcp_options) field." - type: dict - suboptions: - action: - description: - - "The inheritance setting." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(block): Don't use the inherited value." - - "Defaults to I(inherit)." - type: str - value: - description: - - "The inherited DHCP option values." - type: list - elements: dict - suboptions: - action: - description: - - "The inheritance setting." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(block): Don't use the inherited value." - - "Defaults to I(inherit)." - type: str - header_option_filename: - description: - - "The inheritance configuration for I(header_option_filename) field." - type: dict - suboptions: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - header_option_server_address: - description: - - "The inheritance configuration for I(header_option_server_address) field." - type: dict - suboptions: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - header_option_server_name: - description: - - "The inheritance configuration for I(header_option_server_name) field." - type: dict - suboptions: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - ip_space: - description: - - "The resource identifier." - type: str - match_type: - description: - - "Indicates how to match the client:" - - "* I(mac): match the client MAC address for both IPv4 and IPv6," - - "* I(client_text) or I(client_hex): match the client identifier for IPv4 only," - - "* I(relay_text) or I(relay_hex): match the circuit ID or remote ID in the DHCP relay agent option (82) for IPv4 only," - - "* I(duid): match the DHCP unique identifier, currently match only for IPv6 protocol." - type: str - match_value: - description: - - "The value to match." - type: str - name: - description: - - "The name of the fixed address. May contain 1 to 256 characters. Can include UTF-8." - type: str - parent: - description: - - "The resource identifier." - type: str - tags: - description: - - "The tags for the fixed address in JSON format." - type: dict - -extends_documentation_fragment: - - infoblox.bloxone.common -""" # noqa: E501 - -EXAMPLES = r""" -- name: Create a fixed address - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - ip_space: "example_ip_space" - match_type: "mac" - match_value: "00:00:00:00:00:00" - state: "present" - -- name: Create a fixed address with all parameters - infoblox.bloxone.ipam_fixed_address: - id: "fixed_address_id" - state: "present" - address: "10.0.0.1" - comment: "This is a fixed address" - dhcp_options: - - group: "group1" - option_code: "code1" - option_value: "value1" - type: "option" - disable_dhcp: false - header_option_filename: "filename" - header_option_server_address: "server_address" - header_option_server_name: "server_name" - hostname: "hostname.example.com" - inheritance_parent: "parent_id" - inheritance_sources: - dhcp_options: - action: "inherit" - value: - - action: "inherit" - header_option_filename: - action: "inherit" - header_option_server_address: - action: "inherit" - header_option_server_name: - action: "inherit" - ip_space: "example_ip_space" - match_type: "mac" - match_value: "00:00:00:00:00:00" - name: "fixed_address_name" - parent: "parent_id" - tags: - key1: "value1" - key2: "value2" - -- name: Delete a fixed address - infoblox.bloxone.ipam_fixed_address: - id: "fixed_address_id" - match_type: "mac" - match_value: "00:00:00:00:00:00" - state: "absent" -""" - -RETURN = r""" -id: - description: - - ID of the FixedAddress object - type: str - returned: Always -item: - description: - - FixedAddress object - type: complex - returned: Always - contains: - address: - description: - - "The reserved address." - type: str - returned: Always - comment: - description: - - "The description for the fixed address. May contain 0 to 1024 characters. Can include UTF-8." - type: str - returned: Always - compartment_id: - description: - - "The compartment associated with the object. If no compartment is associated with the object, the value defaults to empty." - type: str - returned: Always - created_at: - description: - - "Time when the object has been created." - type: str - returned: Always - dhcp_options: - description: - - "The list of DHCP options. May be either a specific option or a group of options." - type: list - returned: Always - elements: dict - contains: - group: - description: - - "The resource identifier." - type: str - returned: Always - option_code: - description: - - "The resource identifier." - type: str - returned: Always - option_value: - description: - - "The option value." - type: str - returned: Always - type: - description: - - "The type of item." - - "Valid values are:" - - "* I(group)" - - "* I(option)" - type: str - returned: Always - disable_dhcp: - description: - - "Optional. I(true) to disable object. The fixed address is converted to an exclusion when generating configuration." - - "Defaults to I(false)." - type: bool - returned: Always - header_option_filename: - description: - - "The configuration for header option filename field." - type: str - returned: Always - header_option_server_address: - description: - - "The configuration for header option server address field." - type: str - returned: Always - header_option_server_name: - description: - - "The configuration for header option server name field." - type: str - returned: Always - hostname: - description: - - "The DHCP host name associated with this fixed address. It is of FQDN type and it defaults to empty." - type: str - returned: Always - id: - description: - - "The resource identifier." - type: str - returned: Always - inheritance_assigned_hosts: - description: - - "The list of the inheritance assigned hosts of the object." - type: list - returned: Always - elements: dict - contains: - display_name: - description: - - "The human-readable display name for the host referred to by I(ophid)." - type: str - returned: Always - host: - description: - - "The resource identifier." - type: str - returned: Always - ophid: - description: - - "The on-prem host ID." - type: str - returned: Always - inheritance_parent: - description: - - "The resource identifier." - type: str - returned: Always - inheritance_sources: - description: - - "The inheritance configuration." - type: dict - returned: Always - contains: - dhcp_options: - description: - - "The inheritance configuration for I(dhcp_options) field." - type: dict - returned: Always - contains: - action: - description: - - "The inheritance setting." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(block): Don't use the inherited value." - - "Defaults to I(inherit)." - type: str - returned: Always - value: - description: - - "The inherited DHCP option values." - type: list - returned: Always - elements: dict - contains: - action: - description: - - "The inheritance setting." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(block): Don't use the inherited value." - - "Defaults to I(inherit)." - type: str - returned: Always - display_name: - description: - - "The human-readable display name for the object referred to by I(source)." - type: str - returned: Always - source: - description: - - "The resource identifier." - type: str - returned: Always - value: - description: - - "The inherited value for the DHCP option." - type: dict - returned: Always - contains: - option: - description: - - "Option inherited from the ancestor." - type: dict - returned: Always - contains: - group: - description: - - "The resource identifier." - type: str - returned: Always - option_code: - description: - - "The resource identifier." - type: str - returned: Always - option_value: - description: - - "The option value." - type: str - returned: Always - type: - description: - - "The type of item." - - "Valid values are:" - - "* I(group)" - - "* I(option)" - type: str - returned: Always - overriding_group: - description: - - "The resource identifier." - type: str - returned: Always - header_option_filename: - description: - - "The inheritance configuration for I(header_option_filename) field." - type: dict - returned: Always - contains: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - returned: Always - display_name: - description: - - "The human-readable display name for the object referred to by I(source)." - type: str - returned: Always - source: - description: - - "The resource identifier." - type: str - returned: Always - value: - description: - - "The inherited value." - type: str - returned: Always - header_option_server_address: - description: - - "The inheritance configuration for I(header_option_server_address) field." - type: dict - returned: Always - contains: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - returned: Always - display_name: - description: - - "The human-readable display name for the object referred to by I(source)." - type: str - returned: Always - source: - description: - - "The resource identifier." - type: str - returned: Always - value: - description: - - "The inherited value." - type: str - returned: Always - header_option_server_name: - description: - - "The inheritance configuration for I(header_option_server_name) field." - type: dict - returned: Always - contains: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - returned: Always - display_name: - description: - - "The human-readable display name for the object referred to by I(source)." - type: str - returned: Always - source: - description: - - "The resource identifier." - type: str - returned: Always - value: - description: - - "The inherited value." - type: str - returned: Always - ip_space: - description: - - "The resource identifier." - type: str - returned: Always - match_type: - description: - - "Indicates how to match the client:" - - "* I(mac): match the client MAC address for both IPv4 and IPv6," - - "* I(client_text) or I(client_hex): match the client identifier for IPv4 only," - - "* I(relay_text) or I(relay_hex): match the circuit ID or remote ID in the DHCP relay agent option (82) for IPv4 only," - - "* I(duid): match the DHCP unique identifier, currently match only for IPv6 protocol." - type: str - returned: Always - match_value: - description: - - "The value to match." - type: str - returned: Always - name: - description: - - "The name of the fixed address. May contain 1 to 256 characters. Can include UTF-8." - type: str - returned: Always - parent: - description: - - "The resource identifier." - type: str - returned: Always - tags: - description: - - "The tags for the fixed address 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 FixedAddress, FixedAddressApi -except ImportError: - pass # Handled by BloxoneAnsibleModule - - -class FixedAddressModule(BloxoneAnsibleModule): - def __init__(self, *args, **kwargs): - super(FixedAddressModule, 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 = FixedAddress.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 = FixedAddressApi(self.client).read(self.params["id"], inherit="full") - return resp.result - except NotFoundException as e: - if self.params["state"] == "absent": - return None - raise e - else: - filter = f"address=='{self.params['address']}' and ip_space=='{self.params['ip_space']}'" - resp = FixedAddressApi(self.client).list(filter=filter, inherit="full") - if len(resp.results) == 1: - return resp.results[0] - if len(resp.results) > 1: - self.fail_json(msg=f"Found multiple FixedAddress: {resp.results}") - if len(resp.results) == 0: - return None - - def create(self): - if self.check_mode: - return None - - resp = FixedAddressApi(self.client).create(body=self.payload, inherit="full") - return resp.result.model_dump(by_alias=True, exclude_none=True) - - def update(self): - if self.check_mode: - return None - - resp = FixedAddressApi(self.client).update(id=self.existing.id, body=self.payload, inherit="full") - return resp.result.model_dump(by_alias=True, exclude_none=True) - - def delete(self): - if self.check_mode: - return - - FixedAddressApi(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"] = "FixedAddress created" - elif self.params["state"] == "present" and self.existing is not None: - if self.payload_changed(): - item = self.update() - result["changed"] = True - result["msg"] = "FixedAddress updated" - elif self.params["state"] == "absent" and self.existing is not None: - self.delete() - result["changed"] = True - result["msg"] = "FixedAddress 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"), - address=dict(type="str"), - comment=dict(type="str"), - dhcp_options=dict( - type="list", - elements="dict", - options=dict( - group=dict(type="str"), - option_code=dict(type="str"), - option_value=dict(type="str"), - type=dict(type="str"), - ), - ), - disable_dhcp=dict(type="bool"), - header_option_filename=dict(type="str"), - header_option_server_address=dict(type="str"), - header_option_server_name=dict(type="str"), - hostname=dict(type="str"), - inheritance_parent=dict(type="str"), - inheritance_sources=dict( - type="dict", - options=dict( - dhcp_options=dict( - type="dict", - options=dict( - action=dict(type="str"), - value=dict( - type="list", - elements="dict", - options=dict( - action=dict(type="str"), - ), - ), - ), - ), - header_option_filename=dict( - type="dict", - options=dict( - action=dict(type="str"), - ), - ), - header_option_server_address=dict( - type="dict", - options=dict( - action=dict(type="str"), - ), - ), - header_option_server_name=dict( - type="dict", - options=dict( - action=dict(type="str"), - ), - ), - ), - ), - ip_space=dict(type="str"), - match_type=dict(type="str"), - match_value=dict(type="str"), - name=dict(type="str"), - parent=dict(type="str"), - tags=dict(type="dict"), - ) - - module = FixedAddressModule( - argument_spec=module_args, - supports_check_mode=True, - required_if=[("state", "present", ["address", "ip_space", "match_type", "match_value"])], - ) - - module.run_command() - - -if __name__ == "__main__": - main() diff --git a/plugins/modules/ipam_fixed_address_info.py b/plugins/modules/ipam_fixed_address_info.py deleted file mode 100644 index 1873ab3..0000000 --- a/plugins/modules/ipam_fixed_address_info.py +++ /dev/null @@ -1,497 +0,0 @@ -#!/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_fixed_address_info -short_description: Manage FixedAddress -description: - - Manage FixedAddress -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 - inherit: - description: - - Return inheritance information - type: str - required: false - choices: - - full - - partial - - none - default: full - 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""" -""" - -RETURN = r""" -id: - description: - - ID of the FixedAddress object - type: str - returned: Always -objects: - description: - - FixedAddress object - type: list - elements: dict - returned: Always - contains: - address: - description: - - "The reserved address." - type: str - returned: Always - comment: - description: - - "The description for the fixed address. May contain 0 to 1024 characters. Can include UTF-8." - type: str - returned: Always - compartment_id: - description: - - "The compartment associated with the object. If no compartment is associated with the object, the value defaults to empty." - type: str - returned: Always - created_at: - description: - - "Time when the object has been created." - type: str - returned: Always - dhcp_options: - description: - - "The list of DHCP options. May be either a specific option or a group of options." - type: list - returned: Always - elements: dict - contains: - group: - description: - - "The resource identifier." - type: str - returned: Always - option_code: - description: - - "The resource identifier." - type: str - returned: Always - option_value: - description: - - "The option value." - type: str - returned: Always - type: - description: - - "The type of item." - - "Valid values are:" - - "* I(group)" - - "* I(option)" - type: str - returned: Always - disable_dhcp: - description: - - "Optional. I(true) to disable object. The fixed address is converted to an exclusion when generating configuration." - - "Defaults to I(false)." - type: bool - returned: Always - header_option_filename: - description: - - "The configuration for header option filename field." - type: str - returned: Always - header_option_server_address: - description: - - "The configuration for header option server address field." - type: str - returned: Always - header_option_server_name: - description: - - "The configuration for header option server name field." - type: str - returned: Always - hostname: - description: - - "The DHCP host name associated with this fixed address. It is of FQDN type and it defaults to empty." - type: str - returned: Always - id: - description: - - "The resource identifier." - type: str - returned: Always - inheritance_assigned_hosts: - description: - - "The list of the inheritance assigned hosts of the object." - type: list - returned: Always - elements: dict - contains: - display_name: - description: - - "The human-readable display name for the host referred to by I(ophid)." - type: str - returned: Always - host: - description: - - "The resource identifier." - type: str - returned: Always - ophid: - description: - - "The on-prem host ID." - type: str - returned: Always - inheritance_parent: - description: - - "The resource identifier." - type: str - returned: Always - inheritance_sources: - description: - - "The inheritance configuration." - type: dict - returned: Always - contains: - dhcp_options: - description: - - "The inheritance configuration for I(dhcp_options) field." - type: dict - returned: Always - contains: - action: - description: - - "The inheritance setting." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(block): Don't use the inherited value." - - "Defaults to I(inherit)." - type: str - returned: Always - value: - description: - - "The inherited DHCP option values." - type: list - returned: Always - elements: dict - contains: - action: - description: - - "The inheritance setting." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(block): Don't use the inherited value." - - "Defaults to I(inherit)." - type: str - returned: Always - display_name: - description: - - "The human-readable display name for the object referred to by I(source)." - type: str - returned: Always - source: - description: - - "The resource identifier." - type: str - returned: Always - value: - description: - - "The inherited value for the DHCP option." - type: dict - returned: Always - contains: - option: - description: - - "Option inherited from the ancestor." - type: dict - returned: Always - contains: - group: - description: - - "The resource identifier." - type: str - returned: Always - option_code: - description: - - "The resource identifier." - type: str - returned: Always - option_value: - description: - - "The option value." - type: str - returned: Always - type: - description: - - "The type of item." - - "Valid values are:" - - "* I(group)" - - "* I(option)" - type: str - returned: Always - overriding_group: - description: - - "The resource identifier." - type: str - returned: Always - header_option_filename: - description: - - "The inheritance configuration for I(header_option_filename) field." - type: dict - returned: Always - contains: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - returned: Always - display_name: - description: - - "The human-readable display name for the object referred to by I(source)." - type: str - returned: Always - source: - description: - - "The resource identifier." - type: str - returned: Always - value: - description: - - "The inherited value." - type: str - returned: Always - header_option_server_address: - description: - - "The inheritance configuration for I(header_option_server_address) field." - type: dict - returned: Always - contains: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - returned: Always - display_name: - description: - - "The human-readable display name for the object referred to by I(source)." - type: str - returned: Always - source: - description: - - "The resource identifier." - type: str - returned: Always - value: - description: - - "The inherited value." - type: str - returned: Always - header_option_server_name: - description: - - "The inheritance configuration for I(header_option_server_name) field." - type: dict - returned: Always - contains: - action: - description: - - "The inheritance setting for a field." - - "Valid values are:" - - "* I(inherit): Use the inherited value." - - "* I(override): Use the value set in the object." - - "Defaults to I(inherit)." - type: str - returned: Always - display_name: - description: - - "The human-readable display name for the object referred to by I(source)." - type: str - returned: Always - source: - description: - - "The resource identifier." - type: str - returned: Always - value: - description: - - "The inherited value." - type: str - returned: Always - ip_space: - description: - - "The resource identifier." - type: str - returned: Always - match_type: - description: - - "Indicates how to match the client:" - - "* I(mac): match the client MAC address for both IPv4 and IPv6," - - "* I(client_text) or I(client_hex): match the client identifier for IPv4 only," - - "* I(relay_text) or I(relay_hex): match the circuit ID or remote ID in the DHCP relay agent option (82) for IPv4 only," - - "* I(duid): match the DHCP unique identifier, currently match only for IPv6 protocol." - type: str - returned: Always - match_value: - description: - - "The value to match." - type: str - returned: Always - name: - description: - - "The name of the fixed address. May contain 1 to 256 characters. Can include UTF-8." - type: str - returned: Always - parent: - description: - - "The resource identifier." - type: str - returned: Always - tags: - description: - - "The tags for the fixed address 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 FixedAddressApi -except ImportError: - pass # Handled by BloxoneAnsibleModule - - -class FixedAddressInfoModule(BloxoneAnsibleModule): - def __init__(self, *args, **kwargs): - super(FixedAddressInfoModule, self).__init__(*args, **kwargs) - self._existing = None - self._limit = 1000 - - def find_by_id(self): - try: - resp = FixedAddressApi(self.client).read(self.params["id"], inherit="full") - 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 = FixedAddressApi(self.client).list( - offset=offset, limit=self._limit, filter=filter_str, tfilter=tag_filter_str, inherit="full" - ) - 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), - inherit=dict(type="str", required=False, choices=["full", "partial", "none"], default="full"), - tag_filters=dict(type="dict", required=False), - tag_filter_query=dict(type="str", required=False), - ) - - module = FixedAddressInfoModule( - 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/ipam_range.py b/plugins/modules/ipam_range.py index 41fd902..4cdb745 100644 --- a/plugins/modules/ipam_range.py +++ b/plugins/modules/ipam_range.py @@ -651,7 +651,9 @@ def find(self): return None raise e else: - filter = f"start=='{self.params['start']}' and end=='{self.params['end']}' and space=='{self.params['space']}'" + filter = ( + f"start=='{self.params['start']}' and end=='{self.params['end']}' and space=='{self.params['space']}'" + ) resp = RangeApi(self.client).list(filter=filter, inherit="full") if len(resp.results) == 1: return resp.results[0] @@ -713,7 +715,9 @@ def run_command(self): 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 + 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}") @@ -726,44 +730,68 @@ def main(): state=dict(type="str", required=False, choices=["present", "absent"], default="present"), comment=dict(type="str"), dhcp_host=dict(type="str"), - dhcp_options=dict(type="list", elements="dict", options=dict( - group=dict(type="str"), - option_code=dict(type="str"), - option_value=dict(type="str"), - type=dict(type="str"), - )), + dhcp_options=dict( + type="list", + elements="dict", + options=dict( + group=dict(type="str"), + option_code=dict(type="str"), + option_value=dict(type="str"), + type=dict(type="str"), + ), + ), disable_dhcp=dict(type="bool"), end=dict(type="str"), - exclusion_ranges=dict(type="list", elements="dict", options=dict( - comment=dict(type="str"), - end=dict(type="str"), - start=dict(type="str"), - )), - filters=dict(type="list", elements="dict", options=dict( - access=dict(type="str"), - hardware_filter_id=dict(type="str"), - option_filter_id=dict(type="str"), - )), + exclusion_ranges=dict( + type="list", + elements="dict", + options=dict( + comment=dict(type="str"), + end=dict(type="str"), + start=dict(type="str"), + ), + ), + filters=dict( + type="list", + elements="dict", + options=dict( + access=dict(type="str"), + hardware_filter_id=dict(type="str"), + option_filter_id=dict(type="str"), + ), + ), inheritance_parent=dict(type="str"), - inheritance_sources=dict(type="dict", options=dict( - dhcp_options=dict(type="dict", options=dict( - action=dict(type="str"), - value=dict(type="list", elements="dict", options=dict( - action=dict(type="str"), - )), - )), - )), + inheritance_sources=dict( + type="dict", + options=dict( + dhcp_options=dict( + type="dict", + options=dict( + action=dict(type="str"), + value=dict( + type="list", + elements="dict", + options=dict( + action=dict(type="str"), + ), + ), + ), + ), + ), + ), name=dict(type="str"), parent=dict(type="str"), space=dict(type="str"), start=dict(type="str"), tags=dict(type="dict"), - threshold=dict(type="dict", options=dict( - enabled=dict(type="bool"), - high=dict(type="int"), - low=dict(type="int"), - )), - + threshold=dict( + type="dict", + options=dict( + enabled=dict(type="bool"), + high=dict(type="int"), + low=dict(type="int"), + ), + ), ) module = RangeModule( diff --git a/tests/integration/targets/ipam_fixed_address/meta/main.yml b/tests/integration/targets/ipam_fixed_address/meta/main.yml deleted file mode 100644 index 33e5eb9..0000000 --- a/tests/integration/targets/ipam_fixed_address/meta/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -dependencies: [setup_ip_space, setup_subnet] \ No newline at end of file diff --git a/tests/integration/targets/ipam_fixed_address/tasks/main.yml b/tests/integration/targets/ipam_fixed_address/tasks/main.yml deleted file mode 100644 index 43f2215..0000000 --- a/tests/integration/targets/ipam_fixed_address/tasks/main.yml +++ /dev/null @@ -1,198 +0,0 @@ ---- -- 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: - name: "test-ip-space-{{ 999999 | random | string }}" - tag_value: "site-{{ 999999 | random | string }}" - - - name: "Create a Fixed Address" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - name: "test_fixed_address_ansible" - match_type: "mac" - match_value: "00:00:00:00:00:00" - ip_space: "{{_ip_space.id }}" - state: "present" - register: fixed_address - - - name: Get information about the Fixed Address - infoblox.bloxone.ipam_fixed_address_info: - filters: - address: "10.0.0.1" - ip_space: "{{ _ip_space.id }}" - register: fixed_address_info - - - assert: - that: - - fixed_address is not failed - - fixed_address_info.objects | length == 1 - - fixed_address_info.objects[0].id == fixed_address.id - - fixed_address_info.objects[0].address == fixed_address.object.address - - - name: "Create a Fixed Address (idempotent)" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - match_type: "mac" - match_value: "00:00:00:00:00:00" - ip_space: "{{ _ip_space.id }}" - state: "present" - register: fixed_address - - assert: - that: - - fixed_address is not changed - - fixed_address is not failed - - - name: "Delete a Fixed Address" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:00" - state: "absent" - register: fixed_address - - assert: - that: - - fixed_address is changed - - fixed_address is not failed - - - name: Get information about the Fixed Address - infoblox.bloxone.ipam_fixed_address_info: - filters: - address: "10.0.0.10" - ip_space: "{{ _ip_space.id }}" - register: fixed_address_info - - assert: - that: - - fixed_address is changed - - fixed_address is not failed - - fixed_address_info.objects | length == 0 - - - name: "Delete a Fixed Address (idempotent)" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:00" - state: "absent" - register: fixed_address - - assert: - that: - - fixed_address is not changed - - fixed_address is not failed - - - name: "Create a Fixed Address with Comment" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.2" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:01" - comment: "test comment" - state: "present" - - register: fixed_address_comment - - assert: - that: - - fixed_address_comment is changed - - fixed_address_comment is not failed - - fixed_address_comment.object.comment == "test comment" - - fixed_address_comment.object.address == "10.0.0.2" - - fixed_address_comment.object.match_type == "mac" - - fixed_address_comment.object.match_value == "00:00:00:00:00:01" - - fixed_address_comment.object.ip_space == _ip_space.id - - - name: "Delete a Fixed Address with Comment" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.2" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:01" - state: "absent" - - - name: "Create a Fixed Address with disable dhcp" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.2" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:01" - comment: "test comment" - disable_dhcp: true - state: "present" - - register: fixed_address_disable_dhcp - - assert: - that: - - fixed_address_disable_dhcp is changed - - fixed_address_disable_dhcp is not failed - - fixed_address_disable_dhcp.object.comment == "test comment" - - fixed_address_disable_dhcp.object.address == "10.0.0.2" - - fixed_address_disable_dhcp.object.match_type == "mac" - - fixed_address_disable_dhcp.object.match_value == "00:00:00:00:00:01" - - fixed_address_disable_dhcp.object.ip_space == _ip_space.id - - fixed_address_disable_dhcp.object.disable_dhcp == true - - - name: "Delete a Fixed Address with disable dhcp" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.2" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:01" - state: "absent" - - - name: "Create a Fixed Address with host name" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.2" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:01" - comment: "test comment" - disable_dhcp: true - hostname: "test-host-name" - state: "present" - register: fixed_address_hostname - - assert: - that: - - fixed_address_hostname is changed - - fixed_address_hostname is not failed - - fixed_address_hostname.object.comment == "test comment" - - fixed_address_hostname.object.address == "10.0.0.2" - - fixed_address_hostname.object.match_type == "mac" - - fixed_address_hostname.object.match_value == "00:00:00:00:00:01" - - fixed_address_hostname.object.ip_space == _ip_space.id - - fixed_address_hostname.object.disable_dhcp == true - - fixed_address_hostname.object.hostname == "test-host-name" - - - name: "Delete a Fixed Address with host name" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.2" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:01" - state: "absent" - - always: - - name: "Delete Fixed Address" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - ip_space: "{{ _ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:00" - state: "present" - ignore_errors: true - - - name: "Delete Subnet" - infoblox.bloxone.ipam_subnet: - address: "10.0.0.0/24" - space: "{{ _ip_space.id }}" - state: "absent" - ignore_errors: true - - - name: "Delete IP Space" - infoblox.bloxone.ipam_ip_space: - name: "{{ name }}" - state: "absent" - ignore_errors: true diff --git a/tests/integration/targets/ipam_fixed_address_info/tasks/main.yml b/tests/integration/targets/ipam_fixed_address_info/tasks/main.yml deleted file mode 100644 index 0573598..0000000 --- a/tests/integration/targets/ipam_fixed_address_info/tasks/main.yml +++ /dev/null @@ -1,107 +0,0 @@ ---- -- 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: - name: "test-ip-space-{{ 999999 | random | string }}" - tag_value: "site-{{ 999999 | random | string }}" - - # Basic tests for IP Space and Subnet - - 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 - - # Create a Fixed Address - - name: "Create a Fixed Address" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - name: "test_fixed_address_ansible" - match_type: "mac" - match_value: "00:00:00:00:00:00" - ip_space: "{{ ip_space.id }}" - state: "present" - register: fixed_address - - # Get information about the Fixed Address - - name: Get information about the Fixed Address - infoblox.bloxone.ipam_fixed_address_info: - filters: - address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" - register: fixed_address_info - - - assert: - that: - - fixed_address_info.objects | length == 1 - - fixed_address_info.objects[0].id == fixed_address.id - - fixed_address_info.objects[0].address == fixed_address.object.address - - # Test idempotency of Fixed Address Info - - name: "Get Fixed Address Info (idempotent)" - infoblox.bloxone.ipam_fixed_address_info: - filters: - address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" - register: fixed_address_info - - assert: - that: - - fixed_address_info.objects | length == 1 - - fixed_address_info.objects[0].id == fixed_address.id - - fixed_address_info.objects[0].address == fixed_address.object.address - - # Delete the Fixed Address - - name: "Delete a Fixed Address" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:00" - state: "absent" - register: fixed_address - - # Verify the Fixed Address is deleted - - name: Get information about the Fixed Address - infoblox.bloxone.ipam_fixed_address_info: - filters: - address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" - register: fixed_address_info - - assert: - that: - - fixed_address_info.objects | length == 0 - - always: - - name: "Delete Fixed Address" - infoblox.bloxone.ipam_fixed_address: - address: "10.0.0.1" - ip_space: "{{ ip_space.id }}" - match_type: "mac" - match_value: "00:00:00:00:00:00" - state: "absent" - ignore_errors: true - - - name: "Delete Subnet" - infoblox.bloxone.ipam_subnet: - address: "10.0.0.0/24" - space: "{{ ip_space.id }}" - state: "absent" - ignore_errors: true - - - name: "Delete IP Space" - infoblox.bloxone.ipam_ip_space: - name: "{{ name }}" - state: "absent" - ignore_errors: true diff --git a/tests/integration/targets/ipam_range/meta/main.yml b/tests/integration/targets/ipam_range/meta/main.yml index 33e5eb9..33b9500 100644 --- a/tests/integration/targets/ipam_range/meta/main.yml +++ b/tests/integration/targets/ipam_range/meta/main.yml @@ -1,2 +1,2 @@ --- -dependencies: [setup_ip_space, setup_subnet] \ No newline at end of file +dependencies: [setup_ip_space, setup_subnet] diff --git a/tests/integration/targets/ipam_range/tasks/main.yml b/tests/integration/targets/ipam_range/tasks/main.yml index fff77bc..989630c 100644 --- a/tests/integration/targets/ipam_range/tasks/main.yml +++ b/tests/integration/targets/ipam_range/tasks/main.yml @@ -280,4 +280,4 @@ infoblox.bloxone.ipam_ip_space: name: "{{ name }}" state: "absent" - ignore_errors: true \ No newline at end of file + ignore_errors: true diff --git a/tests/integration/targets/ipam_range_info/meta/main.yml b/tests/integration/targets/ipam_range_info/meta/main.yml index 33e5eb9..33b9500 100644 --- a/tests/integration/targets/ipam_range_info/meta/main.yml +++ b/tests/integration/targets/ipam_range_info/meta/main.yml @@ -1,2 +1,2 @@ --- -dependencies: [setup_ip_space, setup_subnet] \ No newline at end of file +dependencies: [setup_ip_space, setup_subnet] diff --git a/tests/integration/targets/ipam_range_info/tasks/main.yml b/tests/integration/targets/ipam_range_info/tasks/main.yml index 950031a..71f7bee 100644 --- a/tests/integration/targets/ipam_range_info/tasks/main.yml +++ b/tests/integration/targets/ipam_range_info/tasks/main.yml @@ -81,4 +81,4 @@ infoblox.bloxone.ipam_ip_space: name: "{{ name }}" state: "absent" - ignore_errors: true \ No newline at end of file + 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 index 43ef9f3..439b9f7 100644 --- a/tests/integration/targets/setup_ip_space/tasks/main.yml +++ b/tests/integration/targets/setup_ip_space/tasks/main.yml @@ -13,4 +13,4 @@ infoblox.bloxone.ipam_ip_space: name: "{{ name }}" state: "present" - register: _ip_space \ No newline at end of file + register: _ip_space From 07cb6536a913ceb00b1d2affeb31889984f68eb3 Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Tue, 3 Dec 2024 11:49:54 +0530 Subject: [PATCH 7/9] added change log and deprication notification --- changelogs/fragments/38-fixed-address.yml | 3 --- changelogs/fragments/38-range.yml | 2 ++ meta/runtime.yml | 5 +++++ plugins/modules/b1_ipam_fixed_address.py | 4 ---- plugins/modules/b1_ipam_fixed_address_gather.py | 4 ---- plugins/modules/b1_ipam_range.py | 4 ++++ 6 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 changelogs/fragments/38-fixed-address.yml create mode 100644 changelogs/fragments/38-range.yml diff --git a/changelogs/fragments/38-fixed-address.yml b/changelogs/fragments/38-fixed-address.yml deleted file mode 100644 index c5c20be..0000000 --- a/changelogs/fragments/38-fixed-address.yml +++ /dev/null @@ -1,3 +0,0 @@ -deprecated_features: - - b1_ipam_fixed_address - is deprecated in favor of `ipam_fixed_address`. - - b1_ipam_fixed_address_gather - is deprecated in favor of `ipam_fixed_address_info`. \ No newline at end of file diff --git a/changelogs/fragments/38-range.yml b/changelogs/fragments/38-range.yml new file mode 100644 index 0000000..77f4166 --- /dev/null +++ b/changelogs/fragments/38-range.yml @@ -0,0 +1,2 @@ +deprecated_features: + - b1_ipam_range - is deprecated in favor of `ipam_range`. \ No newline at end of file diff --git a/meta/runtime.yml b/meta/runtime.yml index c4b6916..a022bd8 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -67,3 +67,8 @@ plugin_routing: deprecation: removal_version: 3.0.0 warning_text: Use infoblox.bloxone.dns_auth_zone_info instead. + + b1_ipam_range: + deprecation: + removal_version: 3.0.0 + warning_text: Use infoblox.bloxone.ipam_range instead. \ No newline at end of file diff --git a/plugins/modules/b1_ipam_fixed_address.py b/plugins/modules/b1_ipam_fixed_address.py index f8c9c0e..a102325 100644 --- a/plugins/modules/b1_ipam_fixed_address.py +++ b/plugins/modules/b1_ipam_fixed_address.py @@ -15,10 +15,6 @@ contributor: "Chris Marrison (@ccmarris)" short_description: Configure fixed address on Infoblox BloxOne DDI version_added: "1.1.2" -deprecated: - removed_in: "3.0.0" - why: "This module is being deprecated and will be removed in version 3.0.0." - alternative: "Use the M(ipam_fixed_address) module instead." description: - Get, Create, Update and Delete fixed address on Infoblox BloxOne DDI. This module manages the fixed address object using BloxOne REST APIs. requirements: diff --git a/plugins/modules/b1_ipam_fixed_address_gather.py b/plugins/modules/b1_ipam_fixed_address_gather.py index a71a335..84ed35e 100644 --- a/plugins/modules/b1_ipam_fixed_address_gather.py +++ b/plugins/modules/b1_ipam_fixed_address_gather.py @@ -13,10 +13,6 @@ author: "Amit Mishra (@amishra)" short_description: Configure IP space on Infoblox BloxOne DDI version_added: "1.0.1" -deprecated: - removed_in: "3.0.0" - why: "This module is being deprecated and will be removed in version 3.0.0." - alternative: "Use the M(ipam_fixed_address_info) module instead." description: - Gather information about a fixed address object on Infoblox BloxOne DDI. This module gathers the fixed_address object using BloxOne REST APIs. requirements: diff --git a/plugins/modules/b1_ipam_range.py b/plugins/modules/b1_ipam_range.py index c587dfc..ea0e23a 100644 --- a/plugins/modules/b1_ipam_range.py +++ b/plugins/modules/b1_ipam_range.py @@ -14,6 +14,10 @@ author: "Amit Mishra (@amishra), Sriram Kannan(@kannans)" short_description: Configure the IPAM range on Infoblox BloxOne DDI version_added: "1.0.1" +deprecated: + removed_in: "3.0.0" + why: "This module is being deprecated and will be removed in version 3.0.0." + alternative: "Use the M(ipam_range) module instead." description: - Create, Update and Delete the IPAM range on Infoblox BloxOne DDI. This module manages the IPAM IPAM range object using BloxOne REST APIs. requirements: From 7a9df570f4e87a32d58555ebc9aa771398095ca7 Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Fri, 13 Dec 2024 18:37:19 +0530 Subject: [PATCH 8/9] addressed review comments. --- changelogs/fragments/38-range.yml | 2 - changelogs/fragments/48-range.yml | 2 + meta/runtime.yml | 2 +- plugins/modules/b1_ipam_range.py | 2 +- .../targets/ipam_range/tasks/main.yml | 46 ++++++++++++------- 5 files changed, 34 insertions(+), 20 deletions(-) delete mode 100644 changelogs/fragments/38-range.yml create mode 100644 changelogs/fragments/48-range.yml diff --git a/changelogs/fragments/38-range.yml b/changelogs/fragments/38-range.yml deleted file mode 100644 index 77f4166..0000000 --- a/changelogs/fragments/38-range.yml +++ /dev/null @@ -1,2 +0,0 @@ -deprecated_features: - - b1_ipam_range - is deprecated in favor of `ipam_range`. \ No newline at end of file diff --git a/changelogs/fragments/48-range.yml b/changelogs/fragments/48-range.yml new file mode 100644 index 0000000..ede6c8d --- /dev/null +++ b/changelogs/fragments/48-range.yml @@ -0,0 +1,2 @@ +deprecated_features: + - b1_ipam_range - is deprecated in favor of `ipam_range`. diff --git a/meta/runtime.yml b/meta/runtime.yml index a022bd8..c67e1d7 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -71,4 +71,4 @@ plugin_routing: b1_ipam_range: deprecation: removal_version: 3.0.0 - warning_text: Use infoblox.bloxone.ipam_range instead. \ No newline at end of file + warning_text: Use infoblox.bloxone.ipam_range instead. diff --git a/plugins/modules/b1_ipam_range.py b/plugins/modules/b1_ipam_range.py index ea0e23a..75c9e3d 100644 --- a/plugins/modules/b1_ipam_range.py +++ b/plugins/modules/b1_ipam_range.py @@ -16,7 +16,7 @@ version_added: "1.0.1" deprecated: removed_in: "3.0.0" - why: "This module is being deprecated and will be removed in version 3.0.0." + why: "This module is being deprecated and will be removed in version 3.0.0. Use M(ipam_range) instead." alternative: "Use the M(ipam_range) module instead." description: - Create, Update and Delete the IPAM range on Infoblox BloxOne DDI. This module manages the IPAM IPAM range object using BloxOne REST APIs. diff --git a/tests/integration/targets/ipam_range/tasks/main.yml b/tests/integration/targets/ipam_range/tasks/main.yml index 989630c..d8c00e8 100644 --- a/tests/integration/targets/ipam_range/tasks/main.yml +++ b/tests/integration/targets/ipam_range/tasks/main.yml @@ -6,7 +6,6 @@ api_key: "{{ api_key }}" block: - # Create a random IP space name to avoid conflicts - ansible.builtin.set_fact: tag_value: "site-{{ 999999 | random | string }}" range_start: "10.0.0.1" @@ -184,7 +183,6 @@ - range_info.objects | length == 1 - range_info.objects[0].tags.location == tag_value - # Test case for creating a range with a name - name: "Create a Range with name" infoblox.bloxone.ipam_range: start: "{{ range_start }}" @@ -206,7 +204,6 @@ - range_info.objects | length == 1 - range_info.objects[0].name == "Test Range" - # Test case for creating a range with exclusion ranges - name: "Create a Range with exclusion ranges" infoblox.bloxone.ipam_range: start: "{{ range_start }}" @@ -233,7 +230,6 @@ - range_info.objects[0].exclusion_ranges[0].end == "10.0.0.20" - range_info.objects[0].exclusion_ranges[0].comment == "Exclude this range" - # Test case for creating a range with a threshold - name: "Create a Range with threshold" infoblox.bloxone.ipam_range: start: "{{ range_start }}" @@ -260,8 +256,31 @@ - range_info.objects[0].threshold.high == 90 - range_info.objects[0].threshold.low == 10 + - name: "Create a Range with inheritance sources" + infoblox.bloxone.ipam_range: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + inheritance_sources: + dhcp_options: + action: "block" + state: "present" + register: range + - name: Get information about the Range + infoblox.bloxone.ipam_range_info: + filters: + start: "{{ range_start }}" + end: "{{ range_end }}" + space: "{{ _ip_space.id }}" + register: range_info + - assert: + that: + - range is not failed + - range_info.objects | length == 1 + - range_info.objects[0].inheritance_sources.dhcp_options.action == "block" + + always: - # Cleanup if the test fails - name: "Delete a Range" infoblox.bloxone.ipam_range: start: "{{ range_start }}" @@ -269,15 +288,10 @@ space: "{{ _ip_space.id }}" state: "absent" - - name: "Delete Subnet" - infoblox.bloxone.ipam_subnet: - address: "10.0.0.0/24" - space: "{{ _ip_space.id }}" - state: "absent" - ignore_errors: true + - ansible.builtin.include_role: + name: "setup_subnet" + tasks_from: "cleanup.yml" - - name: "Delete IP Space" - infoblox.bloxone.ipam_ip_space: - name: "{{ name }}" - state: "absent" - ignore_errors: true + - ansible.builtin.include_role: + name: "setup_ip_space" + tasks_from: "cleanup.yml" From aa76eab5b69047bc755ded7a9323644b64f64c12 Mon Sep 17 00:00:00 2001 From: JchhatbarInfoblox Date: Thu, 9 Jan 2025 12:35:20 +0530 Subject: [PATCH 9/9] addressed review comments and added cleanup files. --- plugins/modules/ipam_range.py | 20 +++++++++---------- .../targets/setup_ip_space/tasks/cleanup.yml | 12 +++++++++++ .../targets/setup_subnet/tasks/cleanup.yml | 12 +++++++++++ 3 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 tests/integration/targets/setup_ip_space/tasks/cleanup.yml create mode 100644 tests/integration/targets/setup_subnet/tasks/cleanup.yml diff --git a/plugins/modules/ipam_range.py b/plugins/modules/ipam_range.py index 4cdb745..2153850 100644 --- a/plugins/modules/ipam_range.py +++ b/plugins/modules/ipam_range.py @@ -189,13 +189,13 @@ - infoblox.bloxone.common """ EXAMPLES = r""" - - name: "Create an ip space" + - name: "Create an Ip Space" infoblox.bloxone.ipam_ip_space: name: "my-ip-space" state: "present" register: ip_space - - name: "Create a subnet" + - name: "Create a Subnet" infoblox.bloxone.ipam_subnet: address: "10.0.0.0/24" space: "{{ ip_space.id }}" @@ -208,7 +208,7 @@ space: "{{ ip_space.id }}" disable_dhcp: "true" tags: - location: "some where on earth" + location: "site-1" name: "Example Range" exclusion_ranges: - start: "10.0.0.10" @@ -220,11 +220,11 @@ low: 10 state: "present" - - name: "Delete a Range" + - name: "Delete the Range" infoblox.bloxone.ipam_range: start: "10.0.0.1" end: "10.0.0.100" - space: "{{ _ip_space.id }}" + space: "{{ ip_space.id }}" state: "absent" """ @@ -579,23 +579,23 @@ returned: Always contains: abandoned: - description: "" + description: "The number of IP addresses in the scope of the object which are in the abandoned state (issued by a DHCP server and then declined by the client)." type: str returned: Always dynamic: - description: "" + description: "The number of IP addresses handed out by DHCP in the scope of the object. This includes all leased addresses, fixed addresses that are defined but not currently leased and abandoned leases." type: str returned: Always static: - description: "" + description: "The number of defined IP addresses such as reservations or DNS records. It can be computed as static = used - dynamic." type: str returned: Always total: - description: "" + description: "The total number of IP addresses available in the scope of the object." type: str returned: Always used: - description: "" + description: "The number of IP addresses used in the scope of the object." type: str returned: Always """ # noqa: E501 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 0000000..45096ab --- /dev/null +++ b/tests/integration/targets/setup_ip_space/tasks/cleanup.yml @@ -0,0 +1,12 @@ +--- +- 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: "{{ name }}" + state: "absent" + ignore_errors: true diff --git a/tests/integration/targets/setup_subnet/tasks/cleanup.yml b/tests/integration/targets/setup_subnet/tasks/cleanup.yml new file mode 100644 index 0000000..45096ab --- /dev/null +++ b/tests/integration/targets/setup_subnet/tasks/cleanup.yml @@ -0,0 +1,12 @@ +--- +- 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: "{{ name }}" + state: "absent" + ignore_errors: true