aruba: Early httpapi plugin

This commit is contained in:
v-lafeychine 2025-10-05 19:26:47 +02:00
parent 1deba6ebf8
commit f4096cf5af
Signed by: v-lafeychine
GPG key ID: F46CAAD27C7AB0D5
4 changed files with 152 additions and 154 deletions

View file

@ -7,6 +7,7 @@ retry_files_enabled = False
inventory = ./hosts inventory = ./hosts
stdout_callback = debug stdout_callback = debug
library = ./library library = ./library
httpapi_plugins = ./httpapi
filter_plugins = ./filter_plugins filter_plugins = ./filter_plugins
ansible_managed = Ansible managed ansible_managed = Ansible managed
nocows = 1 nocows = 1

49
httpapi/aruba.py Normal file
View file

@ -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()

View file

@ -73,107 +73,58 @@ EXAMPLES = """
""" """
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.connection import Connection
import json # class SwitchApi:
import os # def __init__(self, port, host, use_proxy, api="v1"):
import requests # self.headers = {"Content-Type": "application/json"}
import time # self.url_base = f"http://{host}:{port}/rest/{api}"
class SwitchApi: # self.proxies = None
def __init__(self, port, host, use_proxy, api="v1"): # if use_proxy:
self.headers = {'Content-Type': 'application/json'} # http_proxy = os.getenv("HTTP_PROXY")
self.url_base = f"http://{host}:{port}/rest/{api}" # all_proxy = os.getenv("ALL_PROXY")
self.proxies = None # if http_proxy != "":
if use_proxy: # self.proxies = {"http": http_proxy}
http_proxy = os.getenv("HTTP_PROXY") # elif all_proxy != "":
all_proxy = os.getenv("ALL_PROXY") # self.proxies = {"http": all_proxy}
if http_proxy != "": # def post(self, url, data=None):
self.proxies = {'http': http_proxy} # kwargs = {"headers": self.headers}
elif all_proxy != "": # if data is not None:
self.proxies = {'http': all_proxy} # kwargs["data"] = data
# if self.proxies is not None:
# kwargs["proxies"] = self.proxies
def login(self, username, password): # return requests.post(self.url_base + url, **kwargs)
"""
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)
if response.status_code != 201: # def get(self, url, data=""):
return False # 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() # return requests.get(self.url_base + url, **kwargs)
if not 'cookie' in data:
return False
self.headers['cookie'] = data['cookie'] # def delete(self, url, data=""):
return True # 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): # return requests.delete(self.url_base + url, **kwargs)
"""
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 post(self, url, data = None): # def put(self, url, data=""):
kwargs = { # kwargs = {"headers": self.headers}
"headers": self.headers # if data is not None:
} # kwargs["data"] = data
if data is not None: # if self.proxies is not None:
kwargs["data"] = data # kwargs["proxies"] = self.proxies
if self.proxies is not None:
kwargs["proxies"] = self.proxies
return requests.post(self.url_base + url, **kwargs) # return requests.put(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): def required_modification(current_conf, modification):
@ -184,33 +135,37 @@ def required_modification(current_conf, modification):
return True return True
return False 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"]) path = "/" + str(config["path"])
url = current_path + path url = current_path + path
check_mode = module.check_mode
changed = False changed = False
before = {"path": path} before = {"path": path}
after = {"path": path} after = {"path": path}
if not "path" in config: if not "path" in config:
api.logout()
raise Exception("A path must be specified.") raise Exception("A path must be specified.")
# If removing configuration # If removing configuration
if "delete" in config and config["delete"]: if "delete" in config and config["delete"]:
# Get the configuration # Get the configuration
response = api.get(url) status, response = connection.send_request(None, url, method="GET")
if response.status_code == 404: if status == 404:
before["delete"] = True 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() before["data"] = response.json()
else: else:
api.logout() throw_err("Failed to check the old configuration",
raise Exception( f"Url: {url}",
"Failed to check the old configuration:", f"Status code: {status}",
f"Url: {response.url}",
f"Status code: {response.status_code}",
f"Response: {response.text}", f"Response: {response.text}",
) )
@ -251,20 +206,17 @@ def configure(module, config, api, current_path="", create_method=None):
# If create or edit # If create or edit
elif "data" in config and type(config["data"]) is dict: elif "data" in config and type(config["data"]) is dict:
# Get the configuration # Get the configuration
response = api.get(url) status, response = connection.send_request(None, url, method="GET")
new_data = {} new_data = {}
if response.status_code == 404: if status == 404:
before["delete"] = True 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() before["data"] = response.json()
new_data = before["data"].copy() new_data = before["data"].copy()
else: else:
api.logout() throw_err(
raise Exception( "Failed to check the old configuration",
"Failed to check the old configuration:", url, status, response
f"Url: {response.url}",
f"Status code: {response.status_code}",
f"Response: {response.text}",
) )
# If required, modify # If required, modify
@ -317,7 +269,11 @@ def configure(module, config, api, current_path="", create_method=None):
) )
new_data.update(config["data"]) new_data.update(config["data"])
after["data"] = new_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 # Configure the subpaths
if "subpath" in config and type(config["subpath"]) is list: 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"] = [] after["subpath"] = []
for subconf in config["subpath"]: for subconf in config["subpath"]:
response = configure( response = configure(
module, subconf, connection,
subconf,
api, api,
current_path=url, current_path=url,
create_method=create_method create_method=create_method,
) )
changed = changed or response["changed"] changed = changed or response["changed"]
before["subpath"].append(response["diff"]["before"]) before["subpath"].append(response["diff"]["before"])
after["subpath"].append(response["diff"]["after"]) after["subpath"].append(response["diff"]["after"])
return { return {"changed": changed, "diff": {"after": after, "before": before}}
"changed": changed,
"diff": {"after": after, "before": before}
}
def run_module(): def run_module():
module_args = dict( module_args = {
config=dict(type='dict', required=True), "config": {"type": "dict", "required": True},
username=dict(type='str', required=True), "username": {"type": "str", "required": True},
password=dict(type='str', required=True, no_log=True), "password": {"type": "str", "required": True, "no_log": True},
port=dict(type='int', required=True), "port": {"type": "int", "required": True},
host=dict(type='str', required=True), "host": {"type": "str", "required": True},
version=dict(type='str', required=False, default='v1'), "version": {"type": "str", "required": False, "default": "v1"},
use_proxy=dict(type='bool', required=False, default=False), "use_proxy": {"type": "bool", "required": False, "default": False},
) }
module = AnsibleModule( module = AnsibleModule(argument_spec=module_args, supports_check_mode=True)
argument_spec=module_args,
supports_check_mode=True connection = Connection(module._socket_path)
) connection.set_option("uri_root_path", module.params["host"])
result = { result = {
"changed": False, "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: try:
response = configure(module, module.params["config"], api) response = configure(connection, module.params["config"], module.check_mode)
except Exception as msg: except Exception as msg:
module.fail_json(msg="\n".join(msg.args), **result) module.fail_json(msg="\n".join(msg.args), **result)
return return
api.logout()
result.update(response) result.update(response)
if module.check_mode:
module.exit_json(**result)
module.exit_json(**result) module.exit_json(**result)
@ -395,5 +331,5 @@ def main():
run_module() run_module()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View file

@ -2,13 +2,25 @@
--- ---
- hosts: - hosts:
- switch - switch
connection: httpapi
gather_facts: false
environment: environment:
HTTP_PROXY: "socks5://localhost:3000" HTTP_PROXY: "socks5://localhost:3000"
connection: httpapi
gather_facts: false
vars: 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: switch:
use_proxy: true use_proxy: true
roles: roles:
- switch-system - switch-system
- switch-vlans - switch-vlans