Skip to content

Commit

Permalink
Next Available Subnet Support for Ansible V2 (infobloxopen#59)
Browse files Browse the repository at this point in the history
* created a next_available_subnet

* Added next available subnet functionality

* created a setup_ip_space and setup_address_block seperately

* refactor the next_available_subnet

* fixed next_available_subnet

* added required_by instead of required_together in ipam_subnet.py

* added description and change example name

* added description

* Fixed sanity issues

* changed Module_defaults

---------

Co-authored-by: Ujjwal Nasra <125353741+unasra@users.noreply.github.com>
  • Loading branch information
ybhalchim and unasra authored Jan 31, 2025
1 parent 6ea0db5 commit 970ac02
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 115 deletions.
1 change: 1 addition & 0 deletions meta/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ action_groups:
- ipam_ip_space_info
- ipam_subnet
- ipam_subnet_info
- ipam_next_available_subnet_info
- ipam_address_block
- ipam_address_block_info
- ipam_range
Expand Down
149 changes: 149 additions & 0 deletions plugins/modules/ipam_next_available_subnet_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/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_next_available_subnet_info
short_description: Retrieves the Next available subnet
description:
- Retrieves the Next Available Subnet in the specified Address Block
version_added: 2.0.0
author: Infoblox Inc. (@infobloxopen)
options:
id:
description:
- An application specific resource identity of a resource
type: str
required: true
cidr:
description:
- The cidr value of address blocks to be created.
type: int
required: true
count:
description:
- Number of address blocks to generate. Default 1 if not set.
type: int
required: false
extends_documentation_fragment:
- infoblox.bloxone.common
""" # noqa: E501

EXAMPLES = r"""
- name: "Create an IP Space (required as parent)"
infoblox.bloxone.ipam_ip_space:
name: "example-ipspace"
state: "present"
register: ip_space
- name: "Create an Address Block (required as parent)"
infoblox.bloxone.ipam_address_block:
address: "10.0.0.0/16"
space: "{{ ip_space.id }}"
state: "present"
register: address_block
- name: "Get information about the Subnet"
infoblox.bloxone.ipam_next_available_subnet_info:
id: "{{ address_block.id }}"
cidr: 24
- name: "Get information about the Subnet with count"
infoblox.bloxone.ipam_next_available_subnet_info:
id: "{{ address_block.id }}"
cidr: 24
count: 5
"""

RETURN = r"""
id:
description:
- ID of the Subnet object
type: str
returned: Always
objects:
description:
- List of next available subnet addresses
type: list
elements: str
returned: Always
""" # noqa: E501

from ansible_collections.infoblox.bloxone.plugins.module_utils.modules import BloxoneAnsibleModule

try:
from ipam import AddressBlockApi
from universal_ddi_client import ApiException
except ImportError:
pass # Handled by BloxoneAnsibleModule


class NextAvailableSubnetInfoModule(BloxoneAnsibleModule):
def __init__(self, *args, **kwargs):
super(NextAvailableSubnetInfoModule, self).__init__(*args, **kwargs)
self._existing = None
self._limit = 1000

def find(self):
all_results = []
offset = 0

while True:
try:
resp = AddressBlockApi(self.client).list_next_available_subnet(
id=self.params["id"], cidr=self.params["cidr"], count=self.params["count"]
)
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:
# The expected output is a list of addresses as strings.
# Therefore, we extract only the 'address' field from each object in the results.
all_results.append(r.address)

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=True),
cidr=dict(type="int", required=True),
count=dict(type="int", required=False),
)

module = NextAvailableSubnetInfoModule(
argument_spec=module_args,
supports_check_mode=True,
)
module.run_command()


if __name__ == "__main__":
main()
69 changes: 54 additions & 15 deletions plugins/modules/ipam_subnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
short_description: Manage Subnet
description:
- Manage Subnet
- The Subnet object represents a set of addresses from which addresses are assigned to network equipment interfaces.
version_added: 2.0.0
author: Infoblox Inc. (@infobloxopen)
options:
Expand Down Expand Up @@ -76,7 +77,8 @@
- "The minimum percentage of addresses that must be available outside of the DHCP ranges and fixed addresses when making a suggested change.."
type: int
reenable_date:
description: ""
description:
- "The date at which notifications will be re-enabled automatically."
type: str
cidr:
description:
Expand Down Expand Up @@ -744,28 +746,40 @@
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
next_available_id:
description:
- "The resource identifier for the address block where the next available subnet should be generated."
type: str
extends_documentation_fragment:
- infoblox.bloxone.common
""" # noqa: E501

EXAMPLES = r"""
- name: "Create an ip space"
- name: "Create an IP Space (required as parent)"
infoblox.bloxone.ipam_ip_space:
name: "my-ip-space"
name: "example-ipspace"
state: "present"
register: ip_space
- name: "Create an Address Block (required as parent)"
infoblox.bloxone.ipam_address_block:
address: "10.0.0.0/16"
space: "{{ ip_space.id }}"
state: "present"
register: address_block
- name: "Create a subnet"
infoblox.bloxone.ipam_subnet:
address: "10.0.0.0/24"
space: "{{ ip_space.id }}"
state: "present"
- name: "Create a subnet with dhcp_config overridden"
- name: "Create a subnet with Additional Fields"
infoblox.bloxone.ipam_subnet:
address: "10.0.0.0/24"
space: "{{ ip_space_id }}"
tags: [location: "site1" ]
state: "present"
dhcp_config:
abandoned_reclaim_time: 3600
Expand Down Expand Up @@ -797,6 +811,13 @@
lease_time_v6:
action: inherit
- name: "Create a Next available Subnet"
infoblox.bloxone.ipam_subnet:
cidr: 24
next_available_id: "{{ address_block.id }}"
space: "{{ ip_space.id }}"
state: "present"
- name: "Delete a subnet"
infoblox.bloxone.ipam_subnet:
address: "10.0.0.0"
Expand Down Expand Up @@ -874,7 +895,8 @@
type: int
returned: Always
reenable_date:
description: ""
description:
- "The date at which notifications will be re-enabled automatically."
type: str
returned: Always
asm_scope_flag:
Expand Down Expand Up @@ -2368,23 +2390,28 @@
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
Expand All @@ -2402,11 +2429,13 @@ class SubnetModule(BloxoneAnsibleModule):
def __init__(self, *args, **kwargs):
super(SubnetModule, self).__init__(*args, **kwargs)

if "/" in self.params["address"]:
self.params["address"], netmask = self.params["address"].split("/")
self.params["cidr"] = int(netmask)
self.next_available_id = self.params.get("next_available_id")
if self.params["address"] is not None:
if "/" in self.params["address"]:
self.params["address"], netmask = self.params["address"].split("/")
self.params["cidr"] = int(netmask)

exclude = ["state", "csp_url", "api_key", "portal_url", "portal_key", "id"]
exclude = ["state", "csp_url", "api_key", "portal_url", "portal_key", "id", "next_available_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 = Subnet.from_dict(self._payload_params)

Expand Down Expand Up @@ -2451,6 +2480,9 @@ def find(self):
return None
raise e
else:
if self.params["address"] is None:
return None

filter = f"address=='{self.params['address']}' and space=='{self.params['space']}' and cidr=={self.params['cidr']}"
resp = SubnetApi(self.client).list(filter=filter, inherit="full")
if len(resp.results) == 1:
Expand All @@ -2464,6 +2496,9 @@ def create(self):
if self.check_mode:
return None

if self.next_available_id is not None:
naId = f"{self.next_available_id}/nextavailablesubnet"
self._payload.address = naId
resp = SubnetApi(self.client).create(body=self.payload, inherit="full")
return resp.result.model_dump(by_alias=True, exclude_none=True)

Expand Down Expand Up @@ -2528,6 +2563,7 @@ def main():
id=dict(type="str", required=False),
state=dict(type="str", required=False, choices=["present", "absent"], default="present"),
address=dict(type="str"),
next_available_id=dict(type="str", required=False),
asm_config=dict(
type="dict",
options=dict(
Expand Down Expand Up @@ -2822,7 +2858,10 @@ def main():
module = SubnetModule(
argument_spec=module_args,
supports_check_mode=True,
required_if=[("state", "present", ["address", "space"])],
mutually_exclusive=[["address", "next_available_id"]],
required_if=[("state", "present", ["space"])],
required_one_of=[["address", "next_available_id"]],
required_by={"next_available_id": "cidr"},
)

module.run_command()
Expand Down
23 changes: 15 additions & 8 deletions plugins/modules/ipam_subnet_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
DOCUMENTATION = r"""
---
module: ipam_subnet_info
short_description: Manage Subnet
short_description: Retrieve a Subnet
description:
- Manage Subnet
- Retrieves information about existing Subnets.
- The Subnet object represents a set of addresses from which addresses are assigned to network equipment interfaces.
version_added: 2.0.0
author: Infoblox Inc. (@infobloxopen)
options:
Expand Down Expand Up @@ -146,7 +147,8 @@
type: int
returned: Always
reenable_date:
description: ""
description:
- "The date at which notifications will be re-enabled automatically."
type: str
returned: Always
asm_scope_flag:
Expand Down Expand Up @@ -1652,23 +1654,28 @@
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
dependencies: [setup_ip_space, setup_address_block]
Loading

0 comments on commit 970ac02

Please sign in to comment.