ansible/library/switch_config.py

335 lines
10 KiB
Python

#!/usr/bin/python
DOCUMENTATION = """
---
module: Switch
short_description: Allow the setup of switches using rest API
description: Allow the setup of switches using rest API
options:
config:
description: configuration to send to the switch.
required: true
type: dict
host:
description: host of switch.
required: false
type: str
password:
description: password of the user.
required: true
type: str
port:
description: port of rest api.
required: false
type: int
use_proxy:
description:
Use a proxy to communicate with the switch.
HTTP_PROXY or ALL_PROXY must be set.
required: false
type: bool
username:
description: username of the rest API.
required: true
type: str
version:
description: version of the rest API.
required: false
type: str
"""
EXAMPLES = """
- name: Setup switch name
switch_config:
username: test
password: 1234
host: 192.168.1.1
port: 80
version: v8
config:
path: system
data:
name: "SwitchName"
- name: Setup vlans
switch_config:
username: test
password: 1234
host: 192.168.1.1
port: 80
config:
path: vlans
create_method: POST
subpath:
- path: 42
data:
name: "TheAnswer"
vlan_id: 42
status: VS_PORT_BASED
type: VT_STATIC
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import Connection
# class SwitchApi:
# def __init__(self, port, host, use_proxy, api="v1"):
# self.headers = {"Content-Type": "application/json"}
# self.url_base = f"http://{host}:{port}/rest/{api}"
# self.proxies = None
# if use_proxy:
# http_proxy = os.getenv("HTTP_PROXY")
# all_proxy = os.getenv("ALL_PROXY")
# if http_proxy != "":
# self.proxies = {"http": http_proxy}
# elif all_proxy != "":
# self.proxies = {"http": all_proxy}
# def post(self, url, data=None):
# kwargs = {"headers": self.headers}
# if data is not None:
# kwargs["data"] = data
# if self.proxies is not None:
# kwargs["proxies"] = self.proxies
# return requests.post(self.url_base + url, **kwargs)
# def get(self, url, data=""):
# kwargs = {"headers": self.headers}
# if data is not None:
# kwargs["data"] = data
# if self.proxies is not None:
# kwargs["proxies"] = self.proxies
# return requests.get(self.url_base + url, **kwargs)
# def delete(self, url, data=""):
# kwargs = {"headers": self.headers}
# if data is not None:
# kwargs["data"] = data
# if self.proxies is not None:
# kwargs["proxies"] = self.proxies
# return requests.delete(self.url_base + url, **kwargs)
# def put(self, url, data=""):
# kwargs = {"headers": self.headers}
# if data is not None:
# kwargs["data"] = data
# if self.proxies is not None:
# kwargs["proxies"] = self.proxies
# return requests.put(self.url_base + url, **kwargs)
def required_modification(current_conf, modification):
for k, v in modification.items():
if not k in current_conf:
return True
if current_conf[k] != v:
return True
return False
def throw_err(msg, url, status, response):
raise Exception(
msg + ":",
"Url: " + url,
"Status code: " + status,
"Response: " + response,
)
def configure(connection, config, check_mode, current_path="", create_method=None):
path = "/" + str(config["path"])
url = current_path + path
changed = False
before = {"path": path}
after = {"path": path}
if not "path" in config:
raise Exception("A path must be specified.")
# If removing configuration
if "delete" in config and config["delete"]:
# Get the configuration
status, response = connection.send_request(None, url, method="GET")
if status == 404:
before["delete"] = True
elif status in (200, 201, 202, 203, 204):
before["data"] = response.json()
else:
throw_err("Failed to check the old configuration",
f"Url: {url}",
f"Status code: {status}",
f"Response: {response.text}",
)
# If required, delete
if not "delete" in before and not check_mode:
response = api.delete(url)
if response.status_code >= 400:
api.logout()
raise Exception(
"Failed to delete:",
f"Url: {response.url}",
f"Status code: {response.status_code}",
f"Response: {response.text}",
)
else:
# Verify that everything is ok
response = api.get(url)
if response.status_code == 404:
changed = True
after["delete"] = True
elif response.status_code in (200, 201, 202, 203, 204):
after["data"] = response.json()
after["delete"] = False
else:
api.logout()
raise Exception(
"Failed to check the configuration after delete:",
f"Url: {response.url}",
f"Status code: {response.status_code}",
f"Response: {response.text}",
)
elif not "delete" in before:
after["delete"] = True
changed = True
else:
after["delete"] = True
# If create or edit
elif "data" in config and type(config["data"]) is dict:
# Get the configuration
status, response = connection.send_request(None, url, method="GET")
new_data = {}
if status == 404:
before["delete"] = True
elif status in (200, 201, 202, 203, 204):
before["data"] = response.json()
new_data = before["data"].copy()
else:
throw_err(
"Failed to check the old configuration",
url, status, response
)
# If required, modify
if "delete" in before and not check_mode:
# Create
if create_method == "POST":
response = api.post(current_path, json.dumps(config["data"]))
elif create_method == "PUT":
response = api.put(current_path, json.dumps(config["data"]))
else:
api.logout()
raise Exception(
"Failed to create:",
"Variable create_method must be set to PUT or POST",
)
if response.status_code >= 400:
api.logout()
raise Exception(
"Failed to create:",
f"Url: {response.url}",
f"Status code: {response.status_code}",
f"Data: {config["data"]}",
f"Response: {response.text}",
)
after["data"] = response.json()
changed = True
elif not check_mode:
# Edit
if required_modification(before["data"], config["data"]):
response = api.put(url, json.dumps(config["data"]))
if response.status_code >= 400:
api.logout()
raise Exception(
"Failed to edit:",
f"Url: {response.url}",
f"Status code : {response.status_code}",
f"Data: {config["data"]}",
f"Response: {response.text}",
)
changed = True
after["data"] = response.json()
else:
after["data"] = before["data"].copy()
else:
if "delete" in before and create_method is None:
api.logout()
raise Exception(
"Failed to create:",
"Variable create_method must be set to PUT or POST",
)
new_data.update(config["data"])
after["data"] = new_data
changed = (
changed
or (not "data" in before)
or after["data"] != before["data"]
)
# Configure the subpaths
if "subpath" in config and type(config["subpath"]) is list:
create_method = None
if "create_method" in config:
create_method = config["create_method"]
before["subpath"] = []
after["subpath"] = []
for subconf in config["subpath"]:
response = configure(
connection,
subconf,
api,
current_path=url,
create_method=create_method,
)
changed = changed or response["changed"]
before["subpath"].append(response["diff"]["before"])
after["subpath"].append(response["diff"]["after"])
return {"changed": changed, "diff": {"after": after, "before": before}}
def run_module():
module_args = {
"config": {"type": "dict", "required": True},
"username": {"type": "str", "required": True},
"password": {"type": "str", "required": True, "no_log": True},
"port": {"type": "int", "required": True},
"host": {"type": "str", "required": True},
"version": {"type": "str", "required": False, "default": "v1"},
"use_proxy": {"type": "bool", "required": False, "default": False},
}
module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
connection = Connection(module._socket_path)
connection.set_option("uri_root_path", module.params["host"])
result = {
"changed": False,
}
try:
response = configure(connection, module.params["config"], module.check_mode)
except Exception as msg:
module.fail_json(msg="\n".join(msg.args), **result)
return
result.update(response)
module.exit_json(**result)
def main():
run_module()
if __name__ == "__main__":
main()