summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cisco/mso/plugins/module_utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-26 04:06:02 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-26 04:06:02 +0000
commite3eb94c23206603103f3c4faec6c227f59a1544c (patch)
treef2639459807ba88f55fc9c54d745bd7075d7f15c /ansible_collections/cisco/mso/plugins/module_utils
parentReleasing progress-linux version 9.4.0+dfsg-1~progress7.99u1. (diff)
downloadansible-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')
-rw-r--r--ansible_collections/cisco/mso/plugins/module_utils/constants.py39
-rw-r--r--ansible_collections/cisco/mso/plugins/module_utils/mso.py217
-rw-r--r--ansible_collections/cisco/mso/plugins/module_utils/schema.py75
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