summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cisco/mso/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-26 04:05:57 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-26 04:05:57 +0000
commit0dcbb2c58231264c2f0a0374733b5e9cf8747e1f (patch)
tree7f133117f9ebecefdc96e42e01ee7557247d5d8a /ansible_collections/cisco/mso/plugins
parentAdding debian version 9.4.0+dfsg-1. (diff)
downloadansible-0dcbb2c58231264c2f0a0374733b5e9cf8747e1f.tar.xz
ansible-0dcbb2c58231264c2f0a0374733b5e9cf8747e1f.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')
-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
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_backup.py10
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py5
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_label.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py5
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_rest.py5
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_role.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema.py70
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py1
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py9
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py208
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py569
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_useg_attribute.py18
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py5
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_contract_service_graph.py376
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_contract_service_graph_listener.py739
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py40
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py164
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py3
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py21
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py5
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py358
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_useg_attribute.py16
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py3
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py3
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py3
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py5
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py5
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py11
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py10
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_site.py4
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_tenant.py36
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py9
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_user.py6
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/mso_version.py1
-rw-r--r--ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py5
73 files changed, 2573 insertions, 650 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
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_backup.py b/ansible_collections/cisco/mso/plugins/modules/mso_backup.py
index fc2564b82..d7ceab4fb 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_backup.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_backup.py
@@ -83,7 +83,6 @@ EXAMPLES = r"""
description: via Ansible
location_type: local
state: present
- delegate_to: localhost
- name: Create a new remote backup
cisco.mso.mso_backup:
@@ -95,7 +94,6 @@ EXAMPLES = r"""
location_type: remote
remote_location: ansible_test
state: present
- delegate_to: localhost
- name: Move backup to remote location
cisco.mso.mso_backup:
@@ -106,7 +104,6 @@ EXAMPLES = r"""
remote_location: ansible_test
remote_path: test
state: move
- delegate_to: localhost
- name: Download a backup
cisco.mso.mso_backup:
@@ -116,7 +113,6 @@ EXAMPLES = r"""
backup: Backup
destination: ./
state: download
- delegate_to: localhost
- name: Upload a backup
cisco.mso.mso_backup:
@@ -125,7 +121,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
backup: ./Backup
state: upload
- delegate_to: localhost
- name: Restore a backup
cisco.mso.mso_backup:
@@ -134,7 +129,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
backup: Backup
state: restore
- delegate_to: localhost
- name: Remove a Backup
cisco.mso.mso_backup:
@@ -143,7 +137,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
backup: Backup
state: absent
- delegate_to: localhost
- name: Query a backup
cisco.mso.mso_backup:
@@ -152,7 +145,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
backup: Backup
state: query
- delegate_to: localhost
register: query_result
- name: Query a backup with its complete name
@@ -162,7 +154,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
backup: Backup_20200721220043
state: query
- delegate_to: localhost
register: query_result
- name: Query all backups
@@ -171,7 +162,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py b/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py
index e97b59b2e..a96c1da97 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_backup_schedule.py
@@ -65,7 +65,7 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
+ register: query_result
- name: Set backup schedule
cisco.mso.mso_backup_schedule:
@@ -76,7 +76,6 @@ EXAMPLES = r"""
frequency_length: 7
remote_location: ansible_test
state: present
- delegate_to: localhost
- name: Set backup schedule with date and time
cisco.mso.mso_backup_schedule:
@@ -90,7 +89,6 @@ EXAMPLES = r"""
start_time: 20:57:36
start_date: 2023-04-09
state: present
- delegate_to: localhost
- name: Delete backup schedule
cisco.mso.mso_backup_schedule:
@@ -98,7 +96,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: absent
- delegate_to: localhost
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py
index e9b7f23d3..7568c4704 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy.py
@@ -56,7 +56,6 @@ EXAMPLES = r"""
description: "My Test DHCP Policy"
tenant: ansible_test
state: present
- delegate_to: localhost
- name: Remove DHCP Option Policy
cisco.mso.mso_dhcp_option_policy:
@@ -65,7 +64,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
dhcp_option_policy: my_test_dhcp_policy
state: absent
- delegate_to: localhost
- name: Query a DHCP Option Policy
cisco.mso.mso_dhcp_option_policy:
@@ -74,7 +72,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
dhcp_option_policy: my_test_dhcp_policy
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all DHCP Option Policies
cisco.mso.mso_dhcp_option_policy:
@@ -82,7 +80,7 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py
index f4c397c24..4d5edd14f 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_option_policy_option.py
@@ -63,7 +63,6 @@ EXAMPLES = r"""
id: 1
data: Data stored in the option
state: present
- delegate_to: localhost
- name: Remove a option to a DHCP Option Policy
cisco.mso.mso_dhcp_option_policy_option:
@@ -73,7 +72,6 @@ EXAMPLES = r"""
dhcp_option_policy: my_test_dhcp_policy
name: ansible_test
state: absent
- delegate_to: localhost
- name: Query a option to a DHCP Option Policy
cisco.mso.mso_dhcp_option_policy_option:
@@ -83,7 +81,7 @@ EXAMPLES = r"""
dhcp_option_policy: my_test_dhcp_policy
name: ansible_test
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all option of a DHCP Option Policy
cisco.mso.mso_dhcp_option_policy_option:
@@ -92,7 +90,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
dhcp_option_policy: my_test_dhcp_policy
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py
index 394825feb..59fdf4db7 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy.py
@@ -55,7 +55,6 @@ EXAMPLES = r"""
description: "My Test DHCP Policy"
tenant: ansible_test
state: present
- delegate_to: localhost
- name: Remove DHCP Relay Policy
cisco.mso.mso_dhcp_relay_policy:
@@ -64,7 +63,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
dhcp_relay_policy: my_test_dhcp_policy
state: absent
- delegate_to: localhost
- name: Query a DHCP Relay Policy
cisco.mso.mso_dhcp_relay_policy:
@@ -73,7 +71,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
dhcp_relay_policy: my_test_dhcp_policy
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all DHCP Relay Policies
cisco.mso.mso_dhcp_relay_policy:
@@ -81,7 +79,7 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py
index 760d90430..e17ee92bc 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_dhcp_relay_policy_provider.py
@@ -82,7 +82,6 @@ EXAMPLES = r"""
application_profile: ansible_test
endpoint_group: ansible_test
state: present
- delegate_to: localhost
- name: Remove a provider to a DHCP Relay Policy
cisco.mso.mso_dhcp_relay_policy_provider:
@@ -96,7 +95,6 @@ EXAMPLES = r"""
application_profile: ansible_test
endpoint_group: ansible_test
state: absent
- delegate_to: localhost
- name: Query a provider to a DHCP Relay Policy
cisco.mso.mso_dhcp_relay_policy_provider:
@@ -110,7 +108,7 @@ EXAMPLES = r"""
application_profile: ansible_test
endpoint_group: ansible_test
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all provider of a DHCP Relay Policy
cisco.mso.mso_dhcp_relay_policy_provider:
@@ -119,7 +117,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
dhcp_relay_policy: my_test_dhcp_policy
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_label.py b/ansible_collections/cisco/mso/plugins/modules/mso_label.py
index f4f05f7de..2b65de521 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_label.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_label.py
@@ -49,7 +49,6 @@ EXAMPLES = r"""
label: Belgium
type: site
state: present
- delegate_to: localhost
- name: Remove a label
cisco.mso.mso_label:
@@ -58,7 +57,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
label: Belgium
state: absent
- delegate_to: localhost
- name: Query a label
cisco.mso.mso_label:
@@ -67,7 +65,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
label: Belgium
state: query
- delegate_to: localhost
register: query_result
- name: Query all labels
@@ -76,7 +73,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py b/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py
index 10546563f..b3ec5e405 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_remote_location.py
@@ -89,7 +89,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
register: backups
- name: Query a remote location
@@ -99,7 +98,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
remote_location: ansible_test
state: query
- delegate_to: localhost
+ register: query_result
- name: Configure a remote location
cisco.mso.mso_remote_location:
@@ -114,7 +113,6 @@ EXAMPLES = r"""
remote_username: username
remote_password: password
state: present
- delegate_to: localhost
- name: Delete a remote location
cisco.mso.mso_remote_location:
@@ -123,7 +121,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
remote_location: ansible_test
state: absent
- delegate_to: localhost
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_rest.py b/ansible_collections/cisco/mso/plugins/modules/mso_rest.py
index ae05b093b..04387c505 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_rest.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_rest.py
@@ -83,7 +83,6 @@ EXAMPLES = r"""
"sites": [],
"_updateVersion": 0
}
- delegate_to: localhost
- name: Query schema
cisco.mso.mso_rest:
@@ -92,7 +91,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
path: /mso/api/v1/schemas
method: get
- delegate_to: localhost
+ register: query_result
- name: Patch schema (YAML)
cisco.mso.mso_rest:
@@ -109,7 +108,6 @@ EXAMPLES = r"""
displayName: AP2
epgs: []
_updateVersion: 0
- delegate_to: localhost
- name: Add a tenant from a templated payload file from templates
cisco.mso.mso_rest:
@@ -119,7 +117,6 @@ EXAMPLES = r"""
method: post
path: /api/v1/tenants
content: "{{ lookup('template', 'mso/tenant.json.j2') }}"
- delegate_to: localhost
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_role.py b/ansible_collections/cisco/mso/plugins/modules/mso_role.py
index cfa4483b0..a99df655a 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_role.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_role.py
@@ -115,7 +115,6 @@ EXAMPLES = r"""
- manage-tenant-schemas
- manage-users
state: present
- delegate_to: localhost
- name: Remove a role
cisco.mso.mso_role:
@@ -124,7 +123,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
role: readOnly
state: absent
- delegate_to: localhost
- name: Query a role
cisco.mso.mso_role:
@@ -133,7 +131,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
role: readOnly
state: query
- delegate_to: localhost
register: query_result
- name: Query all roles
@@ -142,7 +139,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema.py
index 2eba13ac9..7ba2df609 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema.py
@@ -2,6 +2,8 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com>
+
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
@@ -18,21 +20,32 @@ description:
- Manage schemas on Cisco ACI Multi-Site.
author:
- Dag Wieers (@dagwieers)
+- Akini Ross (@akinross)
options:
schema:
description:
- The name of the schema.
type: str
aliases: [ name ]
+ id:
+ description:
+ - The id of the schema.
+ - This parameter is required when the C(schema) needs to be updated.
+ type: str
+ description:
+ description:
+ - The description of the schema.
+ type: str
state:
description:
- Use C(absent) for removing.
- Use C(query) for listing an object or multiple objects.
+ - Use C(present) for creating or updating. Only supported on versions of MSO that are 4.1 or greater.
type: str
- choices: [ absent, query ]
+ choices: [ absent, query, present ]
default: query
notes:
-- Due to restrictions of the MSO REST API this module cannot create empty schemas (i.e. schemas without templates).
+- Due to restrictions of the MSO REST API this module can only create empty schemas (i.e. schemas without templates) on versions of MSO that are 4.1 or greater.
Use the M(cisco.mso.mso_schema_template) to automatically create schemas with templates.
seealso:
- module: cisco.mso.mso_schema_site
@@ -41,6 +54,15 @@ extends_documentation_fragment: cisco.mso.modules
"""
EXAMPLES = r"""
+- name: Create schema
+ cisco.mso.mso_schema:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: present
+ delegate_to: localhost
+
- name: Remove schemas
cisco.mso.mso_schema:
host: mso_host
@@ -48,7 +70,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
schema: Schema 1
state: absent
- delegate_to: localhost
- name: Query a schema
cisco.mso.mso_schema:
@@ -57,7 +78,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
schema: Schema 1
state: query
- delegate_to: localhost
register: query_result
- name: Query all schemas
@@ -66,7 +86,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
register: query_result
"""
@@ -81,12 +100,9 @@ def main():
argument_spec = mso_argument_spec()
argument_spec.update(
schema=dict(type="str", aliases=["name"]),
- # messages=dict(type='dict'),
- # associations=dict(type='list'),
- # health_faults=dict(type='list'),
- # references=dict(type='dict'),
- # policy_states=dict(type='list'),
- state=dict(type="str", default="query", choices=["absent", "query"]),
+ id=dict(type="str"),
+ description=dict(type="str"),
+ state=dict(type="str", default="query", choices=["absent", "query", "present"]),
)
module = AnsibleModule(
@@ -94,28 +110,48 @@ def main():
supports_check_mode=True,
required_if=[
["state", "absent", ["schema"]],
+ ["state", "present", ["schema"]],
],
)
schema = module.params.get("schema")
+ schema_id = module.params.get("id")
+ description = module.params.get("description")
state = module.params.get("state")
mso = MSOModule(module)
-
- schema_id = None
path = "schemas"
# Query for existing object(s)
if schema:
- mso.existing = mso.get_obj(path, displayName=schema)
+ if schema_id:
+ mso.existing = mso.get_obj(path, id=schema_id)
+ else:
+ mso.existing = mso.get_obj(path, displayName=schema)
+
if mso.existing:
- schema_id = mso.existing.get("id")
+ if not schema_id:
+ schema_id = mso.existing.get("id")
path = "schemas/{id}".format(id=schema_id)
else:
mso.existing = mso.query_objs(path)
- if state == "query":
- pass
+ mso.previous = mso.existing
+ if state == "present":
+ mso.sanitize(dict(displayName=schema, id=schema_id, description=description), collate=True)
+ if mso.existing:
+ ops = []
+ if mso.existing.get("displayName") != schema:
+ ops.append(dict(op="replace", path="/displayName", value=schema))
+ if mso.existing.get("description") != description and description is not None:
+ ops.append(dict(op="replace", path="/description", value=description))
+
+ if not module.check_mode:
+ mso.request(path, method="PATCH", data=ops)
+ else:
+ if not module.check_mode:
+ mso.request(path, method="POST", data=dict(displayName=schema, description=description))
+ mso.existing = mso.proposed
elif state == "absent":
mso.previous = mso.existing
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py
index 840fb12ca..f02322f9e 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_clone.py
@@ -49,7 +49,6 @@ EXAMPLES = r"""
source_schema: Source_Schema
destination_schema: Destination_Schema
state: clone
- delegate_to: localhost
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py
index 83a5213a2..b8e1a7cda 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site.py
@@ -58,7 +58,6 @@ EXAMPLES = r"""
site: Site1
template: Template 1
state: present
- delegate_to: localhost
- name: Remove a site from a schema
cisco.mso.mso_schema_site:
@@ -69,7 +68,6 @@ EXAMPLES = r"""
site: Site1
template: Template 1
state: absent
- delegate_to: localhost
- name: Query a schema site
cisco.mso.mso_schema_site:
@@ -80,7 +78,6 @@ EXAMPLES = r"""
site: Site1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
- name: Query all schema sites
@@ -90,7 +87,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
schema: Schema 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py
index 0552b6a31..39c5225cd 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp.py
@@ -64,7 +64,6 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
state: present
- delegate_to: localhost
- name: Remove a site ANP
cisco.mso.mso_schema_site_anp:
@@ -76,7 +75,6 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
state: absent
- delegate_to: localhost
- name: Query a specific site ANP
cisco.mso.mso_schema_site_anp:
@@ -88,7 +86,6 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
state: query
- delegate_to: localhost
register: query_result
- name: Query all site ANPs
@@ -100,7 +97,6 @@ EXAMPLES = r"""
site: Site1
template: Template1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py
index 1c2ad6433..6c23a0ca0 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg.py
@@ -75,7 +75,6 @@ EXAMPLES = r"""
anp: ANP1
epg: EPG1
state: present
- delegate_to: localhost
- name: Remove a site EPG
cisco.mso.mso_schema_site_anp_epg:
@@ -88,7 +87,6 @@ EXAMPLES = r"""
anp: ANP1
epg: EPG1
state: absent
- delegate_to: localhost
- name: Query a specific site EPGs
cisco.mso.mso_schema_site_anp_epg:
@@ -101,7 +99,6 @@ EXAMPLES = r"""
anp: ANP1
epg: EPG1
state: query
- delegate_to: localhost
register: query_result
- name: Query all site EPGs
@@ -114,7 +111,6 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py
index eda60cfd1..aa302a991 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_bulk_staticport.py
@@ -164,6 +164,10 @@ notes:
seealso:
- module: cisco.mso.mso_schema_site_anp_epg
- module: cisco.mso.mso_schema_template_anp_epg
+deprecated:
+ removed_in: 3.0.0
+ alternative: Use M(cisco.mso.mso_schema_site_anp_epg_staticport) with option `force_replace=true` instead.
+ why: The module has been merged to centralise all static port functionality into M(cisco.mso.mso_schema_site_anp_epg_staticport).
extends_documentation_fragment: cisco.mso.modules
"""
@@ -190,7 +194,6 @@ EXAMPLES = r"""
- path: eth1/3
vlan: 124
state: present
- delegate_to: localhost
- name: Add a new static fex port to a site EPG
cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
@@ -215,7 +218,6 @@ EXAMPLES = r"""
vlan: 124
- fex: 151
state: present
- delegate_to: localhost
- name: Add a new static VPC to a site EPG
cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
@@ -245,7 +247,6 @@ EXAMPLES = r"""
mode: untagged
deployment_immediacy: lazy
state: present
- delegate_to: localhost
- name: Remove static ports from a site EPG
cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
@@ -258,7 +259,6 @@ EXAMPLES = r"""
anp: ANP1
epg: EPG1
state: absent
- delegate_to: localhost
- name: Query all site EPG static ports
cisco.mso.mso_schema_site_anp_epg_bulk_staticport:
@@ -270,7 +270,6 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py
index c684a27be..4e6097f7f 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_domain.py
@@ -2,6 +2,8 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Nirav Katarmal (@nkatarmal-crest) <nirav.katarmal@crestdatasys.com>
+# Copyright: (c) 2023, Mabille Florent (@fmabille09) <florent.mabille@smals.be>
+# Copyright: (c) 2024, Akini Ross (@akinross) <akinross@cisco.com>
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
@@ -18,6 +20,8 @@ description:
- Manage site-local EPG domains in schema template on Cisco ACI Multi-Site.
author:
- Nirav Katarmal (@nkatarmal-crest)
+- Mabille Florent (@fmabille09)
+- Akini Ross (@akinross)
options:
schema:
description:
@@ -68,46 +72,95 @@ options:
choices: [ immediate, lazy, pre-provision ]
micro_seg_vlan_type:
description:
- - Virtual LAN type for microsegmentation. This attribute can only be used with vmmDomain domain association.
- - vlan is currently the only accepted value.
+ - Virtual LAN type for microsegmentation. This attribute can only be used with VMM domain association.
type: str
+ choices: [ vlan ]
micro_seg_vlan:
description:
- - Virtual LAN for microsegmentation. This attribute can only be used with vmmDomain domain association.
+ - Virtual LAN for microsegmentation. This attribute can only be used with VMM domain association.
type: int
port_encap_vlan_type:
description:
- - Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association.
- - vlan is currently the only accepted value.
+ - Virtual LAN type for port encap. This attribute can only be used with VMM domain association.
type: str
+ choices: [ vlan ]
port_encap_vlan:
description:
- - Virtual LAN type for port encap. This attribute can only be used with vmmDomain domain association.
+ - Virtual LAN type for port encap. This attribute can only be used with VMM domain association.
type: int
vlan_encap_mode:
description:
- - Which VLAN enacap mode to use. This attribute can only be used with vmmDomain domain association.
+ - Which VLAN enacap mode to use. This attribute can only be used with VMM domain association.
type: str
choices: [ static, dynamic ]
allow_micro_segmentation:
description:
- - Specifies microsegmentation is enabled or not. This attribute can only be used with vmmDomain domain association.
+ - Specifies microsegmentation is enabled or not. This attribute can only be used with VMM domain association.
type: bool
switch_type:
description:
- - Which switch type to use with this domain association. This attribute can only be used with vmmDomain domain association.
+ - Which switch type to use with this domain association. This attribute can only be used with VMM domain association.
type: str
switching_mode:
description:
- - Which switching mode to use with this domain association. This attribute can only be used with vmmDomain domain association.
+ - Which switching mode to use with this domain association. This attribute can only be used with VMM domain association.
type: str
enhanced_lagpolicy_name:
description:
- - EPG enhanced lagpolicy name. This attribute can only be used with vmmDomain domain association.
+ - EPG enhanced lagpolicy name. This attribute can only be used with VMM domain association.
type: str
enhanced_lagpolicy_dn:
description:
- - Distinguished name of EPG lagpolicy. This attribute can only be used with vmmDomain domain association.
+ - Distinguished name of EPG lagpolicy. This attribute can only be used with VMM domain association.
+ type: str
+ delimiter:
+ description:
+ - Which delimiter to use with this domain association. This attribute can only be used with VMM domain association.
+ type: str
+ choices: [ '+', '|', '~', '!', '@', '^', '=' ]
+ binding_type:
+ description:
+ - Which binding_type to use with this domain association. This attribute can only be used with VMM domain association.
+ type: str
+ default: none
+ choices: [ static, dynamic, none, ephemeral ]
+ num_ports:
+ description:
+ - Number of ports for the binding type. This attribute can only be used with VMM domain association.
+ type: int
+ port_allocation:
+ description:
+ - Port allocation for the binding type. This attribute can only be used with VMM domain association and binding type in static.
+ - Required when O(binding_type=static).
+ type: str
+ choices: [ elastic, fixed ]
+ netflow_pref:
+ description:
+ - The netflow preference state of the domain association. This attribute can only be used with VMM domain association.
+ type: str
+ default: disabled
+ choices: [ enabled, disabled ]
+ allow_promiscuous:
+ description:
+ - The allow promiscuous state of the domain association. This attribute can only be used with VMM domain association.
+ type: str
+ default: reject
+ choices: [ accept, reject ]
+ forged_transmits:
+ description:
+ - The forged transmits state of the domain association. This attribute can only be used with VMM domain association.
+ type: str
+ default: reject
+ choices: [ accept, reject ]
+ mac_changes:
+ description:
+ - The mac changes state of the domain association. This attribute can only be used with VMM domain association.
+ type: str
+ default: reject
+ choices: [ accept, reject ]
+ custom_epg_name:
+ description:
+ - Which custom_epg_name to use with this domain association. This attribute can only be used with VMM domain association.
type: str
state:
description:
@@ -142,9 +195,8 @@ EXAMPLES = r"""
deployment_immediacy: lazy
resolution_immediacy: pre-provision
state: present
- delegate_to: localhost
-- name: Remove a domain from a site EPG
+- name: Add a new domain to a site EPG with all possible attributes set
cisco.mso.mso_schema_site_anp_epg_domain:
host: mso_host
username: admin
@@ -156,10 +208,28 @@ EXAMPLES = r"""
epg: EPG1
domain_association_type: vmmDomain
domain_profile: 'VMware-VMM'
- deployment_immediacy: lazy
- resolution_immediacy: pre-provision
- state: absent
- delegate_to: localhost
+ deployment_immediacy: immediate
+ resolution_immediacy: immediate
+ micro_seg_vlan_type: vlan
+ micro_seg_vlan: 100
+ port_encap_vlan_type: vlan
+ port_encap_vlan: 100
+ vlan_encap_mode: static
+ allow_micro_segmentation: true
+ switch_type: default
+ switching_mode: native
+ enhanced_lagpolicy_name: ansible_lag_name
+ enhanced_lagpolicy_dn: ansible_lag_dn
+ delimiter: '|'
+ binding_type: static
+ num_ports: 2
+ port_allocation: elastic
+ netflow_pref: enabled
+ allow_promiscuous: accept
+ forged_transmits: accept
+ mac_changes: accept
+ custom_epg_name: ansible_custom_epg
+ state: present
- name: Query a domain associated with a specific site EPG
cisco.mso.mso_schema_site_anp_epg_domain:
@@ -174,7 +244,6 @@ EXAMPLES = r"""
domain_association_type: vmmDomain
domain_profile: 'VMware-VMM'
state: query
- delegate_to: localhost
register: query_result
- name: Query all domains associated with a site EPG
@@ -188,8 +257,23 @@ EXAMPLES = r"""
anp: ANP1
epg: EPG1
state: query
- delegate_to: localhost
register: query_result
+
+- name: Remove a domain from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_domain:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ domain_association_type: vmmDomain
+ domain_profile: 'VMware-VMM'
+ deployment_immediacy: lazy
+ resolution_immediacy: pre-provision
+ state: absent
"""
RETURN = r"""
@@ -212,9 +296,9 @@ def main():
deployment_immediacy=dict(type="str", choices=["immediate", "lazy"]),
resolution_immediacy=dict(type="str", choices=["immediate", "lazy", "pre-provision"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
- micro_seg_vlan_type=dict(type="str"),
+ micro_seg_vlan_type=dict(type="str", choices=["vlan"]),
micro_seg_vlan=dict(type="int"),
- port_encap_vlan_type=dict(type="str"),
+ port_encap_vlan_type=dict(type="str", choices=["vlan"]),
port_encap_vlan=dict(type="int"),
vlan_encap_mode=dict(type="str", choices=["static", "dynamic"]),
allow_micro_segmentation=dict(type="bool"),
@@ -222,6 +306,15 @@ def main():
switching_mode=dict(type="str"),
enhanced_lagpolicy_name=dict(type="str"),
enhanced_lagpolicy_dn=dict(type="str"),
+ binding_type=dict(type="str", default="none", choices=["dynamic", "ephemeral", "none", "static"]),
+ port_allocation=dict(type="str", choices=["elastic", "fixed"]),
+ num_ports=dict(type="int"),
+ netflow_pref=dict(type="str", default="disabled", choices=["enabled", "disabled"]),
+ allow_promiscuous=dict(type="str", default="reject", choices=["accept", "reject"]),
+ forged_transmits=dict(type="str", default="reject", choices=["accept", "reject"]),
+ mac_changes=dict(type="str", default="reject", choices=["accept", "reject"]),
+ delimiter=dict(type="str", choices=["+", "|", "~", "!", "@", "^", "="]),
+ custom_epg_name=dict(type="str"),
)
module = AnsibleModule(
@@ -230,6 +323,12 @@ def main():
required_if=[
["state", "absent", ["domain_association_type", "domain_profile", "deployment_immediacy", "resolution_immediacy"]],
["state", "present", ["domain_association_type", "domain_profile", "deployment_immediacy", "resolution_immediacy"]],
+ ["binding_type", "static", ["port_allocation"]],
+ ],
+ required_together=[
+ ("micro_seg_vlan_type", "micro_seg_vlan"),
+ ("port_encap_vlan_type", "port_encap_vlan"),
+ ("enhanced_lagpolicy_name", "enhanced_lagpolicy_dn"),
],
)
@@ -253,6 +352,15 @@ def main():
switching_mode = module.params.get("switching_mode")
enhanced_lagpolicy_name = module.params.get("enhanced_lagpolicy_name")
enhanced_lagpolicy_dn = module.params.get("enhanced_lagpolicy_dn")
+ binding_type = module.params.get("binding_type")
+ port_allocation = module.params.get("port_allocation")
+ num_ports = module.params.get("num_ports")
+ netflow_pref = module.params.get("netflow_pref")
+ allow_promiscuous = module.params.get("allow_promiscuous")
+ forged_transmits = module.params.get("forged_transmits")
+ mac_changes = module.params.get("mac_changes")
+ delimiter = module.params.get("delimiter")
+ custom_epg_name = module.params.get("custom_epg_name")
mso = MSOModule(module)
@@ -397,46 +505,54 @@ def main():
if domain_association_type == "vmmDomain":
vmmDomainProperties = {}
if micro_seg_vlan_type and micro_seg_vlan:
- microSegVlan = dict(vlanType=micro_seg_vlan_type, vlan=micro_seg_vlan)
- vmmDomainProperties["microSegVlan"] = microSegVlan
- elif not micro_seg_vlan_type and micro_seg_vlan:
- mso.fail_json(msg="micro_seg_vlan_type is required when micro_seg_vlan is provided.")
- elif micro_seg_vlan_type and not micro_seg_vlan:
- mso.fail_json(msg="micro_seg_vlan is required when micro_seg_vlan_type is provided.")
+ vmmDomainProperties["microSegVlan"] = dict(vlanType=micro_seg_vlan_type, vlan=micro_seg_vlan)
if port_encap_vlan_type and port_encap_vlan:
- portEncapVlan = dict(vlanType=port_encap_vlan_type, vlan=port_encap_vlan)
- vmmDomainProperties["portEncapVlan"] = portEncapVlan
- elif not port_encap_vlan_type and port_encap_vlan:
- mso.fail_json(msg="port_encap_vlan_type is required when port_encap_vlan is provided.")
- elif port_encap_vlan_type and not port_encap_vlan:
- mso.fail_json(msg="port_encap_vlan is required when port_encap_vlan_type is provided.")
+ vmmDomainProperties["portEncapVlan"] = dict(vlanType=port_encap_vlan_type, vlan=port_encap_vlan)
if vlan_encap_mode:
vmmDomainProperties["vlanEncapMode"] = vlan_encap_mode
- if allow_micro_segmentation:
+ if allow_micro_segmentation is not None:
vmmDomainProperties["allowMicroSegmentation"] = allow_micro_segmentation
+
if switch_type:
vmmDomainProperties["switchType"] = switch_type
+
if switching_mode:
vmmDomainProperties["switchingMode"] = switching_mode
if enhanced_lagpolicy_name and enhanced_lagpolicy_dn:
- enhancedLagPol = dict(name=enhanced_lagpolicy_name, dn=enhanced_lagpolicy_dn)
- epgLagPol = dict(enhancedLagPol=enhancedLagPol)
- vmmDomainProperties["epgLagPol"] = epgLagPol
- elif not enhanced_lagpolicy_name and enhanced_lagpolicy_dn:
- mso.fail_json(msg="enhanced_lagpolicy_name is required when enhanced_lagpolicy_dn is provided.")
- elif enhanced_lagpolicy_name and not enhanced_lagpolicy_dn:
- mso.fail_json(msg="enhanced_lagpolicy_dn is required when enhanced_lagpolicy_name is provided.")
+ vmmDomainProperties["epgLagPol"] = dict(enhancedLagPol=dict(name=enhanced_lagpolicy_name, dn=enhanced_lagpolicy_dn))
+
+ if delimiter:
+ vmmDomainProperties["delimiter"] = delimiter
+
+ if binding_type:
+ vmmDomainProperties["bindingType"] = binding_type
+ vmmDomainProperties["numPorts"] = num_ports
+
+ if port_allocation:
+ vmmDomainProperties["portAllocation"] = port_allocation
+
+ if netflow_pref:
+ vmmDomainProperties["netflowPref"] = netflow_pref
+
+ if allow_promiscuous:
+ vmmDomainProperties["allowPromiscuous"] = allow_promiscuous
+
+ if forged_transmits:
+ vmmDomainProperties["forgedTransmits"] = forged_transmits
+
+ if mac_changes:
+ vmmDomainProperties["macChanges"] = mac_changes
+
+ if custom_epg_name:
+ vmmDomainProperties["customEpgName"] = custom_epg_name
if vmmDomainProperties:
new_domain["vmmDomainProperties"] = vmmDomainProperties
- properties = ["allowMicroSegmentation", "epgLagPol", "switchType", "switchingMode", "vlanEncapMode", "portEncapVlan", "microSegVlan"]
- for property in properties:
- if property in vmmDomainProperties:
- new_domain[property] = vmmDomainProperties[property]
+ new_domain.update(vmmDomainProperties)
# If payload is empty, anp and EPG already exist at site level
if not payload:
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py
index ffd3c682e..54780ceed 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_selector.py
@@ -103,7 +103,6 @@ EXAMPLES = r"""
operator: in
value: test
state: present
- delegate_to: localhost
- name: Remove a Selector from a site EPG
cisco.mso.mso_schema_site_anp_epg_selector:
@@ -117,7 +116,6 @@ EXAMPLES = r"""
epg: EPG 1
selector: selector_1
state: absent
- delegate_to: localhost
- name: Query a specific Selector
cisco.mso.mso_schema_site_anp_epg_selector:
@@ -131,7 +129,6 @@ EXAMPLES = r"""
epg: EPG 1
selector: selector_1
state: query
- delegate_to: localhost
register: query_result
- name: Query all Selectors
@@ -145,7 +142,6 @@ EXAMPLES = r"""
anp: ANP 1
epg: EPG 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py
index 01e2ac386..64a2bcddb 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticleaf.py
@@ -88,7 +88,6 @@ EXAMPLES = r"""
leaf: Leaf1
vlan: 123
state: present
- delegate_to: localhost
- name: Remove a static leaf from a site EPG
cisco.mso.mso_schema_site_anp_epg_staticleaf:
@@ -102,7 +101,6 @@ EXAMPLES = r"""
epg: EPG1
leaf: Leaf1
state: absent
- delegate_to: localhost
- name: Query a specific site EPG static leaf
cisco.mso.mso_schema_site_anp_epg_staticleaf:
@@ -116,7 +114,6 @@ EXAMPLES = r"""
epg: EPG1
leaf: Leaf1
state: query
- delegate_to: localhost
register: query_result
- name: Query all site EPG static leafs
@@ -129,7 +126,6 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py
index d05336c52..d22ae1fcf 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_staticport.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2024, Akini Ross (@akinross) <akinross@cisco.com>
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
@@ -18,6 +19,7 @@ description:
- Manage site-local EPG static ports in schema template on Cisco ACI Multi-Site.
author:
- Dag Wieers (@dagwieers)
+- Akini Ross (@akinross)
options:
schema:
description:
@@ -47,9 +49,9 @@ options:
type:
description:
- The path type of the static port
- - vpc is used for a Virtual Port Channel
- - dpc is used for a Direct Port Channel
- - port is used for a single interface
+ - C(vpc) is used for a Virtual Port Channel
+ - C(dpc) is used for a Direct Port Channel
+ - C(port) is used for a single interface
type: str
choices: [ port, vpc, dpc ]
default: port
@@ -94,6 +96,75 @@ options:
description:
- Primary micro-seg VLAN of static port.
type: int
+ force_replace:
+ description:
+ - Replaces all the configured static port(s) with the provided static port(s).
+ - This option can only be used in combination with the O(static_ports) option.
+ - In combination with the O(state=absent) and without any static port configuration all configured static port(s) will be removed.
+ type: bool
+ static_ports:
+ description:
+ - A list of Static Ports associated to this EPG.
+ - All configured Static Ports will be replaced with the provided Static Ports when used with O(force_replace=true).
+ - Only the provided Static Ports will be added, updated or removed when used with O(force_replace=false).
+ - In combination with the O(state=query) all provided Static Ports must be found else the task will fail.
+ - When I(static_ports) attributes are not provided the module attributes will be used.
+ - For each Static Ports provided in the list, the following attributes must be resolved
+ - I(static_ports.type)
+ - I(static_ports.pod)
+ - I(static_ports.leaf)
+ - I(static_ports.path)
+ - I(static_ports.vlan)
+ type: list
+ elements: dict
+ suboptions:
+ type:
+ description:
+ - The path type of the static port
+ - C(vpc) is used for a Virtual Port Channel
+ - C(dpc) is used for a Direct Port Channel
+ - C(port) is used for a single interface
+ type: str
+ choices: [ port, vpc, dpc ]
+ pod:
+ description:
+ - The pod of the static port.
+ type: str
+ leaf:
+ description:
+ - The leaf of the static port.
+ type: str
+ fex:
+ description:
+ - The fex id of the static port.
+ type: str
+ path:
+ description:
+ - The path of the static port.
+ type: str
+ vlan:
+ description:
+ - The port encapsulation VLAN id of the static port.
+ type: int
+ deployment_immediacy:
+ description:
+ - The deployment immediacy of the static port.
+ - C(immediate) means B(Deploy immediate).
+ - C(lazy) means B(Deploy on demand).
+ type: str
+ choices: [ immediate, lazy ]
+ mode:
+ description:
+ - The mode of the static port.
+ - C(native) means B(Access (802.1p)).
+ - C(regular) means B(Trunk).
+ - C(untagged) means B(Access (untagged)).
+ type: str
+ choices: [ native, regular, untagged ]
+ primary_micro_segment_vlan:
+ description:
+ - Primary micro-seg VLAN of static port.
+ type: int
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -129,7 +200,6 @@ EXAMPLES = r"""
vlan: 126
deployment_immediacy: immediate
state: present
- delegate_to: localhost
- name: Add a new static fex port to a site EPG
mso_schema_site_anp_epg_staticport:
@@ -149,7 +219,6 @@ EXAMPLES = r"""
vlan: 126
deployment_immediacy: lazy
state: present
- delegate_to: localhost
- name: Add a new static VPC to a site EPG
mso_schema_site_anp_epg_staticport:
@@ -169,9 +238,8 @@ EXAMPLES = r"""
mode: untagged
deployment_immediacy: lazy
state: present
- delegate_to: localhost
-- name: Remove a static port from a site EPG
+- name: Add two new static port to a site EPG
cisco.mso.mso_schema_site_anp_epg_staticport:
host: mso_host
username: admin
@@ -181,12 +249,40 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
epg: EPG1
- type: port
- pod: pod-1
- leaf: 101
- path: eth1/1
- state: absent
- delegate_to: localhost
+ static_ports:
+ - pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ - pod: pod-2
+ leaf: 101
+ path: eth2/1
+ vlan: 128
+ deployment_immediacy: immediate
+ state: present
+
+- name: Replace all existing static pors on a site EPG with 2 new static ports
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ force_replace: true
+ static_ports:
+ - pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ - pod: pod-2
+ leaf: 101
+ path: eth2/1
+ vlan: 128
+ deployment_immediacy: immediate
+ state: present
- name: Query a specific site EPG static port
cisco.mso.mso_schema_site_anp_epg_staticport:
@@ -203,7 +299,28 @@ EXAMPLES = r"""
leaf: 101
path: eth1/1
state: query
- delegate_to: localhost
+ register: query_result
+
+- name: Query a list of static ports
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ static_ports:
+ - pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ - pod: pod-2
+ leaf: 101
+ path: eth2/1
+ vlan: 128
+ state: query
register: query_result
- name: Query all site EPG static ports
@@ -216,15 +333,66 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
state: query
- delegate_to: localhost
register: query_result
+
+- name: Remove a static port from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ type: port
+ pod: pod-1
+ leaf: 101
+ path: eth1/1
+ state: absent
+
+- name: Remove two static ports from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ static_ports:
+ - pod: pod-1
+ leaf: 101
+ path: eth1/1
+ vlan: 126
+ - pod: pod-2
+ leaf: 101
+ path: eth2/1
+ vlan: 128
+ deployment_immediacy: immediate
+ state: absent
+
+- name: Remove all existing static pors from a site EPG
+ cisco.mso.mso_schema_site_anp_epg_staticport:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ site: Site1
+ template: Template1
+ anp: ANP1
+ epg: EPG1
+ force_replace: true
+ state: absent
"""
RETURN = r"""
"""
from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_site_anp_epg_bulk_staticport_spec
+from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema
def main():
@@ -235,25 +403,23 @@ def main():
template=dict(type="str", required=True),
anp=dict(type="str", required=True),
epg=dict(type="str", required=True),
+ force_replace=dict(type="bool"),
type=dict(type="str", default="port", choices=["port", "vpc", "dpc"]),
- pod=dict(type="str"), # This parameter is not required for querying all objects
- leaf=dict(type="str"), # This parameter is not required for querying all objects
- fex=dict(type="str"), # This parameter is not required for querying all objects
- path=dict(type="str"), # This parameter is not required for querying all objects
- vlan=dict(type="int"), # This parameter is not required for querying all objects
- primary_micro_segment_vlan=dict(type="int"), # This parameter is not required for querying all objects
+ pod=dict(type="str"),
+ leaf=dict(type="str"),
+ fex=dict(type="str"),
+ path=dict(type="str"),
+ vlan=dict(type="int"),
+ primary_micro_segment_vlan=dict(type="int"),
deployment_immediacy=dict(type="str", default="lazy", choices=["immediate", "lazy"]),
mode=dict(type="str", default="untagged", choices=["native", "regular", "untagged"]),
+ static_ports=dict(type="list", elements="dict", options=mso_site_anp_epg_bulk_staticport_spec()),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
- required_if=[
- ["state", "absent", ["type", "pod", "leaf", "path", "vlan"]],
- ["state", "present", ["type", "pod", "leaf", "path", "vlan"]],
- ],
)
schema = module.params.get("schema")
@@ -270,174 +436,247 @@ def main():
primary_micro_segment_vlan = module.params.get("primary_micro_segment_vlan")
deployment_immediacy = module.params.get("deployment_immediacy")
mode = module.params.get("mode")
+ force_replace = module.params.get("force_replace")
+ static_ports = module.params.get("static_ports")
state = module.params.get("state")
- if path_type == "port" and fex is not None:
- # Select port path for fex if fex param is used
- portpath = "topology/{0}/paths-{1}/extpaths-{2}/pathep-[{3}]".format(pod, leaf, fex, path)
- elif path_type == "vpc":
- portpath = "topology/{0}/protpaths-{1}/pathep-[{2}]".format(pod, leaf, path)
- else:
- portpath = "topology/{0}/paths-{1}/pathep-[{2}]".format(pod, leaf, path)
+ if not static_ports and state in ["present", "absent"]:
+ key_list = ["pod", "leaf", "path", "vlan"]
+ required_missing = [key for key in key_list if module.params.get(key)]
+ if len(required_missing) != 4 and not (len(required_missing) == 0 and state == "absent" and force_replace):
+ module.fail_json(
+ msg="state is present or absent but all of the following are missing: {0}.".format(
+ ", ".join([key for key in key_list if not module.params.get(key)]),
+ )
+ )
mso = MSOModule(module)
- # Get schema objects
- schema_id, schema_path, schema_obj = mso.query_schema(schema)
-
- # Get template
- templates = [t.get("name") for t in schema_obj.get("templates")]
- if template not in templates:
- mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
- template_idx = templates.index(template)
-
- # Get site
- site_id = mso.lookup_site(site)
-
- # Get site_idx
- if not schema_obj.get("sites"):
- mso.fail_json(msg="No site associated with template '{0}'. Associate the site with the template using mso_schema_site.".format(template))
- sites = [(s.get("siteId"), s.get("templateName")) for s in schema_obj.get("sites")]
- sites_list = [s.get("siteId") + "/" + s.get("templateName") for s in schema_obj.get("sites")]
- if (site_id, template) not in sites:
- mso.fail_json(
- msg="Provided site/siteId/template '{0}/{1}/{2}' does not exist. "
- "Existing siteIds/templates: {3}".format(site, site_id, template, ", ".join(sites_list))
- )
-
- # Schema-access uses indexes
- site_idx = sites.index((site_id, template))
- # Path-based access uses site_id-template
- site_template = "{0}-{1}".format(site_id, template)
+ mso_schema = MSOSchema(mso, schema, template, site)
+ mso_schema.set_template_anp(anp)
+ mso_schema.set_template_anp_epg(epg)
+ mso_schema.set_site_anp(anp, False)
+ mso_schema.set_site_anp_epg(epg, False)
- payload = dict()
ops = []
- op_path = ""
-
- # Get ANP
- anp_ref = mso.anp_ref(schema_id=schema_id, template=template, anp=anp)
- anps = [a.get("anpRef") for a in schema_obj["sites"][site_idx]["anps"]]
- anps_in_temp = [a.get("name") for a in schema_obj["templates"][template_idx]["anps"]]
- if anp not in anps_in_temp:
- mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps)))
- else:
- # Update anp index at template level
- template_anp_idx = anps_in_temp.index(anp)
-
- # If anp not at site level but exists at template level
- if anp_ref not in anps:
- op_path = "/sites/{0}/anps/-".format(site_template)
- payload.update(
- anpRef=dict(
- schemaId=schema_id,
- templateName=template,
- anpName=anp,
- ),
- )
- else:
- # Update anp index at site level
- anp_idx = anps.index(anp_ref)
+ # Create missing site anp and site epg if not present
+ # Logic is only needed for NDO version below 4.x when validate false flag was still available
+ # This did not trigger the auto creation of site anp and site epg during template anp and epg creation or ataching site to template
+ # Coverage misses this two conditionals when testing on 4.x and above
+ if state == "present" and not mso_schema.schema_objects.get("site_anp"):
+ ops.append(
+ dict(
+ op="add",
+ path="/sites/{0}-{1}/anps/-".format(mso_schema.schema_objects.get("site").details.get("siteId"), template),
+ value=dict(epgRef=dict(schemaId=mso_schema.id, templateName=template, anpName=anp, epgName=epg)),
+ )
+ )
- # Get EPG
- epg_ref = mso.epg_ref(schema_id=schema_id, template=template, anp=anp, epg=epg)
+ if state == "present" and not mso_schema.schema_objects.get("site_anp_epg"):
+ ops.append(
+ dict(
+ op="add",
+ path="/sites/{0}-{1}/anps/{2}/epgs/-".format(mso_schema.schema_objects.get("site").details.get("siteId"), template, anp),
+ value=dict(anpRef=dict(schemaId=mso_schema.id, templateName=template, anpName=anp)),
+ )
+ )
- # If anp exists at site level
- if "anpRef" not in payload:
- epgs = [e.get("epgRef") for e in schema_obj["sites"][site_idx]["anps"][anp_idx]["epgs"]]
+ static_ports_path = "/sites/{0}-{1}/anps/{2}/epgs/{3}/staticPorts".format(
+ mso_schema.schema_objects.get("site").details.get("siteId"),
+ template,
+ anp,
+ epg,
+ )
+ static_port_path = "{0}/-".format(static_ports_path)
+
+ full_paths = []
+ if static_ports:
+ found_static_ports = []
+ found_full_paths = []
+ set_existing_static_ports(mso, mso_schema, full_paths)
+ for static_port in static_ports:
+ overwrite_static_path_unprovided_attributes(
+ mso, static_port, path_type, pod, leaf, fex, path, vlan, primary_micro_segment_vlan, deployment_immediacy, mode
+ )
+ full_path = get_full_static_path(
+ static_port.get("type"), static_port.get("pod"), static_port.get("leaf"), static_port.get("fex"), static_port.get("path")
+ )
+ mso_schema.set_site_anp_epg_static_port(full_path, False)
+ if mso_schema.schema_objects.get("site_anp_epg_static_port") is not None:
+ found_static_ports.append(mso_schema.schema_objects["site_anp_epg_static_port"].details)
+ found_full_paths.append(full_path)
+
+ elif path_type and pod and leaf and path and vlan:
+ full_path = get_full_static_path(path_type, pod, leaf, fex, path)
+ mso_schema.set_site_anp_epg_static_port(full_path, False)
+ if mso_schema.schema_objects.get("site_anp_epg_static_port") is not None:
+ mso.existing = mso_schema.schema_objects["site_anp_epg_static_port"].details
+ static_port_path = "{0}/{1}".format(static_ports_path, mso_schema.schema_objects["site_anp_epg_static_port"].index)
+ else:
+ set_existing_static_ports(mso, mso_schema, full_paths)
- # If anp already at site level AND if epg not at site level (or) anp not at site level
- if ("anpRef" not in payload and epg_ref not in epgs) or "anpRef" in payload:
- epgs_in_temp = [e.get("name") for e in schema_obj["templates"][template_idx]["anps"][template_anp_idx]["epgs"]]
+ if state == "query":
+ if static_ports:
+ if len(found_static_ports) == len(static_ports):
+ mso.existing = found_static_ports
+ else:
+ not_found_static_ports = [
+ "Provided Static Port Path '{0}' not found".format(full_paths[index])
+ for index, static_port in enumerate(static_ports)
+ if full_paths[index] not in found_full_paths
+ ]
+ mso.fail_json(msg=not_found_static_ports)
+ elif not mso.existing and full_path:
+ mso.fail_json(msg="Provided Static Port Path '{0}' not found".format(full_path))
+ mso.exit_json()
- # If EPG not at template level - Fail
- if epg not in epgs_in_temp:
- mso.fail_json(msg="Provided EPG '{0}' does not exist. Existing EPGs: {1} epgref {2}".format(epg, ", ".join(epgs_in_temp), epg_ref))
+ mso.previous = mso.existing
- # EPG at template level but not at site level. Create payload at site level for EPG
+ if state == "absent" and mso.existing:
+ if static_ports and not force_replace:
+ mso.proposed = mso.existing.copy()
+ remove_index = []
+ for found_full_path in found_full_paths:
+ if found_full_path in full_paths:
+ index = full_paths.index(found_full_path)
+ remove_index.append(index)
+ # The list index should not shift when removing static ports from the list
+ # By sorting the indexes found in reverse order, we assure that the highest index is removed first by the NDO backend
+ # This logic is to avoid removing the wrong static ports
+ for index in reversed(sorted(remove_index)):
+ mso.proposed.pop(index)
+ ops.append(dict(op="remove", path="{0}/{1}".format(static_ports_path, index)))
+ mso.sent = mso.proposed
+ elif not force_replace:
+ mso.sent = mso.existing = {}
+ ops.append(dict(op="remove", path=static_port_path))
else:
- new_epg = dict(
- epgRef=dict(
- schemaId=schema_id,
- templateName=template,
- anpName=anp,
- epgName=epg,
- )
- )
+ mso.sent = mso.proposed = mso.existing = []
+ ops.append(dict(op="remove", path=static_ports_path))
- # If anp not in payload then, anp already exists at site level. New payload will only have new EPG payload
- if "anpRef" not in payload:
- op_path = "/sites/{0}/anps/{1}/epgs/-".format(site_template, anp)
- payload = new_epg
+ elif state == "present":
+ if static_ports and force_replace:
+ mso.sent = mso.proposed = [
+ get_static_port_payload(
+ get_full_static_path(
+ static_port.get("type"),
+ static_port.get("pod"),
+ static_port.get("leaf"),
+ static_port.get("fex"),
+ static_port.get("path"),
+ ),
+ static_port.get("deployment_immediacy"),
+ static_port.get("mode"),
+ static_port.get("vlan"),
+ static_port.get("type"),
+ static_port.get("primary_micro_segment_vlan"),
+ )
+ for static_port in static_ports
+ ]
+ if mso.existing:
+ ops.append(dict(op="replace", path=static_ports_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=static_ports_path, value=mso.sent))
+ elif static_ports:
+ mso.sent = mso.proposed = mso.existing.copy()
+ for static_port in static_ports:
+ full_path = get_full_static_path(
+ static_port.get("type"), static_port.get("pod"), static_port.get("leaf"), static_port.get("fex"), static_port.get("path")
+ )
+ payload = get_static_port_payload(
+ full_path,
+ static_port.get("deployment_immediacy"),
+ static_port.get("mode"),
+ static_port.get("vlan"),
+ static_port.get("type"),
+ static_port.get("primary_micro_segment_vlan"),
+ )
+ if full_path not in found_full_paths:
+ ops.append(dict(op="add", path=static_port_path, value=payload))
+ mso.proposed.append(payload)
+ else:
+ index = found_full_paths.index(full_path)
+ mso.proposed[index] = payload
+ ops.append(dict(op="replace", path="{0}/{1}".format(static_ports_path, index), value=payload))
+ else:
+ payload = get_static_port_payload(full_path, deployment_immediacy, mode, vlan, path_type, primary_micro_segment_vlan)
+ mso.sanitize(payload, collate=True)
+ if mso.existing:
+ ops.append(dict(op="replace", path=static_port_path, value=mso.sent))
else:
- # If anp in payload, anp exists at site level. Update payload with EPG payload
- payload["epgs"] = [new_epg]
+ ops.append(dict(op="add", path=static_port_path, value=mso.sent))
+ mso.existing = payload
- # Update index of EPG at site level
- else:
- epg_idx = epgs.index(epg_ref)
+ mso.existing = mso.proposed
- # Get Leaf
- # If anp at site level and epg is at site level
- if "anpRef" not in payload and "epgRef" not in payload:
- portpaths = [p.get("path") for p in schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"]]
- if portpath in portpaths:
- portpath_idx = portpaths.index(portpath)
- port_path = "/sites/{0}/anps/{1}/epgs/{2}/staticPorts/{3}".format(site_template, anp, epg, portpath_idx)
- mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"][portpath_idx]
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(mso_schema.path, method="PATCH", data=ops)
- if state == "query":
- if leaf is None or vlan is None:
- mso.existing = schema_obj.get("sites")[site_idx]["anps"][anp_idx]["epgs"][epg_idx]["staticPorts"]
- elif not mso.existing:
- mso.fail_json(msg="Static port '{portpath}' not found".format(portpath=portpath))
- mso.exit_json()
+ mso.exit_json()
- ports_path = "/sites/{0}/anps/{1}/epgs/{2}/staticPorts".format(site_template, anp, epg)
- ops = []
- new_leaf = dict(
+
+def set_existing_static_ports(mso, mso_schema, full_paths):
+ mso.existing = []
+ if mso_schema.schema_objects.get("site_anp_epg"):
+ for existing_static_port in mso_schema.schema_objects["site_anp_epg"].details.get("staticPorts"):
+ full_paths.append(existing_static_port.get("path"))
+ mso.existing.append(existing_static_port)
+
+
+def get_static_port_payload(full_path, deployment_immediacy, mode, vlan, path_type, primary_micro_segment_vlan):
+ payload = dict(
deploymentImmediacy=deployment_immediacy,
mode=mode,
- path=portpath,
+ path=full_path,
portEncapVlan=vlan,
type=path_type,
)
if primary_micro_segment_vlan:
- new_leaf.update(microSegVlan=primary_micro_segment_vlan)
+ payload.update(microSegVlan=primary_micro_segment_vlan)
+ return payload
- # If payload is empty, anp and EPG already exist at site level
- if not payload:
- op_path = ports_path + "/-"
- payload = new_leaf
- # If payload exists
+def get_full_static_path(path_type, pod, leaf, fex, path):
+ if path_type == "port" and fex is not None:
+ return "topology/{0}/paths-{1}/extpaths-{2}/pathep-[{3}]".format(pod, leaf, fex, path)
+ elif path_type == "vpc":
+ return "topology/{0}/protpaths-{1}/pathep-[{2}]".format(pod, leaf, path)
else:
- # If anp already exists at site level
- if "anpRef" not in payload:
- payload["staticPorts"] = [new_leaf]
- else:
- payload["epgs"][0]["staticPorts"] = [new_leaf]
-
- mso.previous = mso.existing
- if state == "absent":
- if mso.existing:
- mso.sent = mso.existing = {}
- ops.append(dict(op="remove", path=port_path))
-
- elif state == "present":
- mso.sanitize(payload, collate=True)
-
- if mso.existing:
- ops.append(dict(op="replace", path=port_path, value=mso.sent))
- else:
- ops.append(dict(op="add", path=op_path, value=mso.sent))
-
- mso.existing = new_leaf
-
- if not module.check_mode:
- mso.request(schema_path, method="PATCH", data=ops)
-
- mso.exit_json()
+ return "topology/{0}/paths-{1}/pathep-[{2}]".format(pod, leaf, path)
+
+
+def overwrite_static_path_unprovided_attributes(mso, static_path, path_type, pod, leaf, fex, path, vlan, micro_vlan, deployment_immediacy, mode):
+ required_overwrites = []
+ if not static_path.get("type"):
+ static_path["type"] = path_type
+ if not static_path.get("pod"):
+ static_path["pod"] = pod
+ if not pod:
+ required_overwrites.append("pod")
+ if not static_path.get("leaf"):
+ static_path["leaf"] = leaf
+ if not leaf:
+ required_overwrites.append("leaf")
+ if not static_path.get("fex"):
+ static_path["fex"] = fex
+ if not static_path.get("path"):
+ static_path["path"] = path
+ if not path:
+ required_overwrites.append("path")
+ if not static_path.get("vlan"):
+ static_path["vlan"] = vlan
+ if not vlan:
+ required_overwrites.append("vlan")
+ if not static_path.get("primary_micro_segment_vlan"):
+ static_path["primary_micro_segment_vlan"] = micro_vlan
+ if not static_path.get("deployment_immediacy"):
+ static_path["deployment_immediacy"] = deployment_immediacy
+ if not static_path.get("mode"):
+ static_path["mode"] = mode
+
+ if required_overwrites:
+ mso.fail_json(msg="state is present but all of the following are missing: {0}.".format(", ".join(required_overwrites)))
if __name__ == "__main__":
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py
index e5aaeba37..ba55e7c07 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_subnet.py
@@ -100,7 +100,6 @@ EXAMPLES = r"""
epg: EPG1
subnet: 10.0.0.0/24
state: present
- delegate_to: localhost
- name: Remove a subnet from a site EPG
cisco.mso.mso_schema_site_anp_epg_subnet:
@@ -114,7 +113,6 @@ EXAMPLES = r"""
epg: EPG1
subnet: 10.0.0.0/24
state: absent
- delegate_to: localhost
- name: Query a specific site EPG subnet
cisco.mso.mso_schema_site_anp_epg_subnet:
@@ -128,7 +126,6 @@ EXAMPLES = r"""
epg: EPG1
subnet: 10.0.0.0/24
state: query
- delegate_to: localhost
register: query_result
- name: Query all site EPG subnets
@@ -141,7 +138,6 @@ EXAMPLES = r"""
template: Template1
anp: ANP1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_useg_attribute.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_useg_attribute.py
index 3030852f5..7e69f5481 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_useg_attribute.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_anp_epg_useg_attribute.py
@@ -74,7 +74,7 @@ options:
description:
- The uSeg Subnet can only be used when the I(attribute_type) is IP.
- Use C(false) to set the custom uSeg Subnet IP address to the uSeg Attribute.
- - Use C(true) to set the uSeg Subnet IP address to 0.0.0.0.
+ - Use C(true) to set the default uSeg Subnet IP address 0.0.0.0.
type: bool
state:
description:
@@ -104,7 +104,6 @@ EXAMPLES = r"""
useg_subnet: false
value: 10.0.0.0/24
state: present
- delegate_to: localhost
- name: Query a specific EPG uSeg attr with name
cisco.mso.mso_schema_site_anp_epg_useg_attribute:
@@ -118,7 +117,7 @@ EXAMPLES = r"""
name: useg_attr_ip
site: ansible_test
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all EPG uSeg attrs
cisco.mso.mso_schema_site_anp_epg_useg_attribute:
@@ -131,7 +130,7 @@ EXAMPLES = r"""
epg: EPG 1
site: ansible_test
state: query
- delegate_to: localhost
+ register: query_result
- name: Remove a uSeg attr from an EPG with name
cisco.mso.mso_schema_site_anp_epg_useg_attribute:
@@ -145,7 +144,6 @@ EXAMPLES = r"""
site: ansible_test
name: useg_attr_ip
state: absent
- delegate_to: localhost
"""
RETURN = r"""
@@ -251,11 +249,13 @@ def main():
payload = dict(name=name, displayName=name, description=description, type=EPG_U_SEG_ATTR_TYPE_MAP[attribute_type], value=value)
if attribute_type == "ip":
- if useg_subnet is False:
- payload["fvSubnet"] = True
- else:
- payload["fvSubnet"] = False
+ if useg_subnet:
+ if value != "" and value != "0.0.0.0" and value is not None:
+ mso.fail_json(msg="The value of uSeg subnet IP should be an empty string or 0.0.0.0, when the useg_subnet is set to true.")
+ payload["fvSubnet"] = useg_subnet
payload["value"] = "0.0.0.0"
+ else:
+ payload["fvSubnet"] = useg_subnet
mso.sanitize(payload, collate=True)
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py
index 35c352fb6..64a798b1a 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd.py
@@ -73,7 +73,6 @@ EXAMPLES = r"""
template: Template1
bd: BD1
state: present
- delegate_to: localhost
- name: Remove a site BD
cisco.mso.mso_schema_site_bd:
@@ -85,7 +84,6 @@ EXAMPLES = r"""
template: Template1
bd: BD1
state: absent
- delegate_to: localhost
- name: Query a specific site BD
cisco.mso.mso_schema_site_bd:
@@ -97,7 +95,6 @@ EXAMPLES = r"""
template: Template1
bd: BD1
state: query
- delegate_to: localhost
register: query_result
- name: Query all site BDs
@@ -109,7 +106,6 @@ EXAMPLES = r"""
site: Site1
template: Template1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py
index 8f9581294..2f562a684 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_l3out.py
@@ -94,7 +94,6 @@ EXAMPLES = r"""
l3out:
name: L3out1
state: present
- delegate_to: localhost
- name: Add a new site BD l3out with different schema and template
cisco.mso.mso_schema_site_bd_l3out:
@@ -110,7 +109,6 @@ EXAMPLES = r"""
schema: Schema2
template: Template2
state: present
- delegate_to: localhost
- name: Remove a site BD l3out
cisco.mso.mso_schema_site_bd_l3out:
@@ -124,7 +122,6 @@ EXAMPLES = r"""
l3out:
name: L3out1
state: absent
- delegate_to: localhost
- name: Query a specific site BD l3out
cisco.mso.mso_schema_site_bd_l3out:
@@ -138,7 +135,6 @@ EXAMPLES = r"""
l3out:
name: L3out1
state: query
- delegate_to: localhost
register: query_result
- name: Query all site BD l3outs
@@ -151,7 +147,6 @@ EXAMPLES = r"""
template: Template1
bd: BD1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py
index c4ab52eec..9bce8c2da 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_bd_subnet.py
@@ -111,7 +111,6 @@ EXAMPLES = r"""
bd: BD1
subnet: 11.11.11.0/24
state: present
- delegate_to: localhost
- name: Remove a site BD subnet
cisco.mso.mso_schema_site_bd_subnet:
@@ -124,7 +123,6 @@ EXAMPLES = r"""
bd: BD1
subnet: 11.11.11.0/24
state: absent
- delegate_to: localhost
- name: Query a specific site BD subnet
cisco.mso.mso_schema_site_bd_subnet:
@@ -137,7 +135,6 @@ EXAMPLES = r"""
bd: BD1
subnet: 11.11.11.0/24
state: query
- delegate_to: localhost
register: query_result
- name: Query all site BD subnets
@@ -150,7 +147,6 @@ EXAMPLES = r"""
template: Template1
bd: BD1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_contract_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_contract_service_graph.py
new file mode 100644
index 000000000..231ee4284
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_contract_service_graph.py
@@ -0,0 +1,376 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Sabari Jaganathan (@sajagana) <sajagana@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
+
+
+DOCUMENTATION = r"""
+---
+module: mso_schema_site_contract_service_graph
+short_description: Manage the service graph association with a contract in schema sites
+description:
+- Manage the service graph association with a contract in schema sites on Cisco ACI Multi-Site.
+- This module is only compatible with NDO versions 3.7 and 4.2+. NDO versions 4.0 and 4.1 are not supported.
+author:
+- Sabari Jaganathan (@sajagana)
+options:
+ tenant:
+ description:
+ - The name of the tenant.
+ type: str
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ contract:
+ description:
+ - The name of the contract.
+ type: str
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ service_graph_schema:
+ description:
+ - The name of the schema in which the service graph is located.
+ type: str
+ service_graph_template:
+ description:
+ - The name of the template in which the service graph is located.
+ type: str
+ service_graph:
+ description:
+ - The name of the service graph to associate with the site contract.
+ type: str
+ node_relationship:
+ description:
+ - A list of nodes and their connector details associated with the Service Graph.
+ type: list
+ elements: dict
+ suboptions:
+ cluster_interface_device:
+ description:
+ - The name of the cluster interface device.
+ type: str
+ required: true
+ aliases: [ cluster_device, device, device_name ]
+ provider_connector_cluster_interface:
+ description:
+ - The name of the cluster interface for the provider connector.
+ type: str
+ required: true
+ aliases: [ provider_cluster_interface, provider_interface, provider_interface_name ]
+ provider_connector_redirect_policy_tenant:
+ description:
+ - The name of the tenant for the provider connector redirect policy.
+ type: str
+ aliases: [ provider_redirect_policy_tenant, provider_tenant ]
+ provider_connector_redirect_policy:
+ description:
+ - The name of the redirect policy for the provider connector.
+ type: str
+ aliases: [ provider_redirect_policy, provider_policy ]
+ consumer_connector_cluster_interface:
+ description:
+ - The name of the cluster interface for the consumer connector.
+ type: str
+ required: true
+ aliases: [ consumer_cluster_interface, consumer_interface, consumer_interface_name ]
+ consumer_connector_redirect_policy_tenant:
+ description:
+ - The name of the tenant for the consumer connector redirect policy.
+ type: str
+ aliases: [ consumer_redirect_policy_tenant, consumer_tenant ]
+ consumer_connector_redirect_policy:
+ description:
+ - The name of the redirect policy for the consumer connector.
+ type: str
+ aliases: [ consumer_redirect_policy, consumer_policy ]
+ consumer_subnet_ips:
+ description:
+ - The list of subnet IPs for the consumer connector.
+ - The subnet IPs option is only available for the load balancer devices.
+ type: list
+ elements: str
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_contract_service_graph
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+
+EXAMPLES = r"""
+- name: Associate a service graph with a site contract
+ cisco.mso.mso_schema_site_contract_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: ansible_tenant
+ schema: ansible_schema
+ template: ansible_template1
+ site: ansible_test
+ contract: Contract1
+ service_graph_schema: ansible_schema
+ service_graph_template: ansible_template1
+ service_graph: sg
+ node_relationship:
+ - cluster_interface_device: ansible_tenant_firewall1
+ provider_connector_cluster_interface: clu_if1
+ provider_connector_redirect_policy: redirect_policy1
+ consumer_connector_cluster_interface: clu_if1
+ consumer_connector_redirect_policy: redirect_policy1
+ - cluster_interface_device: ansible_tenant_adc
+ provider_connector_cluster_interface: clu_if3
+ provider_connector_redirect_policy: redirect_policy1
+ consumer_connector_cluster_interface: clu_if3
+ consumer_connector_redirect_policy: redirect_policy1
+ consumer_subnet_ips: ["1.1.1.1/24", "4.4.4.4/24"]
+ - cluster_interface_device: ansible_tenant_other
+ provider_connector_cluster_interface: clu_if4
+ provider_connector_redirect_policy: redirect_policy1
+ consumer_connector_cluster_interface: clu_if4
+ consumer_connector_redirect_policy: redirect_policy1
+ state: present
+
+- name: Associate a service graph with a cloud site contract
+ cisco.mso.mso_schema_site_contract_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ tenant: ansible_tenant
+ schema: ansible_schema
+ template: ansible_template1
+ site: ansible_test
+ contract: Contract1
+ service_graph_schema: ansible_schema
+ service_graph_template: ansible_template1
+ service_graph: sg
+ state: present
+
+- name: Query a site contract service graph with contract name
+ cisco.mso.mso_schema_site_contract_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_schema
+ template: ansible_template1
+ contract: Contract1
+ site: ansible_test
+ state: query
+ register: query_result
+
+- name: Query all site contract service graphs associated with a site template
+ cisco.mso.mso_schema_site_contract_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_schema
+ template: ansible_template1
+ site: ansible_test
+ state: query
+ register: query_result
+
+- name: Remove a site contract service graph with contract name
+ cisco.mso.mso_schema_site_contract_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: ansible_schema
+ template: ansible_template1
+ site: ansible_test
+ contract: Contract1
+ state: absent
+"""
+
+RETURN = r"""
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_schema_site_contract_service_graph_spec
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ tenant=dict(type="str"),
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ contract=dict(type="str"),
+ site=dict(type="str", required=True),
+ service_graph_schema=dict(type="str"),
+ service_graph_template=dict(type="str"),
+ service_graph=dict(type="str"),
+ node_relationship=dict(type="list", elements="dict", options=mso_schema_site_contract_service_graph_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["contract"]],
+ ["state", "present", ["contract", "service_graph"]],
+ ],
+ )
+
+ tenant = module.params.get("tenant")
+ schema = module.params.get("schema")
+ template = module.params.get("template").replace(" ", "")
+ contract = module.params.get("contract")
+ site = module.params.get("site")
+ service_graph_schema = module.params.get("service_graph_schema")
+ service_graph_template = module.params.get("service_graph_template")
+ service_graph = module.params.get("service_graph")
+ node_relationship = module.params.get("node_relationship")
+ state = module.params.get("state")
+
+ mso = MSOModule(module)
+
+ mso_schema = MSOSchema(mso, schema, template)
+ mso_schema.set_template(template)
+
+ service_graph_schema_id = schema_id = mso.lookup_schema(schema, True)
+ service_graph_reference_schema = service_graph_schema or schema
+
+ # Get service graph reference schema id if the service graph schema is not matching with the schema
+ if service_graph_reference_schema != schema:
+ service_graph_schema_id = mso.lookup_schema(service_graph_reference_schema, True)
+ if service_graph_schema_id is None:
+ mso.fail_json(msg="Provided service_graph_schema: '{0}' does not exist.".format(service_graph_schema))
+
+ service_graph_reference_schema_id = service_graph_schema_id or schema_id
+ service_graph_reference_template = service_graph_template or template
+
+ # Get site id
+ site_id = mso.lookup_site(site)
+ mso_schema.set_site(template, site)
+ mso_schema.set_site_contract(contract, False)
+
+ if mso.site_type == "on-premise" and state == "present":
+ # When site type is on-premise, both node_relationship and tenant are required
+ if node_relationship is None and tenant is None:
+ mso.fail_json(msg="The node_relationship and tenant attributes are required when state is present and site type is on-premise.")
+ elif node_relationship is None:
+ mso.fail_json(msg="The node_relationship attribute is required when state is present and site type is on-premise.")
+ elif tenant is None:
+ mso.fail_json(msg="The tenant attribute is required when state is present and site type is on-premise.")
+
+ if contract and mso_schema.schema_objects["site_contract"] is not None:
+ site_contract_service_graph = mso_schema.schema_objects["site_contract"].details.get("serviceGraphRelationship")
+ if site_contract_service_graph and service_graph:
+ site_contract_service_graph_name = site_contract_service_graph.get("serviceGraphRef").split("/")[-1]
+ if site_contract_service_graph_name == service_graph:
+ mso.existing = site_contract_service_graph
+ else:
+ mso.fail_json(msg="The service graph: {0} does not associated with the site contract: {1}.".format(service_graph, contract))
+ elif site_contract_service_graph and service_graph is None:
+ mso.existing = site_contract_service_graph
+ elif contract is not None and mso_schema.schema_objects["site_contract"] is None:
+ mso.fail_json(msg="The site contract: {0} does not exist.".format(contract))
+ elif contract is None and mso_schema.schema_objects["site"].details.get("contracts"):
+ mso.existing = [
+ contract.get("serviceGraphRelationship")
+ for contract in mso_schema.schema_objects["site"].details.get("contracts")
+ if contract.get("serviceGraphRelationship")
+ ]
+
+ if state == "query":
+ mso.exit_json()
+
+ site_contract_service_graph_path = "/sites/{0}-{1}/contracts/{2}/serviceGraphRelationship".format(site_id, service_graph_reference_template, contract)
+
+ ops = []
+ mso.previous = mso.existing
+
+ if state == "absent":
+ mso.existing = {}
+ ops.append(dict(op="remove", path=site_contract_service_graph_path))
+ elif state == "present":
+ service_graph_ref = dict(schemaId=service_graph_reference_schema_id, serviceGraphName=service_graph, templateName=service_graph_reference_template)
+ service_node_relationship = []
+ if mso.site_type == "on-premise" and node_relationship:
+ for node_index, node in enumerate(node_relationship, 1):
+ service_node_ref = dict(
+ schemaId=service_graph_reference_schema_id,
+ serviceGraphName=service_graph,
+ serviceNodeName="node{0}".format(node_index),
+ templateName=service_graph_reference_template,
+ )
+
+ consumer_subnet_ips_list = [dict(ip=subnet) for subnet in node.get("consumer_subnet_ips")] if node.get("consumer_subnet_ips") else []
+ consumer_connector = dict(
+ clusterInterface=dict(
+ dn="uni/tn-{0}/lDevVip-{1}/lIf-{2}".format(
+ tenant, node.get("cluster_interface_device"), node.get("consumer_connector_cluster_interface")
+ )
+ ),
+ redirectPolicy=dict(
+ dn="uni/tn-{0}/svcCont/svcRedirectPol-{1}".format(
+ node.get("consumer_connector_redirect_policy_tenant") or tenant, node.get("consumer_connector_redirect_policy")
+ )
+ ),
+ subnets=consumer_subnet_ips_list,
+ )
+ provider_connector = dict(
+ clusterInterface=dict(
+ dn="uni/tn-{0}/lDevVip-{1}/lIf-{2}".format(
+ tenant, node.get("cluster_interface_device"), node.get("provider_connector_cluster_interface")
+ )
+ ),
+ redirectPolicy=dict(
+ dn="uni/tn-{0}/svcCont/svcRedirectPol-{1}".format(
+ node.get("provider_connector_redirect_policy_tenant") or tenant, node.get("provider_connector_redirect_policy")
+ )
+ ),
+ )
+ service_node_relationship.append(
+ dict(consumerConnector=consumer_connector, providerConnector=provider_connector, serviceNodeRef=service_node_ref)
+ )
+
+ payload = dict(serviceGraphRef=service_graph_ref, serviceNodesRelationship=service_node_relationship)
+ elif mso.cloud_provider_type == "azure":
+ mso_schema.set_site_service_graph(service_graph)
+ service_nodes = mso_schema.schema_objects["site_service_graph"].details.get("serviceNodes", [])
+ payload = dict(
+ serviceGraphRef=service_graph_ref, serviceNodesRelationship=[dict(serviceNodeRef=sg_node.get("serviceNodeRef")) for sg_node in service_nodes]
+ )
+
+ mso.sanitize(payload, collate=True)
+
+ if mso.existing:
+ ops.append(dict(op="replace", path=site_contract_service_graph_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=site_contract_service_graph_path, value=mso.sent))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(mso_schema.path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_contract_service_graph_listener.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_contract_service_graph_listener.py
new file mode 100644
index 000000000..b854f0404
--- /dev/null
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_contract_service_graph_listener.py
@@ -0,0 +1,739 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2024, Sabari Jaganathan (@sajagana) <sajagana@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
+
+
+DOCUMENTATION = r"""
+---
+module: mso_schema_site_contract_service_graph_listener
+short_description: Manage the listener for Azure site contract service graph in schema sites
+description:
+- Manage the listener for Azure site contract service graph in schema sites on Cisco ACI Multi-Site.
+- This module is only compatible with NDO versions 3.7 and 4.2+. NDO versions 4.0 and 4.1 are not supported.
+author:
+- Sabari Jaganathan (@sajagana)
+options:
+ tenant:
+ description:
+ - The name of the tenant.
+ type: str
+ device:
+ description:
+ - The name of the device.
+ type: str
+ aliases: [ device_name ]
+ schema:
+ description:
+ - The name of the schema.
+ type: str
+ required: true
+ template:
+ description:
+ - The name of the template.
+ type: str
+ required: true
+ contract:
+ description:
+ - The name of the contract.
+ type: str
+ required: true
+ site:
+ description:
+ - The name of the site.
+ type: str
+ required: true
+ service_node_index:
+ description:
+ - The index of the service node in the site contract service graph. The value starts from 0.
+ type: int
+ listener:
+ description:
+ - The name of the listener.
+ type: str
+ aliases: [ name, listener_name ]
+ listener_protocol:
+ description:
+ - The protocol of the listener.
+ type: str
+ choices: [ http, https, tcp, udp, tls, inherit ]
+ listener_port:
+ description:
+ - The port of the listener.
+ type: int
+ security_policy:
+ description:
+ - The security policy of the listener.
+ type: str
+ choices: [
+ default,
+ elb_sec_2016_18,
+ elb_sec_fs_2018_06,
+ elb_sec_tls_1_2_2017_01,
+ elb_sec_tls_1_2_ext_2018_06,
+ elb_sec_tls_1_1_2017_01,
+ elb_sec_2015_05,
+ elb_sec_tls_1_0_2015_04,
+ app_gw_ssl_default,
+ app_gw_ssl_2015_501,
+ app_gw_ssl_2017_401,
+ app_gw_ssl_2017_401s
+ ]
+ ssl_certificates:
+ description:
+ - The ssl certificates of the listener.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The name of the ssl certificate.
+ type: str
+ required: true
+ certificate_store:
+ description:
+ - The certificate store of the ssl certificate.
+ type: str
+ required: true
+ choices: [ default, iam, acm ]
+ frontend_ip:
+ description:
+ - The frontend ip of the listener. Only supported for Network load balancers.
+ type: str
+ rules:
+ description:
+ - The rules of the listener.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The name of the rule.
+ type: str
+ required: true
+ floating_ip:
+ description:
+ - The floating ip of the rule.
+ type: str
+ priority:
+ description:
+ - The priority of the rule.
+ type: int
+ required: true
+ host:
+ description:
+ - The host of the rule.
+ type: str
+ path:
+ description:
+ - The path of the rule.
+ type: str
+ action:
+ description:
+ - The action of the rule.
+ type: str
+ action_type:
+ description:
+ - The action type of the rule.
+ type: str
+ required: true
+ choices: [ fixed_response, forward, redirect, ha_port ]
+ content_type:
+ description:
+ - The content type of the rule.
+ type: str
+ choices: [ text_plain, text_css, text_html, app_js, app_json ]
+ port:
+ description:
+ - The port of the rule.
+ type: int
+ protocol:
+ description:
+ - The protocol of the rule.
+ type: str
+ choices: [ http, https, tcp, udp, tls, inherit ]
+ provider_epg:
+ description:
+ - The provider epg of the rule.
+ type: dict
+ suboptions:
+ schema:
+ description:
+ - The schema name of the provider epg reference.
+ type: str
+ template:
+ description:
+ - The template name of the provider epg reference.
+ type: str
+ anp_name:
+ description:
+ - The application profile name of the provider epg reference.
+ type: str
+ required: true
+ aliases: [ anp ]
+ epg_name:
+ description:
+ - The epg (Endpoint Group) name of the provider epg reference.
+ type: str
+ required: true
+ aliases: [ epg ]
+ url_type:
+ description:
+ - The url type of the rule.
+ type: str
+ choices: [ original, custom ]
+ custom_url:
+ description:
+ - The custom url of the rule.
+ type: str
+ redirect_host_name:
+ description:
+ - The redirect host name of the rule.
+ type: str
+ redirect_path:
+ description:
+ - The redirect path of the rule.
+ type: str
+ redirect_query:
+ description:
+ - The redirect query of the rule.
+ type: str
+ response_code:
+ description:
+ - The response code of the rule.
+ type: str
+ response_body:
+ description:
+ - The response body of the rule.
+ type: str
+ redirect_protocol:
+ description:
+ - The redirect protocol of the rule.
+ type: str
+ choices: [ http, https, tcp, udp, tls, inherit ]
+ redirect_port:
+ description:
+ - The redirect port of the rule.
+ type: int
+ redirect_code:
+ description:
+ - The redirect code of the rule.
+ type: str
+ choices: [ unknown, permanently_moved, found, see_other, temporary_redirect ]
+ health_check:
+ description:
+ - The health check of the rule.
+ type: dict
+ suboptions:
+ port:
+ description:
+ - The port of the health check.
+ type: int
+ protocol:
+ description:
+ - The protocol of the health check.
+ type: str
+ choices: [ http, https, tcp, udp, tls, inherit ]
+ path:
+ description:
+ - The path of the health check.
+ type: str
+ interval:
+ description:
+ - The interval of the health check.
+ type: int
+ timeout:
+ description:
+ - The timeout of the health check.
+ type: int
+ unhealthy_threshold:
+ description:
+ - The unhealthy threshold of the health check.
+ type: int
+ use_host_from_rule:
+ description:
+ - The use host from rule of the health check.
+ type: bool
+ host:
+ description:
+ - The host of the health check. The host attribute will be enabled when the I(use_host_from_rule) is false.
+ type: str
+ success_code:
+ description:
+ - The success code of the health check.
+ type: str
+ target_ip_type:
+ description:
+ - The target ip type of the rule.
+ type: str
+ choices: [ unspecified, primary, secondary ]
+ state:
+ description:
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+seealso:
+- module: cisco.mso.mso_schema_template_contract_service_graph
+extends_documentation_fragment: cisco.mso.modules
+"""
+
+
+EXAMPLES = r"""
+- name: Add a listener for Network Load-Balancer
+ cisco.mso.mso_schema_site_contract_service_graph_listener:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ contract: "Contract2"
+ schema: mso_schema
+ template: ansible_template1
+ site: mso_site
+ service_node_index: 0
+ listener: nlb_li_tcp
+ listener_port: 80
+ listener_protocol: tcp
+ tenant: mso_tenant
+ frontend_ip: "10.10.10.10"
+ device: ans_test_nlb
+ security_policy: default
+ rules:
+ - name: rule1
+ priority: 1
+ action_type: forward
+ port: 80
+ protocol: tcp
+ health_check:
+ port: 80
+ protocol: tcp
+ interval: 5
+ unhealthy_threshold: 2
+ success_code: 200-399
+
+- name: Add a listener for Application Load-Balancer
+ cisco.mso.mso_schema_site_contract_service_graph_listener:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ contract: "Contract2"
+ schema: mso_schema
+ template: ansible_template1
+ site: mso_site
+ service_node_index: 1
+ listener: aplb_li_https
+ tenant: mso_tenant
+ device: ans_test_aplb
+ listener_port: 443
+ listener_protocol: https
+ security_policy: default
+ ssl_certificates:
+ - name: ans_test_keyring
+ certificate_store: default
+ rules:
+ - name: rule1
+ priority: 1
+ action_type: forward
+ port: 80
+ protocol: http
+ provider_epg:
+ anp_name: AP1
+ epg_name: EPG1
+ health_check:
+ port: 80
+ protocol: http
+ path: "health_check_path"
+ interval: 30
+ timeout: 30
+ unhealthy_threshold: 3
+ use_host_from_rule: true
+ success_code: "200"
+ target_ip_type: unspecified
+
+- name: Query all listeners
+ cisco.mso.mso_schema_site_contract_service_graph_listener:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ contract: "Contract2"
+ schema: mso_schema
+ template: ansible_template1
+ site: mso_site
+ state: query
+ register: query_all_listeners
+
+- name: Query all listeners with name ans_li_common
+ cisco.mso.mso_schema_site_contract_service_graph_listener:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ contract: "Contract2"
+ schema: mso_schema
+ template: ansible_template1
+ site: mso_site
+ listener: ans_li_common
+ state: query
+ register: query_all_ans_li_common
+
+- name: Query a listener with name - aplb_li_https
+ cisco.mso.mso_schema_site_contract_service_graph_listener:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ contract: "Contract2"
+ schema: mso_schema
+ template: ansible_template1
+ site: mso_site
+ service_node_index: 1
+ listener: aplb_li_https
+ state: query
+ register: query_aplb_li_https
+
+- name: Remove an existing listener - ans_li_common
+ cisco.mso.mso_schema_site_contract_service_graph_listener:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ contract: "Contract2"
+ schema: mso_schema
+ template: ansible_template1
+ site: mso_site
+ service_node_index: 1
+ listener: aplb_li_https
+ state: absent
+"""
+
+RETURN = r"""
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema
+from ansible_collections.cisco.mso.plugins.module_utils.mso import (
+ MSOModule,
+ mso_argument_spec,
+ listener_ssl_certificates_spec,
+ listener_rules_spec,
+)
+from ansible_collections.cisco.mso.plugins.module_utils.constants import (
+ LISTENER_REDIRECT_CODE_MAP,
+ LISTENER_CONTENT_TYPE_MAP,
+ LISTENER_ACTION_TYPE_MAP,
+ LISTENER_SECURITY_POLICY_MAP,
+ LISTENER_PROTOCOLS,
+ YES_OR_NO_TO_BOOL_STRING_MAP,
+)
+
+
+def main():
+ argument_spec = mso_argument_spec()
+ argument_spec.update(
+ tenant=dict(type="str"),
+ device=dict(type="str", aliases=["device_name"]),
+ schema=dict(type="str", required=True),
+ template=dict(type="str", required=True),
+ contract=dict(type="str", required=True),
+ site=dict(type="str", required=True),
+ service_node_index=dict(type="int"),
+ listener=dict(type="str", aliases=["name", "listener_name"]),
+ listener_protocol=dict(type="str", choices=LISTENER_PROTOCOLS),
+ listener_port=dict(type="int"),
+ security_policy=dict(type="str", choices=list(LISTENER_SECURITY_POLICY_MAP)),
+ ssl_certificates=dict(type="list", elements="dict", options=listener_ssl_certificates_spec()),
+ frontend_ip=dict(type="str"),
+ rules=dict(type="list", elements="dict", options=listener_rules_spec()),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["listener"]],
+ ["state", "present", ["listener", "listener_protocol", "listener_port", "rules"]],
+ ],
+ )
+
+ mso = MSOModule(module)
+ site = module.params.get("site")
+
+ # Get site id
+ site_id = mso.lookup_site(site)
+
+ if mso.site_type == "on-premise" or mso.cloud_provider_type != "azure":
+ mso.fail_json(msg="The Site Contract Service Graph Listener is not supported for the site: {0}.".format(site))
+
+ schema = module.params.get("schema")
+ template = module.params.get("template")
+ contract = module.params.get("contract")
+ service_node_index = module.params.get("service_node_index")
+ listener = module.params.get("listener")
+ tenant = module.params.get("tenant")
+ device = module.params.get("device")
+ listener_protocol = module.params.get("listener_protocol")
+ listener_port = module.params.get("listener_port")
+ security_policy = LISTENER_SECURITY_POLICY_MAP.get(module.params.get("security_policy"))
+ ssl_certificates = module.params.get("ssl_certificates")
+ frontend_ip = module.params.get("frontend_ip")
+ rules = module.params.get("rules")
+ state = module.params.get("state")
+
+ if listener_protocol == "https":
+ mso.input_validation("listener_protocol", "https", ["security_policy", "ssl_certificates"], module.params, None, object_name=listener)
+
+ mso_schema = MSOSchema(mso, schema, template)
+ mso_schema.set_template(template)
+ schema_id = mso.lookup_schema(schema, True)
+ mso_schema.set_site(template, site)
+ mso_schema.set_site_contract(contract, False)
+
+ service_graph_ref = mso_schema.schema_objects["site_contract"].details.get("serviceGraphRelationship", {}).get("serviceGraphRef")
+ service_nodes = mso_schema.schema_objects["site_contract"].details.get("serviceGraphRelationship", {}).get("serviceNodesRelationship", [])
+
+ parent_present = False
+
+ if service_graph_ref is None:
+ mso.fail_json(msg="The site contract: {0} is not associated with a service graph.".format(contract))
+ # Parent object present check
+ else:
+ if service_node_index is not None and service_node_index >= 0:
+ if len(service_nodes) > 0 and len(service_nodes) > service_node_index:
+ service_node = service_nodes[service_node_index]
+ # Query all listeners under a contract service node
+ # The below condition was never false if the service graph was created properly, but condition is required to avoid error
+ if service_node.get("serviceNodeRef") and service_node.get("serviceNodeRef").split("/")[-1] == "node{0}".format(service_node_index + 1):
+ listeners = service_node.get("deviceConfiguration", {}).get("cloudLoadBalancer", {}).get("listeners", [])
+ if listeners:
+ parent_present = True
+ if listener:
+ for listener_data in listeners:
+ if listener_data.get("name") == listener:
+ mso.existing = listener_data
+ break
+ else:
+ mso.existing = listeners
+ else:
+ mso.fail_json(
+ msg="The service_node_index: {0} is not matching with the service node reference: {1}.".format(
+ service_node_index, service_node.get("serviceNodeRef")
+ )
+ )
+ else:
+ mso.fail_json(msg="The service_node_index: {0} is out of range.".format(service_node_index))
+
+ # Query all listeners under a contract does not require service_node_index, so the below condition is required
+ elif state == "query":
+ # Query all listeners under a contract
+ for service_node in service_nodes:
+ listeners = service_node.get("deviceConfiguration", {}).get("cloudLoadBalancer", {}).get("listeners", [])
+ if listener:
+ for listener_data in listeners:
+ # Query a listener under a contract service node
+ if listener_data.get("name") == listener:
+ mso.existing = ([listener_data] + mso.existing) if mso.existing else [listener_data]
+ break
+ else:
+ mso.existing = (listeners + mso.existing) if mso.existing else listeners
+
+ else:
+ mso.fail_json(msg="The service_node_index: {0} is not valid.".format(service_node_index))
+
+ if state == "query":
+ mso.exit_json()
+
+ ops = []
+ mso.previous = mso.existing
+
+ parent_object = {}
+
+ if state == "present":
+ # Parent object creation logic begins
+ if device is None and parent_present is False:
+ mso.fail_json(msg="The 'device' name is required to initialize the parent object.")
+
+ elif device is not None and parent_present is False:
+ query_device_data = mso.lookup_service_node_device(site_id, tenant, device_name=device)
+
+ if query_device_data.get("deviceVendorType") == "NATIVELB" and (
+ query_device_data.get("devType") == "application" or query_device_data.get("devType") == "network"
+ ):
+ mso_schema.set_site_service_graph(service_graph_ref.split("/")[-1])
+
+ for sg in mso_schema.schema_objects["site_service_graph"].details.get("serviceNodes", []):
+ if device == sg.get("device").get("dn").split("/")[-1].split("-")[-1]:
+ parent_object = dict(deviceConfiguration=dict(cloudLoadBalancer=dict(listeners=[])), serviceNodeRef=sg.get("serviceNodeRef"))
+ break
+ else:
+ mso.fail_json(
+ msg="Listener is not supported for the 'service_node_index': {0} is associated with the Third-Party {1} device.".format(
+ service_node_index, "Load Balancer" if query_device_data.get("deviceVendorType") == "ADC" else "Firewall"
+ )
+ )
+ # Parent object creation logic ends
+
+ # Listener object creation logic begins
+ listener_object = dict(
+ name=listener,
+ protocol=listener_protocol,
+ port=listener_port,
+ secPolicy=security_policy,
+ )
+
+ if frontend_ip:
+ mso.input_validation("frontend_ip", frontend_ip, ["tenant", "device"], module.params, None, object_name=listener)
+ listener_object["nlbDevIp"] = dict(name=frontend_ip, dn="uni/tn-{0}/clb-{1}/vip-{2}".format(tenant, device, frontend_ip))
+
+ if ssl_certificates:
+ listener_object["certificates"] = [
+ {
+ "name": ssl_certificate.get("name"),
+ "tDn": "uni/tn-{0}/certstore".format(tenant),
+ "default": True,
+ "store": ssl_certificate.get("certificate_store"),
+ }
+ for ssl_certificate in ssl_certificates
+ ]
+
+ # Rules object creation logic
+ rules_data = []
+
+ for position, rule in enumerate(rules, 0):
+ if (listener_protocol == "http" and rule.get("protocol") == "http") or (listener_protocol == "https" and rule.get("protocol") == "https"):
+ mso.fail_json(msg="When the 'listener_protocol' is '{0}', the rule 'protocol' must be '{1}'".format(listener_protocol, rule.get("protocol")))
+
+ if rule.get("action_type") == "redirect":
+ mso.input_validation(
+ "action_type", "redirect", ["redirect_protocol", "redirect_port", "url_type", "redirect_code"], rule, position, rule.get("name")
+ )
+ elif rule.get("action_type") == "forward":
+ mso.input_validation("action_type", "forward", ["protocol", "port", "health_check"], rule, position, rule.get("name"))
+
+ if rule.get("url_type") == "custom":
+ mso.input_validation(
+ "url_type", "custom", ["redirect_host_name", "redirect_path", "redirect_query", "response_code"], rule, position, rule.get("name")
+ )
+
+ rule_data = dict(
+ name=rule.get("name"),
+ floatingIp=rule.get("floating_ip"),
+ index=rule.get("priority"),
+ host=rule.get("host"),
+ path=rule.get("path"),
+ action=rule.get("action"),
+ actionType=LISTENER_ACTION_TYPE_MAP.get(rule.get("action_type")),
+ contentType=LISTENER_CONTENT_TYPE_MAP.get(rule.get("content_type")),
+ port=rule.get("port"),
+ protocol=rule.get("protocol"),
+ urlType=rule.get("url_type"),
+ customURL=rule.get("custom_url"),
+ redirectHostName=rule.get("redirect_host_name"),
+ redirectPath=rule.get("redirect_path"),
+ redirectQuery=rule.get("redirect_query"),
+ responseCode=rule.get("response_code"),
+ responseBody=rule.get("response_body"),
+ redirectProtocol=rule.get("redirect_protocol"),
+ redirectPort=rule.get("redirect_port"),
+ redirectCode=LISTENER_REDIRECT_CODE_MAP.get(rule.get("redirect_code")),
+ targetIpType=rule.get("target_ip_type"),
+ )
+
+ if listener_protocol in ["tcp", "udp"]:
+ mso.input_validation("listener_protocol", "tcp/udp", ["health_check"], rule)
+
+ provider_epg = rule.get("provider_epg")
+ if provider_epg:
+ rule_data["providerEpgRef"] = "/schemas/{0}/templates/{1}/anps/{2}/epgs/{3}".format(
+ provider_epg.get("schema") or schema_id,
+ provider_epg.get("template_name") or template,
+ provider_epg.get("anp_name"),
+ provider_epg.get("epg_name"),
+ )
+
+ health_check = rule.get("health_check")
+ if health_check:
+ if listener_protocol in ["tcp", "udp"]:
+ if health_check.get("protocol") == "tcp":
+ mso.input_validation("health_check - 'protocol'", "tcp", ["port", "unhealthy_threshold", "interval"], health_check)
+ elif health_check.get("protocol") in ["http", "https"]:
+ mso.input_validation("health_check - 'protocol'", "http/https", ["port", "path", "unhealthy_threshold", "interval"], health_check)
+ elif (listener_protocol == "http" and health_check.get("protocol") == "https") or (
+ listener_protocol == "https" and health_check.get("protocol") == "http"
+ ):
+ mso.input_validation(
+ "health_check - 'protocol'", "http/https", ["port", "path", "unhealthy_threshold", "timeout", "interval"], health_check
+ )
+ else:
+ mso.fail_json(
+ msg=(
+ "The 'listener_protocol': {0} and the health_check protocol: {1} "
+ + "is not a valid configuration at the object position: {2} and the object name: {3}"
+ ).format(listener_protocol, health_check.get("protocol"), position, rule.get("name"))
+ )
+
+ health_check_data = dict(
+ port=health_check.get("port"),
+ protocol=health_check.get("protocol"),
+ path=health_check.get("path"),
+ interval=health_check.get("interval"),
+ timeout=health_check.get("timeout"),
+ unhealthyThreshold=health_check.get("unhealthy_threshold"),
+ successCode=health_check.get("success_code"),
+ useHostFromRule=YES_OR_NO_TO_BOOL_STRING_MAP.get(health_check.get("use_host_from_rule")),
+ host=health_check.get("host"),
+ )
+
+ rule_data["healthCheck"] = health_check_data
+
+ rules_data.append(rule_data)
+
+ listener_object["rules"] = rules_data
+
+ if parent_present:
+ # Update an existing listener
+ if mso.existing:
+ listener_path = (
+ "/sites/{0}-{1}/contracts/{2}/serviceGraphRelationship/serviceNodesRelationship/{3}/deviceConfiguration/cloudLoadBalancer/listeners/{4}"
+ ).format(site_id, template, contract, service_node_index, listener)
+ op = "replace"
+ else:
+ # Create a new listener
+ listener_path = (
+ "/sites/{0}-{1}/contracts/{2}/serviceGraphRelationship/serviceNodesRelationship/{3}/deviceConfiguration/cloudLoadBalancer/listeners/-"
+ ).format(site_id, template, contract, service_node_index)
+ op = "add"
+ parent_object = listener_object
+ else:
+ # Create a new listener with parent object
+ listener_path = "/sites/{0}-{1}/contracts/{2}/serviceGraphRelationship/serviceNodesRelationship/{3}".format(
+ site_id, template, contract, service_node_index
+ )
+ op = "replace"
+ parent_object["deviceConfiguration"]["cloudLoadBalancer"]["listeners"].append(listener_object)
+
+ mso.sanitize(parent_object, collate=True)
+ ops.append(dict(op=op, path=listener_path, value=mso.sent))
+
+ elif state == "absent":
+ if mso.existing:
+ listener_path = (
+ "/sites/{0}-{1}/contracts/{2}/serviceGraphRelationship/serviceNodesRelationship/{3}/deviceConfiguration/cloudLoadBalancer/listeners/{4}"
+ ).format(site_id, template, contract, service_node_index, listener)
+ ops.append(dict(op="remove", path=listener_path))
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode:
+ mso.request(mso_schema.path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py
index dff4cf152..ee48df378 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg.py
@@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2021, 2023, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
@@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported
DOCUMENTATION = r"""
---
module: mso_schema_site_external_epg
-short_description: Manage External EPG in schema of sites
+short_description: Manage External EPG in schema of sites.
description:
- Manage External EPG in schema of sites on Cisco ACI Multi-Site.
- This module can only be used on versions of MSO that are 3.3 or greater.
@@ -34,6 +34,19 @@ options:
description:
- The L3Out associated with the external epg.
- Required when site is of type on-premise.
+ - In NDO versions over 4.2, the parameter is accessible only when an external EPG is
+ - linked to the current schema-template's VRF.
+ type: str
+ aliases: [ l3out_name ]
+ l3out_schema:
+ description:
+ - The schema that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ l3out_template:
+ description:
+ - The template that defines the referenced L3Out.
+ - If this parameter is unspecified, it defaults to the current template.
type: str
external_epg:
description:
@@ -75,7 +88,6 @@ EXAMPLES = r"""
external_epg: External EPG 1
l3out: L3out1
state: present
- delegate_to: localhost
- name: Remove a Site External EPG
cisco.mso.mso_schema_site_external_epg:
@@ -87,7 +99,6 @@ EXAMPLES = r"""
external_epg: External EPG 1
l3out: L3out1
state: absent
- delegate_to: localhost
- name: Query a Site External EPG
cisco.mso.mso_schema_site_external_epg:
@@ -99,7 +110,7 @@ EXAMPLES = r"""
external_epg: External EPG 1
l3out: L3out1
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all Site External EPGs
cisco.mso.mso_schema_site_external_epg:
@@ -109,7 +120,7 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
@@ -126,7 +137,9 @@ def main():
schema=dict(type="str", required=True),
template=dict(type="str", required=True),
site=dict(type="str", required=True),
- l3out=dict(type="str"),
+ l3out=dict(type="str", aliases=["l3out_name"]),
+ l3out_schema=dict(type="str"),
+ l3out_template=dict(type="str"),
external_epg=dict(type="str", aliases=["name"]),
route_reachability=dict(type="str", default="internet", choices=["internet", "site-ext"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
@@ -142,15 +155,21 @@ def main():
)
schema = module.params.get("schema")
- template = module.params.get("template")
+ template = module.params.get("template").replace(" ", "")
site = module.params.get("site")
external_epg = module.params.get("external_epg")
l3out = module.params.get("l3out")
+ l3out_schema = module.params.get("l3out_schema")
+ l3out_template = module.params.get("l3out_template")
route_reachability = module.params.get("route_reachability")
state = module.params.get("state")
mso = MSOModule(module)
+ l3out_template = template if l3out_template is None else l3out_template.replace(" ", "")
+ l3out_schema = schema if l3out_schema is None else l3out_schema
+ l3out_schema_id = mso.lookup_schema(l3out_schema)
+
mso_schema = MSOSchema(mso, schema, template, site)
mso_objects = mso_schema.schema_objects
@@ -175,7 +194,6 @@ def main():
ops = []
l3out_dn = ""
-
if state == "query":
if external_epg is None:
mso.existing = mso_objects.get("site").details.get("externalEpgs")
@@ -208,8 +226,8 @@ def main():
),
l3outDn=l3out_dn,
l3outRef=dict(
- schemaId=mso_schema.id,
- templateName=template,
+ schemaId=l3out_schema_id,
+ templateName=l3out_template,
l3outName=l3out,
),
routeReachabilityInternetType=route_reachability,
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py
index 001ebf2b5..e4081cebe 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_external_epg_selector.py
@@ -94,7 +94,6 @@ EXAMPLES = r"""
operator: equals
value: 10.0.0.0
state: present
- delegate_to: localhost
- name: Remove a Selector
cisco.mso.mso_schema_site_external_epg_selector:
@@ -107,7 +106,6 @@ EXAMPLES = r"""
external_epg: ext1
selector: test
state: absent
- delegate_to: localhost
- name: Query a specific Selector
cisco.mso.mso_schema_site_external_epg_selector:
@@ -120,7 +118,6 @@ EXAMPLES = r"""
external_epg: ext1
selector: selector_1
state: query
- delegate_to: localhost
register: query_result
- name: Query all Selectors
@@ -133,7 +130,6 @@ EXAMPLES = r"""
site: azure_ansible_test
external_epg: ext1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py
index 4741e4bd7..4293d7253 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_l3out.py
@@ -89,7 +89,6 @@ EXAMPLES = r"""
template: TemplateName
schema: schemaName
state: present
- delegate_to: localhost
- name: Remove a site L3Out
cisco.mso.mso_schema_site_l3out:
@@ -101,7 +100,6 @@ EXAMPLES = r"""
template: Template1
l3out: L3out1
state: absent
- delegate_to: localhost
- name: Query a specific site L3Out
cisco.mso.mso_schema_site_l3out:
@@ -113,7 +111,6 @@ EXAMPLES = r"""
template: Template1
l3out: L3out1
state: query
- delegate_to: localhost
register: query_result
- name: Query all site l3outs
@@ -125,7 +122,6 @@ EXAMPLES = r"""
site: Site1
template: Template1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py
index 9495a501d..29919152d 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_service_graph.py
@@ -50,11 +50,32 @@ options:
type: list
elements: dict
suboptions:
- name:
+ device_name:
description:
- The name of the device
required: true
type: str
+ aliases: [ name ]
+ provider_interface:
+ description:
+ - The name of the provider interface for the Azure CNC L4-L7 device.
+ type: str
+ provider_connector_type:
+ description:
+ - The provider connector type for the Azure CNC site service graph.
+ - Defaults to C(none) when unset during creation.
+ type: str
+ choices: [ none, redirect, source_nat, destination_nat, source_and_destination_nat ]
+ consumer_interface:
+ description:
+ - The name of the consumer interface for the Azure CNC L4-L7 device.
+ type: str
+ consumer_connector_type:
+ description:
+ - The consumer connector type for the Azure CNC site service graph.
+ - Defaults to C(none) when unset during creation.
+ type: str
+ choices: [ none, redirect ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -81,7 +102,28 @@ EXAMPLES = r"""
- name: ansible_test_adc
- name: ansible_test_other
state: present
- delegate_to: localhost
+
+- name: Add a Site service graph for the Azure cloud CNC
+ cisco.mso.mso_schema_site_service_graph:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema1
+ template: Template1
+ service_graph: SG1
+ site: site1
+ tenant: tenant1
+ devices:
+ - name: ans_tnt_firewall1
+ provider_connector_type: source_nat
+ provider_interface: TP_FW_Inf1
+ consumer_connector_type: redirect
+ consumer_interface: TP_FW_Inf1
+ - name: ans_tnt_app_lb
+ - name: ans_tnt_other
+ provider_connector_type: destination_nat
+ consumer_connector_type: redirect
+ state: present
- name: Remove a Service Graph
cisco.mso.mso_schema_site_service_graph_node:
@@ -93,7 +135,6 @@ EXAMPLES = r"""
service_graph: SG1
site: site1
state: absent
- delegate_to: localhost
- name: Query a specific Service Graph
cisco.mso.mso_schema_site_service_graph_node:
@@ -105,7 +146,7 @@ EXAMPLES = r"""
service_graph: SG1
site: site1
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all Service Graphs
cisco.mso.mso_schema_site_service_graph_node:
@@ -116,14 +157,21 @@ EXAMPLES = r"""
template: Template1
site: site1
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
"""
+
from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_service_graph_node_device_spec
+from ansible_collections.cisco.mso.plugins.module_utils.mso import (
+ MSOModule,
+ mso_argument_spec,
+ mso_service_graph_node_device_spec,
+ service_node_ref_str_to_dict,
+)
+from ansible_collections.cisco.mso.plugins.module_utils.constants import AZURE_L4L7_CONNECTOR_TYPE_MAP
def main():
@@ -206,17 +254,25 @@ def main():
ops = []
mso.previous = mso.existing
+ if mso.previous.get("serviceNodes") is not None and len(mso.previous.get("serviceNodes")) > 0:
+ for node in mso.previous.get("serviceNodes"):
+ node["serviceNodeRef"] = service_node_ref_str_to_dict(node.get("serviceNodeRef"))
+
+ devices_payload = []
+
if state == "absent":
if mso.existing:
mso.sent = mso.existing = {}
ops.append(dict(op="remove", path=service_graph_path))
elif state == "present":
- devices_payload = []
service_graphs = templates[template_idx]["serviceGraphs"]
+ service_node_types_from_template = []
for graph in service_graphs:
if graph.get("name") == service_graph:
service_node_types_from_template = graph["serviceNodes"]
+ break
+
user_number_devices = len(devices)
number_of_nodes_in_template = len(service_node_types_from_template)
if user_number_devices != number_of_nodes_in_template:
@@ -227,29 +283,77 @@ def main():
)
if devices is not None:
- service_node_type_names_from_template = [type.get("name") for type in service_node_types_from_template]
+ query_device_data = mso.lookup_service_node_device(site_id, tenant, device_name=None, service_node_type=None)
for index, device in enumerate(devices):
- template_node_type = service_node_type_names_from_template[index]
- apic_type = "OTHERS"
- if template_node_type == "firewall":
- apic_type = "FW"
- elif template_node_type == "load-balancer":
- apic_type = "ADC"
- query_device_data = mso.lookup_service_node_device(site_id, tenant, device.get("name"), apic_type)
- devices_payload.append(
- dict(
- device=dict(
- dn=query_device_data.get("dn"),
- funcTyp=query_device_data.get("funcType"),
- ),
- serviceNodeRef=dict(
- serviceNodeName=template_node_type,
- serviceGraphName=service_graph,
- templateName=template,
- schemaId=schema_id,
- ),
- ),
- )
+ if query_device_data:
+ for device_data in query_device_data:
+ if device.get("device_name") == device_data.get("dn").split("/")[-1].split("-")[-1]:
+ device_payload = dict()
+ device_payload["device"] = dict(
+ dn=device_data.get("dn"),
+ funcType=device_data.get("funcType"),
+ )
+ device_payload["serviceNodeRef"] = dict(
+ serviceNodeName="node{0}".format(index + 1),
+ serviceGraphName=service_graph,
+ templateName=template,
+ schemaId=schema_id,
+ )
+
+ if mso.cloud_provider_type == "azure":
+ consumer_interface = device.get("consumer_interface")
+ provider_interface = device.get("provider_interface")
+ provider_connector_type = device.get("provider_connector_type")
+ consumer_connector_type = device.get("consumer_connector_type")
+
+ if (
+ device_data.get("deviceVendorType") == "NATIVELB"
+ and device_data.get("devType") == "application"
+ and (
+ consumer_interface is not None
+ or provider_interface is not None
+ or provider_connector_type is not None
+ or consumer_connector_type is not None
+ )
+ ):
+ # Application Load Balancer - Consumer Interface, Consumer Connector Type,
+ # Provider Interface, Provider Connector Type - not supported
+ mso.fail_json(
+ msg="Unsupported attributes: provider_connector_type, provider_interface, "
+ + "consumer_connector_type, consumer_interface should be 'None' for the "
+ + "Application Load Balancer device."
+ )
+ elif (
+ device_data.get("deviceVendorType") == "NATIVELB"
+ and device_data.get("devType") == "network"
+ and (consumer_interface is not None or provider_interface is not None)
+ ):
+ # Network Load Balancer - Consumer Interface, Provider Interface - not supported
+ mso.fail_json(
+ msg="Unsupported attributes: provider_interface and consumer_interface should "
+ + "be 'None' for the Network Load Balancer device."
+ )
+ elif (
+ device_data.get("deviceVendorType") == "ADC"
+ and device_data.get("devType") == "CLOUD"
+ and (provider_connector_type is not None or consumer_connector_type is not None)
+ ):
+ # Third-Party Load Balancer - Consumer Connector Type,
+ # Provider Connector Type - not supported
+ mso.fail_json(
+ msg="Unsupported attributes: provider_connector_type and "
+ + "consumer_connector_type should be 'None' for the "
+ + "Third-Party Load Balancer."
+ )
+
+ # (FW) Third-Party Firewall - Consumer Interface, Consumer Connector Type,
+ # Provider Interface, Provider Connector Type - supported
+ device_payload["consumerInterface"] = consumer_interface
+ device_payload["providerInterface"] = provider_interface
+ device_payload["providerConnectorType"] = AZURE_L4L7_CONNECTOR_TYPE_MAP.get(provider_connector_type)
+ device_payload["consumerConnectorType"] = AZURE_L4L7_CONNECTOR_TYPE_MAP.get(consumer_connector_type)
+
+ devices_payload.append(device_payload)
payload = dict(
serviceGraphRef=dict(
@@ -263,6 +367,8 @@ def main():
mso.sanitize(payload, collate=True)
if not mso.existing:
+ # The site service graph reference will be added automatically when the site is associated with the template
+ # So the add(create) part will not be used for the NDO v4.2
ops.append(dict(op="add", path=service_graphs_path, value=payload))
else:
ops.append(dict(op="replace", path=service_graph_path, value=mso.sent))
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py
index a0b864a8b..42de06ab7 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf.py
@@ -63,7 +63,6 @@ EXAMPLES = r"""
template: Template1
vrf: VRF1
state: present
- delegate_to: localhost
- name: Remove a site VRF
cisco.mso.mso_schema_site_vrf:
@@ -75,7 +74,6 @@ EXAMPLES = r"""
template: Template1
vrf: VRF1
state: absent
- delegate_to: localhost
- name: Query a specific site VRF
cisco.mso.mso_schema_site_vrf:
@@ -87,7 +85,6 @@ EXAMPLES = r"""
template: Template1
vrf: VRF1
state: query
- delegate_to: localhost
register: query_result
- name: Query all site VRFs
@@ -99,7 +96,6 @@ EXAMPLES = r"""
site: Site1
template: Template1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py
index 5ac5804e6..6863426ad 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region.py
@@ -101,7 +101,6 @@ EXAMPLES = r"""
region: us-west-1
vpn_gateway_router: false
state: present
- delegate_to: localhost
- name: Remove a site VRF region
cisco.mso.mso_schema_site_vrf_region:
@@ -114,7 +113,6 @@ EXAMPLES = r"""
vrf: VRF1
region: us-west-1
state: absent
- delegate_to: localhost
- name: Query a specific site VRF region
cisco.mso.mso_schema_site_vrf_region:
@@ -127,7 +125,6 @@ EXAMPLES = r"""
vrf: VRF1
region: us-west-1
state: query
- delegate_to: localhost
register: query_result
- name: Query all site VRF regions
@@ -140,7 +137,6 @@ EXAMPLES = r"""
template: Template1
vrf: VRF1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py
index ef409f710..b2a8e4f33 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr.py
@@ -87,7 +87,6 @@ EXAMPLES = r"""
region: us-west-1
cidr: 14.14.14.1/24
state: present
- delegate_to: localhost
- name: Remove a site VRF region CIDR
cisco.mso.mso_schema_site_vrf_region_cidr:
@@ -101,7 +100,6 @@ EXAMPLES = r"""
region: us-west-1
cidr: 14.14.14.1/24
state: absent
- delegate_to: localhost
- name: Query a specific site VRF region CIDR
cisco.mso.mso_schema_site_vrf_region_cidr:
@@ -115,7 +113,6 @@ EXAMPLES = r"""
region: us-west-1
cidr: 14.14.14.1/24
state: query
- delegate_to: localhost
register: query_result
- name: Query all site VRF region CIDR
@@ -129,7 +126,6 @@ EXAMPLES = r"""
vrf: VRF1
region: us-west-1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py
index 85a00ea9c..a8cd42ca5 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_cidr_subnet.py
@@ -113,7 +113,6 @@ EXAMPLES = r"""
subnet: 14.14.14.2/24
zone: us-west-1a
state: present
- delegate_to: localhost
- name: Remove a site VRF region CIDR subnet
cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
@@ -128,7 +127,6 @@ EXAMPLES = r"""
cidr: 14.14.14.1/24
subnet: 14.14.14.2/24
state: absent
- delegate_to: localhost
- name: Query a specific site VRF region CIDR subnet
cisco.mso.mso_schema_site_vrf_region_cidr_subnet:
@@ -143,7 +141,6 @@ EXAMPLES = r"""
cidr: 14.14.14.1/24
subnet: 14.14.14.2/24
state: query
- delegate_to: localhost
register: query_result
- name: Query all site VRF region CIDR subnet
@@ -158,7 +155,6 @@ EXAMPLES = r"""
region: us-west-1
cidr: 14.14.14.1/24
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py
index 9df7bab4f..3d0cf4f3c 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_site_vrf_region_hub_network.py
@@ -93,7 +93,6 @@ EXAMPLES = r"""
name: hub-default
tenant: infra
state: present
- delegate_to: localhost
- name: Remove a site VRF region hub network
cisco.mso.mso_schema_site_vrf_region_hub_network:
@@ -105,7 +104,6 @@ EXAMPLES = r"""
template: Template1
vrf: VRF1
state: absent
- delegate_to: localhost
- name: Query site VRF region hub network
cisco.mso.mso_schema_site_vrf_region_hub_network:
@@ -118,7 +116,6 @@ EXAMPLES = r"""
vrf: VRF1
region: us-west-1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py
index 6f4ece9ca..bd0fa8fef 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template.py
@@ -2,6 +2,8 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com>
+
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
@@ -18,12 +20,12 @@ description:
- Manage templates on Cisco ACI Multi-Site.
author:
- Dag Wieers (@dagwieers)
+- Akini Ross (@akinross)
options:
tenant:
description:
- The tenant used for this template.
type: str
- required: true
schema:
description:
- The name of the schema.
@@ -50,6 +52,7 @@ options:
description:
- Use C(present) or C(absent) for adding or removing.
- Use C(query) for listing an object or multiple objects.
+ - Using C(present) on empty schemas M(cisco.mso.mso_schema) is supported on versions of MSO that are 4.2 or greater.
type: str
choices: [ absent, present, query ]
default: present
@@ -71,7 +74,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: present
- delegate_to: localhost
- name: Remove a template from a schema
cisco.mso.mso_schema_template:
@@ -82,7 +84,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: absent
- delegate_to: localhost
- name: Query a template
cisco.mso.mso_schema_template:
@@ -93,7 +94,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
- name: Query all templates
@@ -104,7 +104,6 @@ EXAMPLES = r"""
tenant: Tenant 1
schema: Schema 1
state: query
- delegate_to: localhost
register: query_result
"""
@@ -118,7 +117,7 @@ from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, ms
def main():
argument_spec = mso_argument_spec()
argument_spec.update(
- tenant=dict(type="str", required=True),
+ tenant=dict(type="str"),
schema=dict(type="str", required=True),
schema_description=dict(type="str"),
template_description=dict(type="str"),
@@ -132,7 +131,7 @@ def main():
supports_check_mode=True,
required_if=[
["state", "absent", ["template"]],
- ["state", "present", ["template"]],
+ ["state", "present", ["template", "tenant"]],
],
)
@@ -157,13 +156,11 @@ def main():
schema_path = "schemas/{id}".format(**schema_obj)
# Get template
- templates = [t.get("name") for t in schema_obj.get("templates")]
+ templates = schema_obj.get("templates") if schema_obj.get("templates") is not None else []
if template:
- if template in templates:
- template_idx = templates.index(template)
- mso.existing = schema_obj.get("templates")[template_idx]
+ mso.existing = next((item for item in templates if item.get("name") == template), {})
else:
- mso.existing = schema_obj.get("templates")
+ mso.existing = templates
else:
schema_path = "schemas"
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py
index 9922750f8..f37b95237 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp.py
@@ -65,7 +65,6 @@ EXAMPLES = r"""
template: Template 1
anp: ANP 1
state: present
- delegate_to: localhost
- name: Remove an ANP
cisco.mso.mso_schema_template_anp:
@@ -76,7 +75,6 @@ EXAMPLES = r"""
template: Template 1
anp: ANP 1
state: absent
- delegate_to: localhost
- name: Query a specific ANPs
cisco.mso.mso_schema_template_anp:
@@ -86,7 +84,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
- name: Query all ANPs
@@ -97,7 +94,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py
index 6c021e7f7..b5a26f31c 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg.py
@@ -212,7 +212,6 @@ EXAMPLES = r"""
vrf:
name: vrf1
state: present
- delegate_to: localhost
- name: Add a new EPG with preferred group.
cisco.mso.mso_schema_template_anp_epg:
@@ -225,7 +224,6 @@ EXAMPLES = r"""
epg: EPG 1
state: present
preferred_group: true
- delegate_to: localhost
- name: Remove an EPG
cisco.mso.mso_schema_template_anp_epg:
@@ -241,7 +239,6 @@ EXAMPLES = r"""
vrf:
name: vrf1
state: absent
- delegate_to: localhost
- name: Query a specific EPG
cisco.mso.mso_schema_template_anp_epg:
@@ -257,7 +254,6 @@ EXAMPLES = r"""
vrf:
name: vrf1
state: query
- delegate_to: localhost
register: query_result
- name: Query all EPGs
@@ -274,7 +270,6 @@ EXAMPLES = r"""
vrf:
name: vrf1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py
index d8c881d5d..3563e78f9 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_contract.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
+# Copyright: (c) 2024, Akini Ross (@akinross) <akinross@cisco.com>
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
@@ -18,15 +19,16 @@ description:
- Manage EPG contracts in schema templates on Cisco ACI Multi-Site.
author:
- Dag Wieers (@dagwieers)
+- Akini Ross (@akinross)
options:
schema:
description:
- - The name of the schema.
+ - The name of the Schema.
type: str
required: true
template:
description:
- - The name of the template to change.
+ - The name of the Template.
type: str
required: true
anp:
@@ -36,31 +38,70 @@ options:
required: true
epg:
description:
- - The name of the EPG to manage.
+ - The name of the EPG.
type: str
required: true
+ force_replace:
+ description:
+ - Replaces all the configured contract(s) with the provided contract(s).
+ - This option can only be used in combination with the O(contracts) option.
+ - In combination with the O(state=absent) and without any contract configuration all configured static port(s) will be removed.
+ type: bool
contract:
description:
- - A contract associated to this EPG.
+ - The Contract associated to this EPG.
+ - This option can not be used in combination with the I(contracts) option.
type: dict
suboptions:
name:
description:
- - The name of the Contract to associate with.
+ - The name of the Contract.
+ required: true
+ type: str
+ schema:
+ description:
+ - The name of the Schema that defines the referenced Contract.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ template:
+ description:
+ - The name of the Template that defines the referenced Contract.
+ - If this parameter is unspecified, it defaults to the current schema.
+ type: str
+ type:
+ description:
+ - The type of the Contract.
+ type: str
+ required: true
+ choices: [ consumer, provider ]
+ contracts:
+ description:
+ - A list of Contracts associated to this EPG.
+ - This option can not be used in combination with the O(contract) option.
+ - All configured contract(s) will be replaced with the provided contract(s) when used with O(force_replace=true).
+ - Only the provided contract(s) will be added, updated or removed when used with O(force_replace=false).
+ - In combination with the O(state=query) all provided contract(s) must be found else the task will fail.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The name of the Contract.
required: true
type: str
schema:
description:
- - The schema that defines the referenced BD.
+ - The name of the Schema that defines the referenced Contract.
- If this parameter is unspecified, it defaults to the current schema.
type: str
template:
description:
- - The template that defines the referenced BD.
+ - The name of the Template that defines the referenced Contract.
+ - If this parameter is unspecified, it defaults to the current schema.
type: str
type:
description:
- - The type of contract.
+ - The type of the Contract.
type: str
required: true
choices: [ consumer, provider ]
@@ -91,9 +132,8 @@ EXAMPLES = r"""
name: Contract 1
type: consumer
state: present
- delegate_to: localhost
-- name: Remove a Contract
+- name: Add 2 contracts to an EPG
cisco.mso.mso_schema_template_anp_epg_contract:
host: mso_host
username: admin
@@ -102,10 +142,29 @@ EXAMPLES = r"""
template: Template 1
anp: ANP 1
epg: EPG 1
- contract:
- name: Contract 1
- state: absent
- delegate_to: localhost
+ contracts:
+ - name: Contract 1
+ type: provider
+ - name: Contract 1
+ type: consumer
+ state: present
+
+- name: Replace all existing contracts on an EPG with 2 new contracts
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ force_replace: true
+ contracts:
+ - name: Contract 2
+ type: provider
+ - name: Contract 2
+ type: consumer
+ state: present
- name: Query a specific Contract
cisco.mso.mso_schema_template_anp_epg_contract:
@@ -118,8 +177,25 @@ EXAMPLES = r"""
epg: EPG 1
contract:
name: Contract 1
+ type: consumer
+ state: query
+ register: query_result
+
+- name: Query a list of Contracts
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contracts:
+ - name: Contract 2
+ type: provider
+ - name: Contract 2
+ type: consumer
state: query
- delegate_to: localhost
register: query_result
- name: Query all Contracts
@@ -131,8 +207,48 @@ EXAMPLES = r"""
template: Template 1
anp: ANP 1
state: query
- delegate_to: localhost
register: query_result
+
+- name: Remove a Contract
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contract:
+ name: Contract 1
+ state: absent
+
+- name: Remove 2 contracts to an EPG
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ contracts:
+ - name: Contract 1
+ type: provider
+ - name: Contract 1
+ type: consumer
+ state: absent
+
+- name: Remove all existing contracts from an EPG
+ cisco.mso.mso_schema_template_anp_epg_contract:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ template: Template 1
+ anp: ANP 1
+ epg: EPG 1
+ force_replace: true
+ state: absent
"""
RETURN = r"""
@@ -140,6 +256,7 @@ RETURN = r"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, mso_contractref_spec
+from ansible_collections.cisco.mso.plugins.module_utils.schema import MSOSchema
def main():
@@ -149,7 +266,9 @@ def main():
template=dict(type="str", required=True),
anp=dict(type="str", required=True),
epg=dict(type="str", required=True),
+ force_replace=dict(type="bool"),
contract=dict(type="dict", options=mso_contractref_spec()),
+ contracts=dict(type="list", elements="dict", options=mso_contractref_spec()),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
)
@@ -157,8 +276,12 @@ def main():
argument_spec=argument_spec,
supports_check_mode=True,
required_if=[
- ["state", "absent", ["contract"]],
- ["state", "present", ["contract"]],
+ ["state", "absent", ["contract", "contracts", "force_replace"], True],
+ ["state", "present", ["contract", "contracts"], True],
+ ],
+ mutually_exclusive=[
+ ["contract", "contracts"],
+ ["contract", "force_replace"],
],
)
@@ -166,97 +289,160 @@ def main():
template = module.params.get("template").replace(" ", "")
anp = module.params.get("anp")
epg = module.params.get("epg")
+ force_replace = module.params.get("force_replace")
contract = module.params.get("contract")
- if contract is not None and contract.get("template") is not None:
- contract["template"] = contract.get("template").replace(" ", "")
+ contracts = module.params.get("contracts")
state = module.params.get("state")
mso = MSOModule(module)
+ mso_schema = MSOSchema(mso, schema, template)
+ mso_schema.set_template(template)
+ mso_schema.set_template_anp(anp)
+ mso_schema.set_template_anp_epg(epg)
+
+ # Schema dict is used as a cache store for schema id lookups
+ # This is done to limit the amount of schema id lookups when schema is not specified for multiple contracts
+ schema_cache = {mso_schema.schema_name: mso_schema.id}
if contract:
- if contract.get("schema") is None:
- contract["schema"] = schema
- contract["schema_id"] = mso.lookup_schema(contract.get("schema"))
- if contract.get("template") is None:
- contract["template"] = template
-
- # Get schema
- schema_id, schema_path, schema_obj = mso.query_schema(schema)
-
- # Get template
- templates = [t.get("name") for t in schema_obj.get("templates")]
- if template not in templates:
- mso.fail_json(msg="Provided template '{0}' does not exist. Existing templates: {1}".format(template, ", ".join(templates)))
- template_idx = templates.index(template)
-
- # Get ANP
- anps = [a.get("name") for a in schema_obj.get("templates")[template_idx]["anps"]]
- if anp not in anps:
- mso.fail_json(msg="Provided anp '{0}' does not exist. Existing anps: {1}".format(anp, ", ".join(anps)))
- anp_idx = anps.index(anp)
-
- # Get EPG
- epgs = [e.get("name") for e in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"]]
- if epg not in epgs:
- mso.fail_json(msg="Provided epg '{epg}' does not exist. Existing epgs: {epgs}".format(epg=epg, epgs=", ".join(epgs)))
- epg_idx = epgs.index(epg)
-
- # Get Contract
+ overwrite_contract_schema_and_template(mso, contract, schema, schema_cache, template)
+ elif contracts:
+ for contract_dict in contracts:
+ overwrite_contract_schema_and_template(mso, contract_dict, schema, schema_cache, template)
+
+ contracts_path = "/templates/{0}/anps/{1}/epgs/{2}/contractRelationships".format(template, anp, epg)
+ contract_path = "{0}/-".format(contracts_path)
+ ops = []
+
if contract:
- contracts = [
- (c.get("contractRef"), c.get("relationshipType"))
- for c in schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"]
- ]
contract_ref = mso.contract_ref(**contract)
- if (contract_ref, contract.get("type")) in contracts:
- contract_idx = contracts.index((contract_ref, contract.get("type")))
- contract_path = "/templates/{0}/anps/{1}/epgs/{2}/contractRelationships/{3}".format(template, anp, epg, contract_idx)
- mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"][contract_idx]
+ mso_schema.set_template_anp_epg_contract(contract_ref, contract.get("type"), False)
+ if mso_schema.schema_objects.get("template_anp_epg_contract") is not None:
+ mso.existing = get_contract_payload_from_schema(mso, mso_schema)
+ contract_path = "{0}/{1}".format(contracts_path, mso_schema.schema_objects["template_anp_epg_contract"].index)
+ else:
+ found_contracts = []
+ set_existing_contracts(mso, mso_schema)
+ if contracts:
+ for contract_details in contracts:
+ contract_ref = mso.contract_ref(**contract_details)
+ mso_schema.set_template_anp_epg_contract(contract_ref, contract_details.get("type"), False)
+ if mso_schema.schema_objects.get("template_anp_epg_contract") is not None:
+ found_contracts.append(get_contract_payload_from_schema(mso, mso_schema))
if state == "query":
- if not contract:
- mso.existing = schema_obj.get("templates")[template_idx]["anps"][anp_idx]["epgs"][epg_idx]["contractRelationships"]
+ if contracts:
+ if len(found_contracts) == len(contracts):
+ mso.existing = found_contracts
+ else:
+ not_found_contracts = [
+ "Contract with Reference '{0}' and type '{1}' not found".format(mso.contract_ref(**contract), contract.get("type"))
+ for contract in contracts
+ if contract not in found_contracts
+ ]
+ mso.fail_json(msg=not_found_contracts)
elif not mso.existing:
- mso.fail_json(msg="Contract '{0}' not found".format(contract_ref))
-
- if "contractRef" in mso.existing:
- mso.existing["contractRef"] = mso.dict_from_ref(mso.existing.get("contractRef"))
+ mso.fail_json(msg="Contract with Reference '{0}' and type '{1}' not found".format(contract_ref, contract.get("type")))
mso.exit_json()
- contracts_path = "/templates/{0}/anps/{1}/epgs/{2}/contractRelationships".format(template, anp, epg)
- ops = []
-
mso.previous = mso.existing
- if state == "absent":
- if mso.existing:
- mso.sent = mso.existing = {}
+
+ if state == "absent" and mso.existing:
+ if contract:
+ mso.sent = mso.proposed = {}
ops.append(dict(op="remove", path=contract_path))
+ elif force_replace:
+ mso.sent = mso.proposed = []
+ ops.append(dict(op="remove", path=contracts_path))
+ else:
+ mso.proposed = mso.existing.copy()
+ remove_index = []
+ for contract in contracts:
+ payload = get_contract_payload(contract)
+ if any(True if payload == found_contract else False for found_contract in found_contracts):
+ mso.proposed.remove(payload)
+ remove_index.append(mso.existing.index(payload))
+ # The list index should not shift when removing contracts from the list
+ # By sorting the indexes found in reverse order, we assure that the highest index is removed first by the NDO backend
+ # This logic is to avoid removing the wrong contract
+ for index in reversed(sorted(remove_index)):
+ ops.append(dict(op="remove", path="{0}/{1}".format(contracts_path, index)))
+ mso.sent = mso.proposed
elif state == "present":
- payload = dict(
- relationshipType=contract.get("type"),
- contractRef=dict(
- contractName=contract.get("name"),
- templateName=contract.get("template"),
- schemaId=contract.get("schema_id"),
- ),
+ if contract:
+ mso.sanitize(get_contract_payload(contract), collate=True)
+ if not mso.existing:
+ ops.append(dict(op="add", path=contract_path, value=mso.sent))
+ elif force_replace:
+ mso.sent = mso.proposed = [get_contract_payload(contract) for contract in contracts]
+ if mso.existing:
+ ops.append(dict(op="replace", path=contracts_path, value=mso.sent))
+ else:
+ ops.append(dict(op="add", path=contracts_path, value=mso.sent))
+ else:
+ mso.sent = []
+ mso.proposed = mso.existing.copy()
+ for contract in contracts:
+ payload = get_contract_payload(contract)
+ if payload not in mso.existing:
+ mso.proposed.append(payload)
+ # Only add the operation list if the contract is not already present
+ # This is to avoid adding the same contract multiple times
+ # Replace operation is not required because there are no attributes that can be changed except the contract itself
+ if not force_replace and not any(True if payload == found_contract else False for found_contract in found_contracts):
+ ops.append(dict(op="add", path=contract_path, value=payload))
+ mso.sent.append(payload)
+
+ mso.existing = mso.proposed
+
+ if not module.check_mode and mso.proposed != mso.previous:
+ mso.request(mso_schema.path, method="PATCH", data=ops)
+
+ mso.exit_json()
+
+
+def set_existing_contracts(mso, mso_schema):
+ mso.existing = []
+ for existing_contract in mso_schema.schema_objects["template_anp_epg"].details.get("contractRelationships"):
+ mso.existing.append(
+ dict(
+ relationshipType=existing_contract.get("relationshipType"),
+ contractRef=mso.dict_from_ref(existing_contract.get("contractRef")),
+ )
)
- mso.sanitize(payload, collate=True)
- if mso.existing:
- ops.append(dict(op="replace", path=contract_path, value=mso.sent))
- else:
- ops.append(dict(op="add", path=contracts_path + "/-", value=mso.sent))
+def get_contract_payload_from_schema(mso, mso_schema):
+ return dict(
+ relationshipType=mso_schema.schema_objects["template_anp_epg_contract"].details.get("relationshipType"),
+ contractRef=mso.dict_from_ref(mso_schema.schema_objects["template_anp_epg_contract"].details.get("contractRef")),
+ )
- mso.existing = mso.proposed
- if "contractRef" in mso.previous:
- mso.previous["contractRef"] = mso.dict_from_ref(mso.previous.get("contractRef"))
- if not module.check_mode and mso.proposed != mso.previous:
- mso.request(schema_path, method="PATCH", data=ops)
+def get_contract_payload(contract):
+ return dict(
+ relationshipType=contract.get("type"),
+ contractRef=dict(
+ contractName=contract.get("name"),
+ templateName=contract.get("template"),
+ schemaId=contract.get("schema_id"),
+ ),
+ )
- mso.exit_json()
+
+def overwrite_contract_schema_and_template(mso, contract, epg_schema_name, schema_cache, epg_template_name):
+ if contract.get("schema") is None:
+ contract["schema"] = epg_schema_name
+ contract["schema_id"] = schema_cache.get(epg_schema_name)
+ else:
+ schema_id = schema_cache.get(contract.get("schema"))
+ contract["schema_id"] = schema_id if schema_id else mso.lookup_schema(contract.get("schema"))
+
+ if contract.get("template") is None:
+ contract["template"] = epg_template_name
+ else:
+ contract["template"] = contract.get("template").replace(" ", "")
if __name__ == "__main__":
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py
index bd98fc321..f479fe807 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_selector.py
@@ -94,7 +94,6 @@ EXAMPLES = r"""
operator: in
value: test
state: present
- delegate_to: localhost
- name: Remove a Selector
cisco.mso.mso_schema_template_anp_epg_selector:
@@ -107,7 +106,6 @@ EXAMPLES = r"""
epg: EPG 1
selector: selector_1
state: absent
- delegate_to: localhost
- name: Query a specific Selector
cisco.mso.mso_schema_template_anp_epg_selector:
@@ -120,7 +118,6 @@ EXAMPLES = r"""
epg: EPG 1
selector: selector_1
state: query
- delegate_to: localhost
register: query_result
- name: Query all Selectors
@@ -133,7 +130,6 @@ EXAMPLES = r"""
anp: ANP 1
epg: EPG 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py
index b060ddd5e..2a88803b3 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_subnet.py
@@ -89,7 +89,6 @@ EXAMPLES = r"""
epg: EPG 1
subnet: 10.0.0.0/24
state: present
- delegate_to: localhost
- name: Remove a subnet from an EPG
cisco.mso.mso_schema_template_anp_epg_subnet:
@@ -102,7 +101,6 @@ EXAMPLES = r"""
epg: EPG 1
subnet: 10.0.0.0/24
state: absent
- delegate_to: localhost
- name: Query a specific EPG subnet
cisco.mso.mso_schema_template_anp_epg_subnet:
@@ -115,7 +113,6 @@ EXAMPLES = r"""
epg: EPG 1
subnet: 10.0.0.0/24
state: query
- delegate_to: localhost
register: query_result
- name: Query all EPGs subnets
@@ -127,7 +124,6 @@ EXAMPLES = r"""
template: Template 1
anp: ANP 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_useg_attribute.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_useg_attribute.py
index 1f61e95de..0e9b8178e 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_useg_attribute.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_anp_epg_useg_attribute.py
@@ -69,7 +69,7 @@ options:
description:
- The uSeg Subnet can only be used when the I(attribute_type) is IP.
- Use C(false) to set the custom uSeg Subnet IP address to the uSeg Attribute.
- - Use C(true) to set the uSeg Subnet IP address to 0.0.0.0.
+ - Use C(true) to set the default uSeg Subnet IP address 0.0.0.0.
type: bool
state:
description:
@@ -98,7 +98,6 @@ EXAMPLES = r"""
useg_subnet: false
value: 10.0.0.0/24
state: present
- delegate_to: localhost
- name: Query a specific EPG uSeg attr with name
cisco.mso.mso_schema_template_anp_epg_useg_attribute:
@@ -111,7 +110,6 @@ EXAMPLES = r"""
epg: EPG 1
name: useg_attr_ip
state: query
- delegate_to: localhost
register: query_result
- name: Query all EPG uSeg attrs
@@ -124,7 +122,6 @@ EXAMPLES = r"""
anp: ANP 1
epg: EPG 1
state: query
- delegate_to: localhost
register: query_result
- name: Remove a uSeg attr from an EPG with name
@@ -138,7 +135,6 @@ EXAMPLES = r"""
epg: EPG 1
name: useg_attr_ip
state: absent
- delegate_to: localhost
"""
RETURN = r"""
@@ -233,11 +229,13 @@ def main():
payload = dict(name=name, displayName=name, description=description, type=EPG_U_SEG_ATTR_TYPE_MAP[attribute_type], value=value)
if attribute_type == "ip":
- if useg_subnet is False:
- payload["fvSubnet"] = True
- else:
- payload["fvSubnet"] = False
+ if useg_subnet:
+ if value != "" and value != "0.0.0.0" and value is not None:
+ mso.fail_json(msg="The value of uSeg subnet IP should be an empty string or 0.0.0.0, when the useg_subnet is set to true.")
+ payload["fvSubnet"] = useg_subnet
payload["value"] = "0.0.0.0"
+ else:
+ payload["fvSubnet"] = useg_subnet
mso.sanitize(payload, collate=True)
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py
index 0793e844d..ca2f9445d 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd.py
@@ -250,7 +250,6 @@ EXAMPLES = r"""
vrf:
name: VRF1
state: present
- delegate_to: localhost
- name: Add a new BD from another Schema
mso_schema_template_bd:
@@ -265,7 +264,6 @@ EXAMPLES = r"""
schema: Schema Origin
template: Template Origin
state: present
- delegate_to: localhost
- name: Add bd with options available on version 3.1
mso_schema_template_bd:
@@ -357,7 +355,6 @@ EXAMPLES = r"""
name: ansible_test_option
version: 1
state: present
- delegate_to: localhost
- name: Remove a BD
cisco.mso.mso_schema_template_bd:
@@ -368,7 +365,6 @@ EXAMPLES = r"""
template: Template 1
bd: BD1
state: absent
- delegate_to: localhost
- name: Query a specific BD
cisco.mso.mso_schema_template_bd:
@@ -379,7 +375,6 @@ EXAMPLES = r"""
template: Template 1
bd: BD1
state: query
- delegate_to: localhost
register: query_result
- name: Query all BDs
@@ -390,7 +385,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py
index 64fe360ea..095e5b668 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_dhcp_policy.py
@@ -85,7 +85,6 @@ EXAMPLES = r"""
name: ansible_test_option
version: 1
state: present
- delegate_to: localhost
- name: Remove a DHCP policy from a BD
cisco.mso.mso_schema_template_bd_dhcp_policy:
@@ -98,7 +97,6 @@ EXAMPLES = r"""
name: ansible_test
version: 1
state: absent
- delegate_to: localhost
- name: Query a specific BD DHCP Policy
cisco.mso.mso_schema_template_bd_dhcp_policy:
@@ -110,7 +108,6 @@ EXAMPLES = r"""
bd: BD 1
name: ansible_test
state: query
- delegate_to: localhost
register: query_result
- name: Query all BD DHCP Policies
@@ -122,7 +119,6 @@ EXAMPLES = r"""
template: Template 1
bd: BD 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py
index cc55eea7b..e4ae628e4 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_bd_subnet.py
@@ -98,7 +98,6 @@ EXAMPLES = r"""
bd: BD 1
subnet: 10.0.0.0/24
state: present
- delegate_to: localhost
- name: Remove a subset from a BD
cisco.mso.mso_schema_template_bd_subnet:
@@ -110,7 +109,6 @@ EXAMPLES = r"""
bd: BD 1
subnet: 10.0.0.0/24
state: absent
- delegate_to: localhost
- name: Query a specific BD subnet
cisco.mso.mso_schema_template_bd_subnet:
@@ -122,7 +120,6 @@ EXAMPLES = r"""
bd: BD 1
subnet: 10.0.0.0/24
state: query
- delegate_to: localhost
register: query_result
- name: Query all BD subnets
@@ -134,7 +131,6 @@ EXAMPLES = r"""
template: Template 1
bd: BD 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py
index 0cd41779f..dd1d1e067 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_clone.py
@@ -69,7 +69,6 @@ EXAMPLES = r"""
destination_template_name: Template1_clone
destination_template_display_name: Template1_clone
state: clone
- delegate_to: localhost
- name: Clone template to different schema
cisco.mso.mso_schema_template_clone:
@@ -83,7 +82,6 @@ EXAMPLES = r"""
destination_template_name: Cloned_template_1
destination_template_display_name: Cloned_template_1
state: clone
- delegate_to: localhost
- name: Clone template in the same schema but different tenant attached
cisco.mso.mso_schema_template_clone:
@@ -96,7 +94,6 @@ EXAMPLES = r"""
source_template_name: Template1_clone
destination_template_name: Template1_clone_2
state: clone
- delegate_to: localhost
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py
index 11bf08731..cb2ac1b0d 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_filter.py
@@ -128,7 +128,6 @@ EXAMPLES = r"""
contract_scope: global
filter: Filter 1
state: present
- delegate_to: localhost
- name: Remove a contract filter
cisco.mso.mso_schema_template_contract_filter:
@@ -140,7 +139,6 @@ EXAMPLES = r"""
contract: Contract 1
filter: Filter 1
state: absent
- delegate_to: localhost
- name: Query a specific contract filter
cisco.mso.mso_schema_template_contract_filter:
@@ -152,7 +150,6 @@ EXAMPLES = r"""
contract: Contract 1
filter: Filter 1
state: query
- delegate_to: localhost
register: query_result
- name: Query all contract filters
@@ -164,7 +161,6 @@ EXAMPLES = r"""
template: Template 1
contract: Contract 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py
index 0e398843b..deaa82e18 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_contract_service_graph.py
@@ -113,7 +113,6 @@ EXAMPLES = r"""
consumer: b2
filter: Filter 1
state: present
- delegate_to: localhost
- name: Remove a contract service graph
cisco.mso.mso_schema_template_contract_service_graph:
@@ -125,7 +124,6 @@ EXAMPLES = r"""
contract: Contract 1
service_graph: SG1
state: absent
- delegate_to: localhost
- name: Query a contract service graph
cisco.mso.mso_schema_template_contract_service_graph:
@@ -136,7 +134,6 @@ EXAMPLES = r"""
template: Template 1
contract: Contract 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py
index 49df465c5..d3714d184 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy.py
@@ -60,7 +60,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: deploy
- delegate_to: localhost
- name: Undeploy a schema template
cisco.mso.mso_schema_template_deploy:
@@ -71,7 +70,6 @@ EXAMPLES = r"""
template: Template 1
site: Site 1
state: undeploy
- delegate_to: localhost
- name: Get deployment status
cisco.mso.mso_schema:
@@ -81,7 +79,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: status
- delegate_to: localhost
register: status_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py
index 707ad7320..768694905 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_deploy_status.py
@@ -51,7 +51,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
- name: Query status of objects using site
@@ -62,7 +61,6 @@ EXAMPLES = r"""
schema: Schema 1
site: ansible_test
state: query
- delegate_to: localhost
register: query_result
- name: Query status of objects in a template associated with a site
@@ -74,7 +72,6 @@ EXAMPLES = r"""
template: Template 1
site: ansible_test
state: query
- delegate_to: localhost
register: query_result
- name: Query status of objects in all templates
@@ -84,7 +81,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
schema: Schema 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py
index ce201913f..3da65c7f5 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg.py
@@ -138,7 +138,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: present
- delegate_to: localhost
- name: Add a new external EPG with external epg in cloud
cisco.mso.mso_schema_template_external_epg:
@@ -158,7 +157,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: present
- delegate_to: localhost
- name: Remove an external EPG
cisco.mso.mso_schema_template_external_epg:
@@ -169,7 +167,6 @@ EXAMPLES = r"""
template: Template 1
external_epg: external EPG1
state: absent
- delegate_to: localhost
- name: Query a specific external EPGs
cisco.mso.mso_schema_template_external_epg:
@@ -180,7 +177,6 @@ EXAMPLES = r"""
template: Template 1
external_epg: external EPG1
state: query
- delegate_to: localhost
register: query_result
- name: Query all external EPGs
@@ -191,7 +187,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py
index 4029175bc..3b13c8020 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_contract.py
@@ -85,7 +85,6 @@ EXAMPLES = r"""
name: Contract 1
type: consumer
state: present
- delegate_to: localhost
- name: Remove a Contract
cisco.mso.mso_schema_template_external_epg_contract:
@@ -98,7 +97,6 @@ EXAMPLES = r"""
contract:
name: Contract 1
state: absent
- delegate_to: localhost
- name: Query a specific Contract
cisco.mso.mso_schema_template_external_epg_contract:
@@ -111,7 +109,6 @@ EXAMPLES = r"""
contract:
name: Contract 1
state: query
- delegate_to: localhost
register: query_result
- name: Query all Contracts
@@ -122,7 +119,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py
index 7d4b93b27..679c4ee27 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_selector.py
@@ -90,7 +90,6 @@ EXAMPLES = r"""
operator: equals
value: 10.0.0.0
state: present
- delegate_to: localhost
- name: Remove a Selector
cisco.mso.mso_schema_template_external_epg_selector:
@@ -102,7 +101,6 @@ EXAMPLES = r"""
external_epg: extEPG 1
selector: selector_1
state: absent
- delegate_to: localhost
- name: Query a specific Selector
cisco.mso.mso_schema_template_external_epg_selector:
@@ -114,7 +112,6 @@ EXAMPLES = r"""
external_epg: extEPG 1
selector: selector_1
state: query
- delegate_to: localhost
register: query_result
- name: Query all Selectors
@@ -126,7 +123,6 @@ EXAMPLES = r"""
template: Template 1
external_epg: extEPG 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py
index c7512c2ee..7339312a0 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_external_epg_subnet.py
@@ -50,6 +50,7 @@ options:
- The C(shared-security) for Shared Security Import can only be used when External Subnets for External EPG is present
type: list
elements: str
+ default: []
aggregate:
description:
- The aggregate option aggregates shared routes for the subnet.
@@ -57,6 +58,7 @@ options:
- The C(shared-rtctrl) option can only be used when scope parameter Shared Route Control in the Route Control section is selected.
type: list
elements: str
+ default: []
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -80,7 +82,6 @@ EXAMPLES = r"""
external_epg: EPG 1
subnet: 10.0.0.0/24
state: present
- delegate_to: localhost
- name: Remove a subnet from an External EPG
cisco.mso.mso_schema_template_external_epg_subnet:
@@ -92,7 +93,6 @@ EXAMPLES = r"""
external_epg: EPG 1
subnet: 10.0.0.0/24
state: absent
- delegate_to: localhost
- name: Query a specific External EPG subnet
cisco.mso.mso_schema_template_external_epg_subnet:
@@ -104,7 +104,6 @@ EXAMPLES = r"""
external_epg: EPG 1
subnet: 10.0.0.0/24
state: query
- delegate_to: localhost
register: query_result
- name: Query all External EPGs subnets
@@ -115,7 +114,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py
index ce201913f..3da65c7f5 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_externalepg.py
@@ -138,7 +138,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: present
- delegate_to: localhost
- name: Add a new external EPG with external epg in cloud
cisco.mso.mso_schema_template_external_epg:
@@ -158,7 +157,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: present
- delegate_to: localhost
- name: Remove an external EPG
cisco.mso.mso_schema_template_external_epg:
@@ -169,7 +167,6 @@ EXAMPLES = r"""
template: Template 1
external_epg: external EPG1
state: absent
- delegate_to: localhost
- name: Query a specific external EPGs
cisco.mso.mso_schema_template_external_epg:
@@ -180,7 +177,6 @@ EXAMPLES = r"""
template: Template 1
external_epg: external EPG1
state: query
- delegate_to: localhost
register: query_result
- name: Query all external EPGs
@@ -191,7 +187,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py
index c0ab485a4..5bf69bde1 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_filter_entry.py
@@ -132,7 +132,6 @@ EXAMPLES = r"""
template: Template 1
filter: Filter 1
state: present
- delegate_to: localhost
- name: Remove a filter entry
cisco.mso.mso_schema_template_filter_entry:
@@ -143,7 +142,6 @@ EXAMPLES = r"""
template: Template 1
filter: Filter 1
state: absent
- delegate_to: localhost
- name: Query a specific filter entry
cisco.mso.mso_schema_template_filter_entry:
@@ -154,7 +152,6 @@ EXAMPLES = r"""
template: Template 1
filter: Filter 1
state: query
- delegate_to: localhost
register: query_result
- name: Query all filter entries
@@ -165,7 +162,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py
index 4b8c1b66d..eb7857df8 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_l3out.py
@@ -87,7 +87,6 @@ EXAMPLES = r"""
schema: vrfSchema
template: vrfTemplate
state: present
- delegate_to: localhost
- name: Remove an L3out
cisco.mso.mso_schema_template_l3out:
@@ -98,7 +97,6 @@ EXAMPLES = r"""
template: Template 1
l3out: L3out 1
state: absent
- delegate_to: localhost
- name: Query a specific L3outs
cisco.mso.mso_schema_template_l3out:
@@ -110,7 +108,6 @@ EXAMPLES = r"""
template: Template 1
l3out: L3out 1
state: query
- delegate_to: localhost
register: query_result
- name: Query all L3outs
@@ -122,7 +119,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py
index d0e15b8d0..35fa0d205 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_migrate.py
@@ -84,7 +84,6 @@ EXAMPLES = r"""
- epg: EPG1
anp: ANP
state: present
- delegate_to: localhost
- name: Migration of objects between templates of different schema
mso_schema_template_migrate:
@@ -101,7 +100,6 @@ EXAMPLES = r"""
- epg: EPG1
anp: ANP
state: present
- delegate_to: localhost
- name: Migration of BD object between templates of same schema
mso_schema_template_migrate:
@@ -116,7 +114,6 @@ EXAMPLES = r"""
- BD
- BD1
state: present
- delegate_to: localhost
- name: Migration of BD object between templates of different schema
mso_schema_template_migrate:
@@ -131,7 +128,6 @@ EXAMPLES = r"""
- BD
- BD1
state: present
- delegate_to: localhost
- name: Migration of EPG objects between templates of same schema
mso_schema_template_migrate:
@@ -148,7 +144,6 @@ EXAMPLES = r"""
- epg: EPG2
anp: ANP2
state: present
- delegate_to: localhost
- name: Migration of EPG objects between templates of different schema
mso_schema_template_migrate:
@@ -165,7 +160,6 @@ EXAMPLES = r"""
- epg: EPG2
anp: ANP2
state: present
- delegate_to: localhost
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py
index 70fadd804..d304673dc 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_service_graph.py
@@ -83,7 +83,6 @@ EXAMPLES = r"""
- type: other
- type: load-balancer
state: present
- delegate_to: localhost
- name: Remove a Service Graph
cisco.mso.mso_schema_template_service_graph:
@@ -94,7 +93,6 @@ EXAMPLES = r"""
template: Template1
service_graph: graph1
state: absent
- delegate_to: localhost
- name: Query a specific Service Graph
cisco.mso.mso_schema_template_service_graph:
@@ -105,7 +103,7 @@ EXAMPLES = r"""
template: Template1
service_graph: graph1
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all Service Graphs
cisco.mso.mso_schema_template_service_graph:
@@ -115,7 +113,7 @@ EXAMPLES = r"""
schema: Schema1
template: Template1
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
@@ -215,14 +213,15 @@ def main():
node_name = node.get("type")
if node_name in service_node_types:
service_node_index = service_node_index + 1
+ service_node_name = "node{0}".format(service_node_index)
for node_data in query_node_data:
if node_data["name"] == node_name:
payload = dict(
- name=node_name,
+ name=service_node_name,
serviceNodeTypeId=node_data.get("id"),
index=service_node_index,
serviceNodeRef=dict(
- serviceNodeName=node_name,
+ serviceNodeName=service_node_name,
serviceGraphName=service_graph,
templateName=template,
schemaId=schema_id,
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py
index efafd2387..84464c312 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf.py
@@ -79,7 +79,6 @@ EXAMPLES = r"""
template: Template 1
vrf: VRF 1
state: present
- delegate_to: localhost
- name: Remove an VRF
cisco.mso.mso_schema_template_vrf:
@@ -90,7 +89,6 @@ EXAMPLES = r"""
template: Template 1
vrf: VRF1
state: absent
- delegate_to: localhost
- name: Query a specific VRFs
cisco.mso.mso_schema_template_vrf:
@@ -101,7 +99,6 @@ EXAMPLES = r"""
template: Template 1
vrf: VRF1
state: query
- delegate_to: localhost
register: query_result
- name: Query all VRFs
@@ -112,7 +109,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py
index eaef8235c..fbfb96352 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_template_vrf_contract.py
@@ -84,7 +84,6 @@ EXAMPLES = r"""
name: Contract 1
type: consumer
state: present
- delegate_to: localhost
- name: Remove a Contract
cisco.mso.mso_schema_template_vrf_contract:
@@ -98,7 +97,6 @@ EXAMPLES = r"""
name: Contract 1
type: consumer
state: absent
- delegate_to: localhost
- name: Query a specific Contract
cisco.mso.mso_schema_template_vrf_contract:
@@ -112,7 +110,6 @@ EXAMPLES = r"""
name: Contract 1
type: consumer
state: query
- delegate_to: localhost
register: query_result
- name: Query all Contracts
@@ -124,7 +121,6 @@ EXAMPLES = r"""
template: Template 1
vrf: VRF 1
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py b/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py
index a4a4f6cd0..35b082618 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_schema_validate.py
@@ -35,11 +35,19 @@ options:
default: query
choices: [ query ]
seealso:
-- module: cisco.mso.mso_schema_template_external_epg
+- module: cisco.mso.mso_schema
extends_documentation_fragment: cisco.mso.modules
"""
EXAMPLES = r"""
+ - name: Get Validation status
+ mso_schema_validate:
+ host: mso_host
+ username: admin
+ password: SomeSecretPassword
+ schema: Schema 1
+ state: query
+ register: query_validate
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py b/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py
index b8319256b..eace7bd26 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_service_node_type.py
@@ -46,7 +46,6 @@ EXAMPLES = r"""
name: ips
display_name: ips
state: present
- delegate_to: localhost
- name: Remove a Service Node Type
cisco.mso.mso_schema_service_node:
@@ -55,7 +54,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
name: ips
state: absent
- delegate_to: localhost
- name: Query a specific Service Node Type
cisco.mso.mso_schema_service_node:
@@ -64,7 +62,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
name: ips
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all Service Node Types
cisco.mso.mso_schema_service_node:
@@ -72,7 +70,7 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_site.py
index a3778d28a..d12f935bc 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_site.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_site.py
@@ -110,7 +110,6 @@ EXAMPLES = r"""
latitude: 50.887318
longitude: 4.447084
state: present
- delegate_to: localhost
- name: Remove a site
cisco.mso.mso_site:
@@ -119,7 +118,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
site: north_europe
state: absent
- delegate_to: localhost
- name: Query a site
cisco.mso.mso_site:
@@ -128,7 +126,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
site: north_europe
state: query
- delegate_to: localhost
register: query_result
- name: Query all sites
@@ -137,7 +134,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py b/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py
index 17aa457e3..b1caf3cbe 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_tenant.py
@@ -3,6 +3,7 @@
# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.com>
# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com>
+# Copyright: (c) 2023, Anvitha Jain (@anvjain) <anvjain@cisco.com>
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
@@ -40,6 +41,22 @@ options:
- Admin user is always added to the associated user list irrespective of this parameter being used.
type: list
elements: str
+ remote_users:
+ description:
+ - A list of associated remote users for this tenant.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The name of the associated remote user for this tenant.
+ required: true
+ type: str
+ login_domain:
+ description:
+ - Domain name of the associated remote user for this tenant.
+ required: true
+ type: str
sites:
description:
- A list of associated sites for this tenant.
@@ -73,7 +90,6 @@ EXAMPLES = r"""
display_name: North European Datacenter
description: This tenant manages the NEDC environment.
state: present
- delegate_to: localhost
- name: Remove a tenant from MSO and Site/APIC
cisco.mso.mso_tenant:
@@ -83,7 +99,6 @@ EXAMPLES = r"""
tenant: north_europe
orchestrator_only: no
state: absent
- delegate_to: localhost
- name: Remove a tenant from MSO only
cisco.mso.mso_tenant:
@@ -93,7 +108,6 @@ EXAMPLES = r"""
tenant: north_europe
orchestrator_only: yes
state: absent
- delegate_to: localhost
- name: Query a tenant
cisco.mso.mso_tenant:
@@ -102,7 +116,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
tenant: north_europe
state: query
- delegate_to: localhost
register: query_result
- name: Query all tenants
@@ -111,7 +124,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
register: query_result
"""
@@ -119,7 +131,7 @@ RETURN = r"""
"""
from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec
+from ansible_collections.cisco.mso.plugins.module_utils.mso import MSOModule, mso_argument_spec, ndo_remote_user_spec
from ansible_collections.cisco.mso.plugins.module_utils.constants import YES_OR_NO_TO_BOOL_STRING_MAP
@@ -130,6 +142,7 @@ def main():
display_name=dict(type="str"),
tenant=dict(type="str", aliases=["name"]),
users=dict(type="list", elements="str"),
+ remote_users=dict(type="list", elements="dict", options=ndo_remote_user_spec()),
sites=dict(type="list", elements="str"),
orchestrator_only=dict(type="str", default="yes", choices=["yes", "no"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
@@ -149,13 +162,10 @@ def main():
tenant = module.params.get("tenant")
orchestrator_only = module.params.get("orchestrator_only")
state = module.params.get("state")
+ remote_users = module.params.get("remote_users")
mso = MSOModule(module)
- # Convert sites and users
- sites = mso.lookup_sites(module.params.get("sites"))
- users = mso.lookup_users(module.params.get("users"))
-
tenant_id = None
path = "tenants"
@@ -182,6 +192,12 @@ def main():
mso.existing = mso.request(path, method="DELETE")
elif state == "present":
+ # Convert sites and users
+ sites = mso.lookup_sites(module.params.get("sites"))
+ users = mso.lookup_users(module.params.get("users"))
+ if remote_users is not None:
+ users += mso.lookup_remote_users(remote_users)
+
mso.previous = mso.existing
payload = dict(
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py b/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py
index 4b9c2af56..895403e12 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_tenant_site.py
@@ -114,7 +114,6 @@ EXAMPLES = r"""
tenant: tenant_name
site: site_name
state: present
- delegate_to: localhost
- name: Associate AWS site with a tenant, with aws_trusted set to true
cisco.mso.mso_tenant_site:
@@ -126,7 +125,6 @@ EXAMPLES = r"""
cloud_account: '000000000000'
aws_trusted: true
state: present
- delegate_to: localhost
- name: Associate AWS site with a tenant, with aws_trusted set to false
cisco.mso.mso_tenant_site:
@@ -141,7 +139,6 @@ EXAMPLES = r"""
secret_key: '0'
aws_account_org: false
state: present
- delegate_to: localhost
- name: Associate Azure site in managed mode
mso.cisco.mso_tenant_site:
@@ -155,7 +152,6 @@ EXAMPLES = r"""
azure_subscription_id: '9'
azure_application_id: '100'
state: present
- delegate_to: localhost
- name: Associate Azure site in unmanaged mode
mso.cisco.mso_tenant_site:
@@ -173,7 +169,6 @@ EXAMPLES = r"""
azure_active_directory_id: '32'
azure_active_directory_name: CiscoINSBUAd
state: present
- delegate_to: localhost
- name: Dissociate a site
cisco.mso.mso_tenant_site:
@@ -183,7 +178,6 @@ EXAMPLES = r"""
tenant: tenant_name
site: site_name
state: absent
- delegate_to: localhost
- name: Query a site
cisco.mso.mso_tenant_site:
@@ -193,7 +187,7 @@ EXAMPLES = r"""
tenant: tenant_name
site: site_name
state: query
- delegate_to: localhost
+ register: query_result
- name: Query all sites of a tenant
cisco.mso.mso_tenant_site:
@@ -202,7 +196,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
tenant: tenant_name
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_user.py b/ansible_collections/cisco/mso/plugins/modules/mso_user.py
index 37127a7f1..d74088206 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_user.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_user.py
@@ -87,7 +87,6 @@ EXAMPLES = r"""
user: admin
user_password: newPassword
state: present
- delegate_to: localhost
- name: Add a new user
cisco.mso.mso_user:
@@ -107,7 +106,6 @@ EXAMPLES = r"""
- name: schemaManager
access_type: read
state: present
- delegate_to: localhost
- name: Add a new user
cisco.mso.mso_user:
@@ -122,7 +120,6 @@ EXAMPLES = r"""
phone: +32 478 436 299
roles:
- powerUser
- delegate_to: localhost
- name: Remove a user
cisco.mso.mso_user:
@@ -132,7 +129,6 @@ EXAMPLES = r"""
validate_certs: false
user: dag
state: absent
- delegate_to: localhost
- name: Query a user
cisco.mso.mso_user:
@@ -142,7 +138,6 @@ EXAMPLES = r"""
validate_certs: false
user: dag
state: query
- delegate_to: localhost
register: query_result
- name: Query all users
@@ -152,7 +147,6 @@ EXAMPLES = r"""
password: SomeSecretPassword
validate_certs: false
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/mso_version.py b/ansible_collections/cisco/mso/plugins/modules/mso_version.py
index 19668afb4..f2c0ce8a1 100644
--- a/ansible_collections/cisco/mso/plugins/modules/mso_version.py
+++ b/ansible_collections/cisco/mso/plugins/modules/mso_version.py
@@ -35,7 +35,6 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
state: query
- delegate_to: localhost
register: query_result
"""
diff --git a/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py b/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py
index b8bdb63ae..f1971280e 100644
--- a/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py
+++ b/ansible_collections/cisco/mso/plugins/modules/ndo_schema_template_deploy.py
@@ -61,7 +61,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: deploy
- delegate_to: localhost
- name: Redeploy a schema template
cisco.mso.ndo_schema_template_deploy:
@@ -71,7 +70,6 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: redeploy
- delegate_to: localhost
- name: Undeploy a schema template
cisco.mso.ndo_schema_template_deploy:
@@ -82,7 +80,6 @@ EXAMPLES = r"""
template: Template 1
sites: [ Site1, Site2 ]
state: undeploy
- delegate_to: localhost
- name: Query a schema template deploy status
cisco.mso.ndo_schema_template_deploy:
@@ -92,7 +89,7 @@ EXAMPLES = r"""
schema: Schema 1
template: Template 1
state: query
- delegate_to: localhost
+ register: query_result
"""
RETURN = r"""