mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
78113c061b
(cherry picked from commit ca2c64b5ca
)
565 lines
22 KiB
Python
565 lines
22 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright: (c) 2017, <meiliu@fusionlayer.com>
|
|
# 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: infinity
|
|
short_description: Manage Infinity IPAM using Rest API
|
|
description:
|
|
- Manage Infinity IPAM using REST API.
|
|
author:
|
|
- Meirong Liu (@MeganLiu)
|
|
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
|
|
default: ''
|
|
ip_address:
|
|
description:
|
|
- IP Address for a reservation or a release.
|
|
type: str
|
|
default: ''
|
|
network_address:
|
|
description:
|
|
- Network address with CIDR format (e.g., 192.168.310.0).
|
|
type: str
|
|
default: ''
|
|
network_size:
|
|
description:
|
|
- Network bitmask (e.g. 255.255.255.220) or CIDR format (e.g., /26).
|
|
type: str
|
|
default: ''
|
|
network_name:
|
|
description:
|
|
- The name of a network.
|
|
type: str
|
|
default: ''
|
|
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, e.g. IPv4, IPv6 and 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.
|
|
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.
|
|
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(object):
|
|
"""
|
|
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 = "https://%s/rest/v1/" % (str(server_ip))
|
|
|
|
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=" openurl response_raw.code show error and error code is %r" %
|
|
(response_raw.code))
|
|
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 = "networks/" + str(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 = "networks/" + str(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 = "networks/" + str(network_id) + "/children"
|
|
response = self._get_api_call_ansible_handler(method, resource_url)
|
|
if not response:
|
|
self.module.exit_json(
|
|
msg="There is an error in release ip %s from network %s." %
|
|
(ip_address, 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 = "ip_addresses/" + str(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 = "ip_addresses/" + str(deleted_ip_id)
|
|
response = self._get_api_call_ansible_handler(
|
|
method, resource_url, stat_codes=[204])
|
|
else:
|
|
self.module.exit_json(
|
|
msg=" When release ip, could not find the ip address %r from the given network %r' ." %
|
|
(ip_address, 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 = "networks/" + str(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
|
|
<dt>network_name (required)</dt><dd>Name of the network</dd>
|
|
<dt>description (optional)</dt><dd>Free description</dd>
|
|
<dt>network_family (required)</dt><dd>Address family of the network. One of '4', '6', 'IPv4', 'IPv6', 'dual'</dd>
|
|
<dt>network_address (optional)</dt><dd>Address of the new network. If not given, the first network available will be created.</dd>
|
|
<dt>network_size (required)</dt><dd>Size of the new network in /<prefix> notation.</dd>
|
|
<dt>network_type (required)</dt><dd>Type of network. One of 'supernet', 'lan', 'shared_lan'</dd>
|
|
|
|
"""
|
|
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 = "networks/" + str(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 = "networks/" + str(network_id) + "/children"
|
|
response = self._get_api_call_ansible_handler(method, resource_url)
|
|
if not response:
|
|
self.module.exit_json(
|
|
msg=" there is an error in releasing network %r from network %s." %
|
|
(network_id, 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 = "networks/" + str(matched_network_id)
|
|
response = self._get_api_call_ansible_handler(
|
|
method, resource_url, stat_codes=[204])
|
|
else:
|
|
self.module.exit_json(
|
|
msg=" When release network , could not find the network %r from the given superent %r' " %
|
|
(released_network_name, 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()
|