#!/usr/bin/python # Copyright (c) 2017, # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import annotations DOCUMENTATION = r""" module: infinity short_description: Manage Infinity IPAM using Rest API description: - Manage Infinity IPAM using REST API. author: - Meirong Liu (@MeganLiu) extends_documentation_fragment: - community.general.attributes attributes: check_mode: support: none diff_mode: support: none options: server_ip: description: - Infinity server_ip with IP address. type: str required: true username: description: - Username to access Infinity. - The user must have REST API privileges. type: str required: true password: description: - Infinity password. type: str required: true action: description: - Action to perform. type: str required: true choices: [add_network, delete_network, get_network, get_network_id, release_ip, release_network, reserve_network, reserve_next_available_ip] network_id: description: - Network ID. type: str ip_address: description: - IP Address for a reservation or a release. type: str network_address: description: - Network address with CIDR format (for example V(192.168.310.0)). type: str network_size: description: - Network bitmask (for example V(255.255.255.220) or CIDR format V(/26)). type: str network_name: description: - The name of a network. type: str network_location: description: - The parent network ID for a given network. type: int default: -1 network_type: description: - Network type defined by Infinity. type: str choices: [lan, shared_lan, supernet] default: lan network_family: description: - Network family defined by Infinity, for example V(IPv4), V(IPv6) and V(Dual stack). type: str choices: ['4', '6', dual] default: '4' """ EXAMPLES = r""" - hosts: localhost connection: local strategy: debug tasks: - name: Reserve network into Infinity IPAM community.general.infinity: server_ip: 80.75.107.12 username: username password: password action: reserve_network network_name: reserve_new_ansible_network network_family: 4 network_type: lan network_id: 1201 network_size: /28 register: infinity """ RETURN = r""" network_id: description: ID for a given network. returned: success type: str sample: '1501' ip_info: description: - When reserve next available IP address from a network, the IP address info is returned. - Please note that the value is a B(string) containing JSON data. returned: success type: str sample: >- { "address": "192.168.10.3", "hostname": "", "FQDN": "", "domainname": "", "id": 3229 } network_info: description: - When reserving a LAN network from a Infinity supernet by providing network_size, the information about the reserved network is returned. - Please note that the value is a B(string) containing JSON data. returned: success type: str sample: >- { "network_address": "192.168.10.32/28", "network_family": "4", "network_id": 3102, "network_size": null, "description": null, "network_location": "3085", "ranges": {"id": 0, "name": null, "first_ip": null, "type": null, "last_ip": null}, "network_type": "lan", "network_name": "'reserve_new_ansible_network'" } """ from ansible.module_utils.basic import AnsibleModule, json from ansible.module_utils.urls import open_url class Infinity: """ Class for manage REST API calls with the Infinity. """ def __init__(self, module, server_ip, username, password): self.module = module self.auth_user = username self.auth_pass = password self.base_url = f"https://{server_ip}/rest/v1/" def _get_api_call_ansible_handler( self, method="get", resource_url="", stat_codes=None, params=None, payload_data=None ): """ Perform the HTTPS request by using ansible get/delete method """ stat_codes = [200] if stat_codes is None else stat_codes request_url = str(self.base_url) + str(resource_url) response = None headers = {"Content-Type": "application/json"} if not request_url: self.module.exit_json(msg="When sending Rest api call , the resource URL is empty, please check.") if payload_data and not isinstance(payload_data, str): payload_data = json.dumps(payload_data) response_raw = open_url( str(request_url), method=method, timeout=20, headers=headers, url_username=self.auth_user, url_password=self.auth_pass, validate_certs=False, force_basic_auth=True, data=payload_data, ) response = response_raw.read() payload = "" if response_raw.code not in stat_codes: self.module.exit_json( changed=False, meta=f" openurl response_raw.code show error and error code is {response_raw.code!r}" ) else: if isinstance(response, str) and len(response) > 0: payload = response elif method.lower() == "delete" and response_raw.code == 204: payload = "Delete is done." if isinstance(payload, dict) and "text" in payload: self.module.exit_json(changed=False, meta="when calling rest api, returned data is not json ") raise Exception(payload["text"]) return payload # --------------------------------------------------------------------------- # get_network() # --------------------------------------------------------------------------- def get_network(self, network_id, network_name, limit=-1): """ Search network_name inside Infinity by using rest api Network id or network_name needs to be provided return the details of a given with given network_id or name """ if network_name is None and network_id is None: self.module.exit_json(msg="You must specify one of the options 'network_name' or 'network_id'.") method = "get" resource_url = "" params = {} response = None if network_id: resource_url = f"networks/{network_id}" response = self._get_api_call_ansible_handler(method, resource_url) if network_id is None and network_name: method = "get" resource_url = "search" params = {"query": json.dumps({"name": network_name, "type": "network"})} response = self._get_api_call_ansible_handler(method, resource_url, payload_data=json.dumps(params)) if response and isinstance(response, str): response = json.loads(response) if response and isinstance(response, list) and len(response) > 1 and limit == 1: response = response[0] response = json.dumps(response) return response # --------------------------------------------------------------------------- # get_network_id() # --------------------------------------------------------------------------- def get_network_id(self, network_name="", network_type="lan"): """ query network_id from Infinity via rest api based on given network_name """ method = "get" resource_url = "search" response = None if network_name is None: self.module.exit_json(msg="You must specify the option 'network_name'") params = {"query": json.dumps({"name": network_name, "type": "network"})} response = self._get_api_call_ansible_handler(method, resource_url, payload_data=json.dumps(params)) network_id = "" if response and isinstance(response, str): response = json.loads(response) if response and isinstance(response, list): response = response[0] network_id = response["id"] return network_id # --------------------------------------------------------------------------- # reserve_next_available_ip() # --------------------------------------------------------------------------- def reserve_next_available_ip(self, network_id=""): """ Reserve ip address via Infinity by using rest api network_id: the id of the network that users would like to reserve network from return the next available ip address from that given network """ method = "post" resource_url = "" response = None ip_info = "" if not network_id: self.module.exit_json(msg="You must specify the option 'network_id'.") if network_id: resource_url = f"networks/{network_id}/reserve_ip" response = self._get_api_call_ansible_handler(method, resource_url) if response and response.find("[") >= 0 and response.find("]") >= 0: start_pos = response.find("{") end_pos = response.find("}") ip_info = response[start_pos : (end_pos + 1)] return ip_info # ------------------------- # release_ip() # ------------------------- def release_ip(self, network_id="", ip_address=""): """ Reserve ip address via Infinity by using rest api """ method = "get" resource_url = "" response = None if ip_address is None or network_id is None: self.module.exit_json(msg="You must specify those two options: 'network_id' and 'ip_address'.") resource_url = f"networks/{network_id}/children" response = self._get_api_call_ansible_handler(method, resource_url) if not response: self.module.exit_json(msg=f"There is an error in release ip {ip_address} from network {network_id}.") ip_list = json.loads(response) ip_idlist = [] for ip_item in ip_list: ip_id = ip_item["id"] ip_idlist.append(ip_id) deleted_ip_id = "" for ip_id in ip_idlist: ip_response = "" resource_url = f"ip_addresses/{ip_id}" ip_response = self._get_api_call_ansible_handler(method, resource_url, stat_codes=[200]) if ip_response and json.loads(ip_response)["address"] == str(ip_address): deleted_ip_id = ip_id break if deleted_ip_id: method = "delete" resource_url = f"ip_addresses/{deleted_ip_id}" response = self._get_api_call_ansible_handler(method, resource_url, stat_codes=[204]) else: self.module.exit_json( msg=f" When release ip, could not find the ip address {ip_address} from the given network {network_id}' ." ) return response # ------------------- # delete_network() # ------------------- def delete_network(self, network_id="", network_name=""): """ delete network from Infinity by using rest api """ method = "delete" resource_url = "" response = None if network_id is None and network_name is None: self.module.exit_json(msg="You must specify one of those options: 'network_id','network_name' .") if network_id is None and network_name: network_id = self.get_network_id(network_name=network_name) if network_id: resource_url = f"networks/{network_id}" response = self._get_api_call_ansible_handler(method, resource_url, stat_codes=[204]) return response # reserve_network() # --------------------------------------------------------------------------- def reserve_network( self, network_id="", reserved_network_name="", reserved_network_description="", reserved_network_size="", reserved_network_family="4", reserved_network_type="lan", reserved_network_address="", ): """ Reserves the first available network of specified size from a given supernet
network_name (required)
Name of the network
description (optional)
Free description
network_family (required)
Address family of the network. One of '4', '6', 'IPv4', 'IPv6', 'dual'
network_address (optional)
Address of the new network. If not given, the first network available will be created.
network_size (required)
Size of the new network in /<prefix> notation.
network_type (required)
Type of network. One of 'supernet', 'lan', 'shared_lan'
""" method = "post" resource_url = "" network_info = None if network_id is None or reserved_network_name is None or reserved_network_size is None: self.module.exit_json( msg="You must specify those options: 'network_id', 'reserved_network_name' and 'reserved_network_size'" ) if network_id: resource_url = f"networks/{network_id}/reserve_network" if not reserved_network_family: reserved_network_family = "4" if not reserved_network_type: reserved_network_type = "lan" payload_data = { "network_name": reserved_network_name, "description": reserved_network_description, "network_size": reserved_network_size, "network_family": reserved_network_family, "network_type": reserved_network_type, "network_location": int(network_id), } if reserved_network_address: payload_data.update({"network_address": reserved_network_address}) network_info = self._get_api_call_ansible_handler( method, resource_url, stat_codes=[200, 201], payload_data=payload_data ) return network_info # --------------------------------------------------------------------------- # release_network() # --------------------------------------------------------------------------- def release_network(self, network_id="", released_network_name="", released_network_type="lan"): """ Release the network with name 'released_network_name' from the given supernet network_id """ method = "get" response = None if network_id is None or released_network_name is None: self.module.exit_json( msg="You must specify those options 'network_id', 'reserved_network_name' and 'reserved_network_size'" ) matched_network_id = "" resource_url = f"networks/{network_id}/children" response = self._get_api_call_ansible_handler(method, resource_url) if not response: self.module.exit_json( msg=f"There is an error in releasing network {network_id} from network {released_network_name}." ) if response: response = json.loads(response) for child_net in response: if child_net["network"] and child_net["network"]["network_name"] == released_network_name: matched_network_id = child_net["network"]["network_id"] break response = None if matched_network_id: method = "delete" resource_url = f"networks/{matched_network_id}" response = self._get_api_call_ansible_handler(method, resource_url, stat_codes=[204]) else: self.module.exit_json( msg=f"When release network, could not find the network {released_network_name} from the given superent {network_id} " ) return response # --------------------------------------------------------------------------- # add_network() # --------------------------------------------------------------------------- def add_network( self, network_name="", network_address="", network_size="", network_family="4", network_type="lan", network_location=-1, ): """ add a new LAN network into a given supernet Fusionlayer Infinity via rest api or default supernet required fields=['network_name', 'network_family', 'network_type', 'network_address','network_size' ] """ method = "post" resource_url = "networks" response = None if network_name is None or network_address is None or network_size is None: self.module.exit_json( msg="You must specify those options 'network_name', 'network_address' and 'network_size'" ) if not network_family: network_family = "4" if not network_type: network_type = "lan" if not network_location: network_location = -1 payload_data = { "network_name": network_name, "network_address": network_address, "network_size": network_size, "network_family": network_family, "network_type": network_type, "network_location": network_location, } response = self._get_api_call_ansible_handler( method="post", resource_url=resource_url, stat_codes=[200], payload_data=payload_data ) return response def main(): module = AnsibleModule( argument_spec=dict( server_ip=dict(type="str", required=True), username=dict(type="str", required=True), password=dict(type="str", required=True, no_log=True), network_id=dict(type="str"), ip_address=dict(type="str"), network_name=dict(type="str"), network_location=dict(type="int", default=-1), network_family=dict(type="str", default="4", choices=["4", "6", "dual"]), network_type=dict(type="str", default="lan", choices=["lan", "shared_lan", "supernet"]), network_address=dict(type="str"), network_size=dict(type="str"), action=dict( type="str", required=True, choices=[ "add_network", "delete_network", "get_network", "get_network_id", "release_ip", "release_network", "reserve_network", "reserve_next_available_ip", ], ), ), required_together=(["username", "password"],), ) server_ip = module.params["server_ip"] username = module.params["username"] password = module.params["password"] action = module.params["action"] network_id = module.params["network_id"] released_ip = module.params["ip_address"] network_name = module.params["network_name"] network_family = module.params["network_family"] network_type = module.params["network_type"] network_address = module.params["network_address"] network_size = module.params["network_size"] network_location = module.params["network_location"] my_infinity = Infinity(module, server_ip, username, password) result = "" if action == "reserve_next_available_ip": if network_id: result = my_infinity.reserve_next_available_ip(network_id) if not result: result = "There is an error in calling method of reserve_next_available_ip" module.exit_json(changed=False, meta=result) module.exit_json(changed=True, meta=result) elif action == "release_ip": if network_id and released_ip: result = my_infinity.release_ip(network_id=network_id, ip_address=released_ip) module.exit_json(changed=True, meta=result) elif action == "delete_network": result = my_infinity.delete_network(network_id=network_id, network_name=network_name) module.exit_json(changed=True, meta=result) elif action == "get_network_id": result = my_infinity.get_network_id(network_name=network_name, network_type=network_type) module.exit_json(changed=True, meta=result) elif action == "get_network": result = my_infinity.get_network(network_id=network_id, network_name=network_name) module.exit_json(changed=True, meta=result) elif action == "reserve_network": result = my_infinity.reserve_network( network_id=network_id, reserved_network_name=network_name, reserved_network_size=network_size, reserved_network_family=network_family, reserved_network_type=network_type, reserved_network_address=network_address, ) module.exit_json(changed=True, meta=result) elif action == "release_network": result = my_infinity.release_network( network_id=network_id, released_network_name=network_name, released_network_type=network_type ) module.exit_json(changed=True, meta=result) elif action == "add_network": result = my_infinity.add_network( network_name=network_name, network_location=network_location, network_address=network_address, network_size=network_size, network_family=network_family, network_type=network_type, ) module.exit_json(changed=True, meta=result) if __name__ == "__main__": main()