#!/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()