diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-26 04:06:02 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-26 04:06:02 +0000 |
commit | e3eb94c23206603103f3c4faec6c227f59a1544c (patch) | |
tree | f2639459807ba88f55fc9c54d745bd7075d7f15c /ansible_collections/cisco/mso/plugins/module_utils | |
parent | Releasing progress-linux version 9.4.0+dfsg-1~progress7.99u1. (diff) | |
download | ansible-e3eb94c23206603103f3c4faec6c227f59a1544c.tar.xz ansible-e3eb94c23206603103f3c4faec6c227f59a1544c.zip |
Merging upstream version 9.5.1+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/mso/plugins/module_utils')
3 files changed, 317 insertions, 14 deletions
diff --git a/ansible_collections/cisco/mso/plugins/module_utils/constants.py b/ansible_collections/cisco/mso/plugins/module_utils/constants.py index 2f3e0d472..ff08019df 100644 --- a/ansible_collections/cisco/mso/plugins/module_utils/constants.py +++ b/ansible_collections/cisco/mso/plugins/module_utils/constants.py @@ -16,7 +16,7 @@ SERVICE_NODE_CONNECTOR_MAP = { # 'external_epg': {'id': 'externalEpg', 'connector_type': 'route-peering'} } -YES_OR_NO_TO_BOOL_STRING_MAP = {"yes": "true", "no": "false"} +YES_OR_NO_TO_BOOL_STRING_MAP = {"yes": "true", "no": "false", True: "yes", False: "no"} NDO_4_UNIQUE_IDENTIFIERS = ["templateID", "autoRouteTargetImport", "autoRouteTargetExport"] @@ -38,3 +38,40 @@ EPG_U_SEG_ATTR_TYPE_MAP = { } EPG_U_SEG_ATTR_OPERATOR_LIST = ["equals", "contains", "starts_with", "ends_with"] + +AZURE_L4L7_CONNECTOR_TYPE_MAP = { + "none": "none", + "redirect": "redir", + "source_nat": "snat", + "destination_nat": "dnat", + "source_and_destination_nat": "snat_dnat", +} + +LISTENER_PROTOCOLS = ["http", "https", "tcp", "udp", "tls", "inherit"] + +LISTENER_SECURITY_POLICY_MAP = { + "default": "default", + "elb_sec_2016_18": "eLBSecurityPolicy-2016-08", + "elb_sec_fs_2018_06": "eLBSecurityPolicy-FS-2018-06", + "elb_sec_tls_1_2_2017_01": "eLBSecurityPolicy-TLS-1-2-2017-01", + "elb_sec_tls_1_2_ext_2018_06": "eLBSecurityPolicy-TLS-1-2-Ext-2018-06", + "elb_sec_tls_1_1_2017_01": "eLBSecurityPolicy-TLS-1-1-2017-01", + "elb_sec_2015_05": "eLBSecurityPolicy-2015-05", + "elb_sec_tls_1_0_2015_04": "eLBSecurityPolicy-TLS-1-0-2015-04", + "app_gw_ssl_default": "AppGwSslPolicyDefault", + "app_gw_ssl_2015_501": "AppGwSslPolicy20150501", + "app_gw_ssl_2017_401": "AppGwSslPolicy20170401", + "app_gw_ssl_2017_401s": "AppGwSslPolicy20170401S", +} + +LISTENER_ACTION_TYPE_MAP = {"fixed_response": "fixedResponse", "forward": "forward", "redirect": "redirect", "ha_port": "haPort"} + +LISTENER_CONTENT_TYPE_MAP = {"text_plain": "textPlain", "text_css": "textCSS", "text_html": "textHtml", "app_js": "appJS", "app_json": "appJson"} + +LISTENER_REDIRECT_CODE_MAP = { + "unknown": "unknown", + "permanently_moved": "permMoved", + "found": "found", + "see_other": "seeOther", + "temporary_redirect": "temporary", +} diff --git a/ansible_collections/cisco/mso/plugins/module_utils/mso.py b/ansible_collections/cisco/mso/plugins/module_utils/mso.py index 4bc9053ef..0f346d79b 100644 --- a/ansible_collections/cisco/mso/plugins/module_utils/mso.py +++ b/ansible_collections/cisco/mso/plugins/module_utils/mso.py @@ -22,7 +22,16 @@ from ansible.module_utils.six.moves.urllib.parse import urlencode, urljoin from ansible.module_utils.urls import fetch_url from ansible.module_utils._text import to_native, to_text from ansible.module_utils.connection import Connection -from ansible_collections.cisco.mso.plugins.module_utils.constants import NDO_API_VERSION_PATH_FORMAT +from ansible_collections.cisco.mso.plugins.module_utils.constants import ( + NDO_API_VERSION_PATH_FORMAT, + AZURE_L4L7_CONNECTOR_TYPE_MAP, + LISTENER_REDIRECT_CODE_MAP, + LISTENER_CONTENT_TYPE_MAP, + LISTENER_ACTION_TYPE_MAP, + LISTENER_PROTOCOLS, +) +from ansible_collections.cisco.nd.plugins.module_utils.nd import NDModule + try: from requests_toolbelt.multipart.encoder import MultipartEncoder @@ -50,7 +59,7 @@ def issubset(subset, superset): return True # Both objects have a different type - if type(subset) != type(superset): + if type(subset) is not type(superset): return False for key, value in subset.items(): @@ -205,7 +214,11 @@ def mso_service_graph_node_spec(): def mso_service_graph_node_device_spec(): return dict( - name=dict(type="str", required=True), + device_name=dict(type="str", aliases=["name"], required=True), + provider_connector_type=dict(type="str", choices=list(AZURE_L4L7_CONNECTOR_TYPE_MAP.keys())), + provider_interface=dict(type="str"), + consumer_connector_type=dict(type="str", choices=["none", "redirect"]), + consumer_interface=dict(type="str"), ) @@ -239,6 +252,13 @@ def mso_site_anp_epg_bulk_staticport_spec(): ) +def ndo_remote_user_spec(): + return dict( + name=dict(type="str", required=True), + login_domain=dict(type="str", required=True), + ) + + # Copied from ansible's module uri.py (url): https://github.com/ansible/ansible/blob/cdf62edc65f564fff6b7e575e084026fa7faa409/lib/ansible/modules/uri.py def write_file(module, url, dest, content, resp, tmpsrc=None): # create a tempfile with some test content @@ -321,6 +341,8 @@ class MSOModule(object): self.status = None self.url = None self.httpapi_logs = list() + self.site_type = None # on-premise or cloud + self.cloud_provider_type = None # aws or azure or gcp if self.module._debug: self.module.warn("Enable debug output because ANSIBLE_DEBUG was set.") @@ -845,6 +867,13 @@ class MSOModule(object): ids.append(dict(roleId=r.get("id"), accessType=access_type)) return ids + def lookup_site_type(self, site_data): + """Get site type(AWS, AZURE or physical)""" + site_type = site_data.get("platform") + if site_type == "cloud": + self.cloud_provider_type = site_data.get("cloudProviders")[0] + self.site_type = site_type + def lookup_site(self, site, ignore_not_found_error=False): """Look up a site and return its id""" if site is None: @@ -858,6 +887,8 @@ class MSOModule(object): return None if "id" not in s: self.fail_json(msg="Site lookup failed for site '{0}': {1}".format(site, s)) + + self.lookup_site_type(s) return s.get("id") def lookup_sites(self, sites, ignore_not_found_error=False): @@ -916,26 +947,65 @@ class MSOModule(object): users.append("admin") ids = [] + if self.platform == "nd": + nd = NDModule(self.module) + remote_users = nd.request("/nexus/infra/api/aaa/v4/remoteusers", method="GET") + local_users = nd.request("/nexus/infra/api/aaa/v4/localusers", method="GET") + for user in users: + user_dict = dict() if self.platform == "nd": - u = self.get_obj("users", loginID=user, api_version="v2") + user_dict = self.get_user_from_list_of_users(user, local_users) + if user_dict is None: + user_dict = self.get_user_from_list_of_users(user, remote_users) else: - u = self.get_obj("users", username=user) - if not u and not ignore_not_found_error: + user_dict = self.get_obj("users", username=user) + if not user_dict and not ignore_not_found_error: self.fail_json(msg="User '{0}' is not a valid user name.".format(user)) - elif (not u or "id" not in u) and ignore_not_found_error: + elif (not user_dict or "id" not in user_dict) and ignore_not_found_error: self.module.warn("User '{0}' is not a valid user name.".format(user)) return ids - if "id" not in u: - if "userID" not in u: - self.fail_json(msg="User lookup failed for user '{0}': {1}".format(user, u)) - id = dict(userId=u.get("userID")) + if "id" not in user_dict: + if "userID" not in user_dict: + self.fail_json(msg="User lookup failed for user '{0}': {1}".format(user, user_dict)) + id = dict(userId=user_dict.get("userID")) else: - id = dict(userId=u.get("id")) + id = dict(userId=user_dict.get("id")) if id in ids: self.fail_json(msg="User '{0}' is duplicate.".format(user)) ids.append(id) + return ids + def get_user_from_list_of_users(self, user_name, list_of_users, login_domain=""): + """Get user from list of users""" + for user in list_of_users.get("items"): + if user.get("spec").get("loginID") == user_name and (login_domain == "" or user.get("spec").get("loginDomain") == login_domain): + return user.get("spec") + return None + + def lookup_remote_users(self, remote_users, ignore_not_found_error=False): + ids = [] + if self.platform == "nd": + nd = NDModule(self.module) + remote_users_data = nd.request("/nexus/infra/api/aaa/v4/remoteusers", method="GET") + for remote_user in remote_users: + user_dict = dict() + if self.platform == "nd": + user_dict = self.get_user_from_list_of_users(remote_user.get("name"), remote_users_data, remote_user.get("login_domain")) + if not user_dict and not ignore_not_found_error: + self.fail_json(msg="User '{0}' is not a valid user name.".format(remote_user.get("name"))) + elif (not user_dict or "id" not in user_dict) and ignore_not_found_error: + self.module.warn("User '{0}' is not a valid user name.".format(remote_user.get("name"))) + return ids + if "id" not in user_dict: + if "userID" not in user_dict: + self.fail_json(msg="User lookup failed for user '{0}': {1}".format(remote_user.get("name"), user_dict)) + id = dict(userId=user_dict.get("userID")) + else: + id = dict(userId=user_dict.get("id")) + if id in ids: + self.fail_json(msg="User '{0}' is duplicate.".format(remote_user.get("name"))) + ids.append(id) return ids def create_label(self, label, label_type): @@ -1112,7 +1182,7 @@ class MSOModule(object): """Create a DHCP policy from input""" if data is None: return None - if type(data) == list: + if isinstance(data, list): dhcps = [] for dhcp in data: if "dhcp_option_policy" in dhcp: @@ -1330,6 +1400,9 @@ class MSOModule(object): return node_objs def lookup_service_node_device(self, site_id, tenant, device_name=None, service_node_type=None, ignore_not_found_error=False): + if self.site_type == "cloud": + tenant = "{0}/{1}".format(tenant, self.site_type) + if service_node_type is None: node_devices = self.query_objs("sites/{0}/aci/tenants/{1}/devices".format(site_id, tenant), key="devices") else: @@ -1393,3 +1466,121 @@ class MSOModule(object): def validate_schema(self, schema_id): return self.request("schemas/{id}/validate".format(id=schema_id), method="GET") + + def input_validation(self, attr_name, attr_value, required_attributes, target_object, object_position=None, object_name=None): + if attr_name in (None, "") or attr_value in (None, ""): + self.module.fail_json(msg="The attribute and value must be set") + + empty_attributes = [attribute for attribute in required_attributes if target_object.get(attribute) in (None, "", [], {}, 0)] + + if object_position is not None and object_name is not None and empty_attributes: + self.module.fail_json( + msg="When the '{0}' is '{1}', the {2} attributes must be set at the object position: {3} and the object name: {4}".format( + attr_name, attr_value, empty_attributes, object_position, object_name + ) + ) + elif object_position is not None and object_name is None and empty_attributes: + self.module.fail_json( + msg="When the '{0}' is '{1}', the {2} attributes must be set at the object position: {3}".format( + attr_name, attr_value, empty_attributes, object_position + ) + ) + elif object_position is None and object_name is not None and empty_attributes: + self.module.fail_json( + msg="When the '{0}' is '{1}', the {2} attributes must be set and the object name: {3}".format( + attr_name, attr_value, empty_attributes, object_name + ) + ) + elif empty_attributes: + self.module.fail_json(msg="When the '{0}' is '{1}', the {2} attributes must be set".format(attr_name, attr_value, empty_attributes)) + + +def service_node_ref_str_to_dict(serviceNodeRefStr): + serviceNodeRefTokens = serviceNodeRefStr.split("/") + return dict( + schemaId=serviceNodeRefTokens[2], + serviceGraphName=serviceNodeRefTokens[6], + serviceNodeName=serviceNodeRefTokens[8], + templateName=serviceNodeRefTokens[4], + ) + + +def mso_schema_site_contract_service_graph_spec(): + return dict( + cluster_interface_device=dict(type="str", required=True, aliases=["cluster_device", "device", "device_name"]), + provider_connector_cluster_interface=dict( + type="str", required=True, aliases=["provider_cluster_interface", "provider_interface", "provider_interface_name"] + ), + provider_connector_redirect_policy_tenant=dict(type="str", aliases=["provider_redirect_policy_tenant", "provider_tenant"]), + provider_connector_redirect_policy=dict(type="str", aliases=["provider_redirect_policy", "provider_policy"]), + consumer_connector_cluster_interface=dict( + type="str", required=True, aliases=["consumer_cluster_interface", "consumer_interface", "consumer_interface_name"] + ), + consumer_connector_redirect_policy_tenant=dict(type="str", aliases=["consumer_redirect_policy_tenant", "consumer_tenant"]), + consumer_connector_redirect_policy=dict(type="str", aliases=["consumer_redirect_policy", "consumer_policy"]), + consumer_subnet_ips=dict(type="list", elements="str"), + ) + + +def listener_ssl_certificates_spec(): + return dict( + name=dict(type="str", required=True), + certificate_store=dict(type="str", choices=["default", "iam", "acm"], required=True), + ) + + +def listener_rules_provider_epg_ref_spec(): + return dict( + schema=dict(type="str"), + template=dict(type="str"), + anp_name=dict(type="str", required=True, aliases=["anp"]), + epg_name=dict(type="str", required=True, aliases=["epg"]), + ) + + +def listener_rules_health_check_spec(): + return dict( + port=dict(type="int"), + protocol=dict(type="str", choices=LISTENER_PROTOCOLS), + path=dict(type="str"), + interval=dict(type="int"), + timeout=dict(type="int"), + unhealthy_threshold=dict(type="int"), + use_host_from_rule=dict(type="bool"), + success_code=dict(type="str"), + host=dict(type="str"), + ) + + +def listener_rules_spec(): + return dict( + name=dict(type="str", required=True), + floating_ip=dict(type="str"), + priority=dict(type="int", required=True), + host=dict(type="str"), + path=dict(type="str"), + action=dict(type="str"), + action_type=dict(type="str", required=True, choices=list(LISTENER_ACTION_TYPE_MAP)), + content_type=dict(type="str", choices=list(LISTENER_CONTENT_TYPE_MAP)), + port=dict(type="int"), + protocol=dict(type="str", choices=LISTENER_PROTOCOLS), + provider_epg=dict( + type="dict", + options=listener_rules_provider_epg_ref_spec(), + ), + url_type=dict(type="str", choices=["original", "custom"]), + custom_url=dict(type="str"), + redirect_host_name=dict(type="str"), + redirect_path=dict(type="str"), + redirect_query=dict(type="str"), + response_code=dict(type="str"), + response_body=dict(type="str"), + redirect_protocol=dict(type="str", choices=LISTENER_PROTOCOLS), + redirect_port=dict(type="int"), + redirect_code=dict(type="str", choices=list(LISTENER_REDIRECT_CODE_MAP)), + health_check=dict( + type="dict", + options=listener_rules_health_check_spec(), + ), + target_ip_type=dict(type="str", choices=["unspecified", "primary", "secondary"]), + ) diff --git a/ansible_collections/cisco/mso/plugins/module_utils/schema.py b/ansible_collections/cisco/mso/plugins/module_utils/schema.py index ce1bd36c7..75da0d1de 100644 --- a/ansible_collections/cisco/mso/plugins/module_utils/schema.py +++ b/ansible_collections/cisco/mso/plugins/module_utils/schema.py @@ -112,6 +112,24 @@ class MSOSchema: self.mso.fail_json(msg=msg) self.schema_objects["template_anp_epg"] = match + def set_template_anp_epg_contract(self, contract_ref, relation_type, fail_module=True): + """ + Get template endpoint group contract item that matches the reference and type of an contract. + :param contract_ref: Reference of the contract to match. -> Str + :param relation_type: Relation_type of the contract to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Template epg item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template_anp_epg"]) + kv_list = [KVPair("contractRef", contract_ref), KVPair("relationshipType", relation_type)] + match, existing = self.get_object_from_list(self.schema_objects["template_anp_epg"].details.get("contractRelationships"), kv_list) + if not match and fail_module: + msg = "Provided Contract Reference '{0}' with type '{1}' not matching existing contacts(s): {2}".format( + contract_ref, relation_type, ", ".join(existing) + ) + self.mso.fail_json(msg=msg) + self.schema_objects["template_anp_epg_contract"] = match + def set_template_anp_epg_useg_attr(self, useg_attr, fail_module=True): """ Get template endpoint group item that matches the name of an EPG uSeg Attribute. @@ -237,3 +255,60 @@ class MSOSchema: msg = "Provided Site uSeg Attribute '{0}' does not match the existing Site uSeg Attribute(s): {1}".format(useg_attr, ", ".join(existing)) self.mso.fail_json(msg=msg) self.schema_objects["site_anp_epg_useg_attribute"] = match + + def set_site_contract(self, contract_name, fail_module=True): + """ + Get site contract item that matches the name of a contract. + :param contract_name: Name of the contract to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Site contract item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template", "site"]) + kv_list = [ + KVPair( + "contractRef", self.mso.contract_ref(schema_id=self.id, template=self.schema_objects["template"].details.get("name"), contract=contract_name) + ) + ] + match, existing = self.get_object_from_list(self.schema_objects["site"].details.get("contracts"), kv_list) + if not match and fail_module: + msg = "Provided Contract '{0}' not matching existing site contract(s): {1}".format(contract_name, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["site_contract"] = match + + def set_site_service_graph(self, site_service_graph, fail_module=True): + """ + Get site item that matches the name of a service graph. + :param service_graph: Name of the service graph to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Site service graph item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["template", "site"]) + + kv_list = [ + KVPair( + "serviceGraphRef", + self.mso.service_graph_ref(schema_id=self.id, template=self.schema_objects["template"].details.get("name"), service_graph=site_service_graph), + ) + ] + + site_service_graph = self.schema_objects["site"].details.get("serviceGraphs") + match, existing = self.get_object_from_list(site_service_graph, kv_list) + if not match and fail_module: + msg = "Provided Site Service Graph '{0}' not matching existing site service graph(s): {1}".format(site_service_graph, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["site_service_graph"] = match + + def set_site_anp_epg_static_port(self, path, fail_module=True): + """ + Get site anp epg static port path item that matches the path of Static Port. + :param path: Path of the Static Port to match. -> Str + :param fail_module: When match is not found fail the ansible module. -> Bool + :return: Site anp epg item. -> Item(Int, Dict) | None + """ + self.validate_schema_objects_present(["site_anp_epg"]) + kv_list = [KVPair("path", path)] + match, existing = self.get_object_from_list(self.schema_objects["site_anp_epg"].details.get("staticPorts"), kv_list) + if not match and fail_module: + msg = "Provided Static Port Path '{0}' not matching existing static port path(s): {1}".format(path, ", ".join(existing)) + self.mso.fail_json(msg=msg) + self.schema_objects["site_anp_epg_static_port"] = match |