From f4096cf5af5b929d8557005bf6c46ad89c26bd5b Mon Sep 17 00:00:00 2001 From: Vincent Lafeychine Date: Sun, 5 Oct 2025 19:26:47 +0200 Subject: [PATCH] aruba: Early httpapi plugin --- ansible.cfg | 1 + httpapi/aruba.py | 49 ++++++++ library/switch_config.py | 240 ++++++++++++++------------------------- playbooks/switch.yml | 16 ++- 4 files changed, 152 insertions(+), 154 deletions(-) create mode 100644 httpapi/aruba.py diff --git a/ansible.cfg b/ansible.cfg index b591cde..e9379ac 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -7,6 +7,7 @@ retry_files_enabled = False inventory = ./hosts stdout_callback = debug library = ./library +httpapi_plugins = ./httpapi filter_plugins = ./filter_plugins ansible_managed = Ansible managed nocows = 1 diff --git a/httpapi/aruba.py b/httpapi/aruba.py new file mode 100644 index 0000000..eede10c --- /dev/null +++ b/httpapi/aruba.py @@ -0,0 +1,49 @@ +from ansible.module_utils.six.moves.urllib.error import HTTPError +from ansible_collections.ansible.netcommon.plugins.plugin_utils.httpapi_base import ( + HttpApiBase, +) + + +class HttpApi(HttpApiBase): + def login(self, username, password): + """ + Log in to the rest api. + Return True if the connection has succeeded and False otherwise. + """ + data = {"userName": username, "password": password} + response = self.send_request(json.dumps(data), path="/login-sessions") + + if response.status_code != 201: + return AnsibleAuthentificationFailure(message="Plop!") + + data = response.json() + if not "cookie" in data: + return False + + self.headers["cookie"] = data["cookie"] + return True + + def logout(self): + """ + Log out of the rest api. + Return True if connection has succeeded and False otherwise + """ + response = self.delete("/login-sessions") + if response.status_code != 204: + return False + self.headers.pop("cookie") + return True + + def send_request(self, data, path, method="POST"): + headers = {"Content-Type": "application/json"} + uri = self.get_option("uri_root_path") + "/" + path + + if data is not None: + content = json.dumps(data) + + try: + response, content = self.connection.send(uri, content, method=method, headers=headers) + except HTTPError as exc: + return exc.code, exc.read() + + return response.read() diff --git a/library/switch_config.py b/library/switch_config.py index 5aa6371..04b1ca6 100644 --- a/library/switch_config.py +++ b/library/switch_config.py @@ -73,107 +73,58 @@ EXAMPLES = """ """ from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection -import json -import os -import requests -import time +# 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}" -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") - 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} - 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 - def login(self, username, password): - """ - Log in to the rest api. - Return True if the connection has succeeded and False otherwise. - """ - data = {"userName": username, "password": password} - max_retries = 3 - for i in range(max_retries): - try: - response = self.post("/login-sessions", data = json.dumps(data),) - break - except requests.exceptions.ConnectionError as err: - if i == max_retries-1: - raise err - time.sleep(1) +# return requests.post(self.url_base + url, **kwargs) - if response.status_code != 201: - return False +# 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 - data = response.json() - if not 'cookie' in data: - return False +# return requests.get(self.url_base + url, **kwargs) - self.headers['cookie'] = data['cookie'] - return True +# 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 - def logout(self): - """ - Log out of the rest api. - Return True if connection has succeeded and False otherwise - """ - response = self.delete("/login-sessions") - if response.status_code != 204: - return False - self.headers.pop('cookie') - return True +# return requests.delete(self.url_base + url, **kwargs) - 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 +# 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.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) +# return requests.put(self.url_base + url, **kwargs) def required_modification(current_conf, modification): @@ -184,33 +135,37 @@ def required_modification(current_conf, modification): return True return False +def throw_err(msg, url, status, response): + raise Exception( + msg + ":", + "Url: " + url, + "Status code: " + status, + "Response: " + response, + ) -def configure(module, config, api, current_path="", create_method=None): + +def configure(connection, config, check_mode, current_path="", create_method=None): path = "/" + str(config["path"]) url = current_path + path - check_mode = module.check_mode changed = False before = {"path": path} after = {"path": path} if not "path" in config: - api.logout() raise Exception("A path must be specified.") # If removing configuration if "delete" in config and config["delete"]: # Get the configuration - response = api.get(url) - if response.status_code == 404: + status, response = connection.send_request(None, url, method="GET") + if status == 404: before["delete"] = True - elif response.status_code in (200, 201, 202, 203, 204): + elif status in (200, 201, 202, 203, 204): before["data"] = response.json() else: - api.logout() - raise Exception( - "Failed to check the old configuration:", - f"Url: {response.url}", - f"Status code: {response.status_code}", + throw_err("Failed to check the old configuration", + f"Url: {url}", + f"Status code: {status}", f"Response: {response.text}", ) @@ -251,20 +206,17 @@ def configure(module, config, api, current_path="", create_method=None): # If create or edit elif "data" in config and type(config["data"]) is dict: # Get the configuration - response = api.get(url) + status, response = connection.send_request(None, url, method="GET") new_data = {} - if response.status_code == 404: + if status == 404: before["delete"] = True - elif response.status_code in (200, 201, 202, 203, 204): + elif status in (200, 201, 202, 203, 204): before["data"] = response.json() new_data = before["data"].copy() else: - api.logout() - raise Exception( - "Failed to check the old configuration:", - f"Url: {response.url}", - f"Status code: {response.status_code}", - f"Response: {response.text}", + throw_err( + "Failed to check the old configuration", + url, status, response ) # If required, modify @@ -317,7 +269,11 @@ def configure(module, config, api, current_path="", create_method=None): ) new_data.update(config["data"]) after["data"] = new_data - changed = changed or (not "data" in before) or after["data"] != before["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: @@ -328,66 +284,46 @@ def configure(module, config, api, current_path="", create_method=None): after["subpath"] = [] for subconf in config["subpath"]: response = configure( - module, subconf, + connection, + subconf, api, current_path=url, - create_method=create_method + 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} - } + return {"changed": changed, "diff": {"after": after, "before": before}} + def run_module(): - module_args = dict( - config=dict(type='dict', required=True), - username=dict(type='str', required=True), - password=dict(type='str', required=True, no_log=True), - port=dict(type='int', required=True), - host=dict(type='str', required=True), - version=dict(type='str', required=False, default='v1'), - use_proxy=dict(type='bool', required=False, default=False), - ) + 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 - ) + 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, } - # api connection - api = SwitchApi( - module.params["port"], - module.params["host"], - module.params["use_proxy"], - api = module.params["version"] - ) - login_success = api.login( - module.params["username"], - module.params["password"], - ) - if not login_success: - module.fail_json(msg='login failed', **result) - return - try: - response = configure(module, module.params["config"], api) + response = configure(connection, module.params["config"], module.check_mode) except Exception as msg: module.fail_json(msg="\n".join(msg.args), **result) return - api.logout() + result.update(response) - - if module.check_mode: - module.exit_json(**result) - module.exit_json(**result) @@ -395,5 +331,5 @@ def main(): run_module() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/playbooks/switch.yml b/playbooks/switch.yml index ccd1b19..9128da9 100755 --- a/playbooks/switch.yml +++ b/playbooks/switch.yml @@ -2,13 +2,25 @@ --- - hosts: - switch - connection: httpapi - gather_facts: false + environment: HTTP_PROXY: "socks5://localhost:3000" + + connection: httpapi + gather_facts: false + vars: + ansible_network_os: aruba + + ansible_user: vault_switch.username + ansible_httpapi_password: vault_switch.password + + ansible_httpapi_use_ssl: false + ansible_httpapi_validate_certs: false + switch: use_proxy: true + roles: - switch-system - switch-vlans