summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cisco/aci/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
commit38b7c80217c4e72b1d8988eb1e60bb6e77334114 (patch)
tree356e9fd3762877d07cde52d21e77070aeff7e789 /ansible_collections/cisco/aci/plugins
parentAdding upstream version 7.7.0+dfsg. (diff)
downloadansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.tar.xz
ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.zip
Adding upstream version 9.4.0+dfsg.upstream/9.4.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/aci/plugins')
-rw-r--r--ansible_collections/cisco/aci/plugins/doc_fragments/aci.py11
-rw-r--r--ansible_collections/cisco/aci/plugins/httpapi/aci.py340
-rw-r--r--ansible_collections/cisco/aci/plugins/module_utils/aci.py438
-rw-r--r--ansible_collections/cisco/aci/plugins/module_utils/constants.py99
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py34
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py49
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group.py326
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src.py442
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src_path.py360
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py13
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py59
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py294
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py330
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py34
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py45
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py20
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py75
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py112
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py12
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py15
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_epg_subnet.py470
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py364
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py30
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_node_control.py268
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py406
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py139
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_dst_group.py361
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group.py300
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src.py416
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_node.py295
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_path.py304
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py318
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_file_remote_path.py357
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py196
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py65
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py37
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py148
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py4
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py47
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py366
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py312
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py57
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py25
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spine_policy_group.py355
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out.py197
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py47
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_protocol_profile.py326
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py107
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py12
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py1
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py41
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py8
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py60
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py17
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py137
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_match_as_path_regex_term.py306
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_match_community_factor.py325
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_match_community_regex_term.py317
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_match_community_term.py298
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py325
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_match_rule.py283
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_rest.py88
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_route_control_context.py393
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_route_control_profile.py333
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py22
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py1
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_system_banner.py296
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_system_endpoint_controls.py338
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_system_global_aes_passphrase_encryption.py236
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_tag.py1
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py4
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py2
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py7
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py10
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py4
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_vrf.py10
-rw-r--r--ansible_collections/cisco/aci/plugins/modules/aci_vrf_leak_internal_subnet.py397
85 files changed, 12873 insertions, 840 deletions
diff --git a/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py b/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py
index 2bed3dc59..e6b18a289 100644
--- a/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py
+++ b/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py
@@ -18,7 +18,6 @@ options:
- IP Address or hostname of APIC resolvable by Ansible control host.
- If the value is not specified in the task, the value of environment variable C(ACI_HOST) will be used instead.
type: str
- required: true
aliases: [ hostname ]
port:
description:
@@ -30,8 +29,8 @@ options:
description:
- The username to use for authentication.
- If the value is not specified in the task, the value of environment variables C(ACI_USERNAME) or C(ANSIBLE_NET_USERNAME) will be used instead.
+ - The default value is admin.
type: str
- default: admin
aliases: [ user ]
password:
description:
@@ -69,27 +68,27 @@ options:
description:
- The socket level timeout in seconds.
- If the value is not specified in the task, the value of environment variable C(ACI_TIMEOUT) will be used instead.
+ - The default value is 30.
type: int
- default: 30
use_proxy:
description:
- If C(false), it will not use a proxy, even if one is defined in an environment variable on the target hosts.
- If the value is not specified in the task, the value of environment variable C(ACI_USE_PROXY) will be used instead.
+ - The default value is true.
type: bool
- default: true
use_ssl:
description:
- If C(false), an HTTP connection will be used instead of the default HTTPS connection.
- If the value is not specified in the task, the value of environment variable C(ACI_USE_SSL) will be used instead.
+ - The default value is true when the connection is local.
type: bool
- default: true
validate_certs:
description:
- If C(false), SSL certificates will not be validated.
- This should only set to C(false) when used on personally controlled sites using self-signed certificates.
- If the value is not specified in the task, the value of environment variable C(ACI_VALIDATE_CERTS) will be used instead.
+ - The default value is true.
type: bool
- default: true
output_path:
description:
- Path to a file that will be used to dump the ACI JSON configuration objects generated by the module.
diff --git a/ansible_collections/cisco/aci/plugins/httpapi/aci.py b/ansible_collections/cisco/aci/plugins/httpapi/aci.py
new file mode 100644
index 000000000..a0474576a
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/httpapi/aci.py
@@ -0,0 +1,340 @@
+# Copyright (c) 2020 Cisco and/or its affiliates.
+# Copyright: (c) 2020, Shreyas Srish (@shrsr) <ssrish@cisco.com>
+# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+---
+name: aci
+author:
+- Shreyas Srish (@shrsr)
+short_description: Ansible ACI HTTPAPI Plugin.
+description:
+ - This ACI plugin provides the HTTPAPI methods needed to initiate
+ a connection to the APIC, send API requests and process the
+ response from the controller.
+"""
+
+import ast
+import base64
+import json
+import os
+import re
+
+from ansible.module_utils._text import to_text, to_native
+from ansible.module_utils.connection import ConnectionError
+from ansible.plugins.httpapi import HttpApiBase
+from copy import copy, deepcopy
+
+# Optional, only used for APIC signature-based authentication
+try:
+ from OpenSSL.crypto import FILETYPE_PEM, load_privatekey, sign
+
+ HAS_OPENSSL = True
+except ImportError:
+ HAS_OPENSSL = False
+
+# Signature-based authentication using cryptography
+try:
+ from cryptography.hazmat.primitives import serialization, hashes
+ from cryptography.hazmat.primitives.asymmetric import padding
+ from cryptography.hazmat.backends import default_backend
+
+ HAS_CRYPTOGRAPHY = True
+except ImportError:
+ HAS_CRYPTOGRAPHY = False
+
+CONNECTION_MAP = {"username": "remote_user", "timeout": "persistent_command_timeout"}
+RESET_KEYS = ["username", "password", "port"]
+CONNECTION_KEYS = RESET_KEYS + ["timeout", "use_proxy", "use_ssl", "validate_certs"]
+
+
+class HttpApi(HttpApiBase):
+ def __init__(self, *args, **kwargs):
+ super(HttpApi, self).__init__(*args, **kwargs)
+ self.params = None
+ self.result = {}
+ self.backup_hosts = None
+ self.connection_error_check = False
+ self.connection_parameters = {}
+ self.current_host = None
+ self.provided_hosts = None
+ self.inventory_hosts = None
+
+ def set_params(self, params):
+ self.params = params
+
+ # Login function is executed until connection to a host is established or until all the hosts in the list are exhausted
+ def login(self, username, password):
+ """Log in to APIC"""
+ # Perform login request
+ self.connection.queue_message("debug", "Establishing login for {0} to {1}".format(username, self.connection.get_option("host")))
+ method = "POST"
+ path = "/api/aaaLogin.json"
+ payload = {"aaaUser": {"attributes": {"name": username, "pwd": password}}}
+ data = json.dumps(payload)
+ self.connection._connected = True
+ try:
+ response, response_data = self.connection.send(path, data, method=method)
+ response_value = self._get_response_value(response_data)
+ self.connection._auth = {
+ "Cookie": "APIC-Cookie={0}".format(self._response_to_json(response_value).get("imdata")[0]["aaaLogin"]["attributes"]["token"])
+ }
+ self.connection.queue_message("debug", "Connection to {0} was successful".format(self.connection.get_option("host")))
+ except Exception as exc_login:
+ self.connection._connected = False
+ exc_login.path = path
+ raise
+
+ def logout(self):
+ method = "POST"
+ path = "/api/aaaLogout.json"
+ payload = {"aaaUser": {"attributes": {"name": self.connection.get_option("remote_user")}}}
+ data = json.dumps(payload)
+ try:
+ response, response_data = self.connection.send(path, data, method=method)
+ except Exception as exc_logout:
+ msg = "Error on attempt to logout from APIC. {0}".format(exc_logout)
+ raise ConnectionError(self._return_info("", method, path, msg))
+ self.connection._auth = None
+ self._verify_response(response, method, path, response_data)
+
+ def set_parameters(self):
+ connection_parameters = {}
+ for key in CONNECTION_KEYS:
+ value = self.params.get(key) if self.params.get(key) is not None else self.connection.get_option(CONNECTION_MAP.get(key, key))
+ if key == "username" and value is None:
+ value = "admin"
+ self.connection.set_option(CONNECTION_MAP.get(key, key), value)
+ if key == "timeout" and self.connection.get_option("persistent_connect_timeout") <= value:
+ self.connection.set_option("persistent_connect_timeout", value + 30)
+
+ connection_parameters[key] = value
+ if self.connection_parameters and value != self.connection_parameters.get(key) and key in RESET_KEYS:
+ self.connection._connected = False
+ self.connection.queue_message("debug", "Re-setting connection due to change in the {0}".format(key))
+
+ if self.params.get("private_key") is not None:
+ self.connection.set_option("session_key", None)
+ connection_parameters["certificate_name"] = self.params.get("certificate_name")
+ connection_parameters["private_key"] = self.params.get("private_key")
+ elif self.connection.get_option("session_key") is not None and self.params.get("password") is None:
+ connection_parameters["certificate_name"] = list(self.connection.get_option("session_key").keys())[0]
+ connection_parameters["private_key"] = list(self.connection.get_option("session_key").values())[0]
+ else:
+ if self.connection_parameters.get("private_key") is not None:
+ self.connection._connected = False
+ self.connection.queue_message(
+ "debug", "Re-setting connection due to change from private/session key authentication to password authentication"
+ )
+ self.connection.set_option("session_key", None)
+ connection_parameters["private_key"] = None
+ connection_parameters["certificate_name"] = None
+
+ if self.connection_parameters != connection_parameters:
+ self.connection_parameters = copy(connection_parameters)
+
+ self.set_hosts()
+
+ def set_hosts(self):
+ if self.params.get("host") is not None:
+ hosts = ast.literal_eval(self.params.get("host")) if "[" in self.params.get("host") else self.params.get("host").split(",")
+ else:
+ if self.inventory_hosts is None:
+ self.inventory_hosts = re.sub(r"[[\]]", "", self.connection.get_option("host")).split(",")
+ hosts = self.inventory_hosts
+
+ if self.provided_hosts is None:
+ self.provided_hosts = deepcopy(hosts)
+ self.connection.queue_message("debug", "Provided Hosts: {0}".format(self.provided_hosts))
+ self.backup_hosts = deepcopy(hosts)
+ self.current_host = self.backup_hosts.pop(0)
+ self.connection.queue_message("debug", "Initializing operation on {0}".format(self.current_host))
+ elif self.provided_hosts != hosts:
+ self.provided_hosts = deepcopy(hosts)
+ self.connection.queue_message("debug", "Provided Hosts have changed: {0}".format(self.provided_hosts))
+ self.backup_hosts = deepcopy(hosts)
+ try:
+ self.backup_hosts.pop(self.backup_hosts.index(self.current_host))
+ self.connection.queue_message("debug", "Connected host {0} found in the provided hosts. Continuing with it.".format(self.current_host))
+ except Exception:
+ self.current_host = self.backup_hosts.pop(0)
+ self.connection._connected = False
+ self.connection.queue_message("debug", "Initializing operation on {0}".format(self.current_host))
+ self.connection.set_option("host", self.current_host)
+
+ # One API call is made via each call to send_request from aci.py in module_utils
+ # As long as a host is active in the list the API call will go through
+ def send_request(self, method, path, data):
+ """This method handles all APIC REST API requests other than login"""
+
+ self.set_parameters()
+
+ if self.connection_parameters.get("private_key") is not None:
+ try:
+ self.connection._auth = {"Cookie": "{0}".format(self.cert_auth(method, path, data).get("Cookie"))}
+ self.connection._connected = True
+ except Exception as exc_response:
+ self.connection._connected = False
+ return self._return_info("", method, self.validate_url(self.connection._url + path), str(exc_response))
+
+ try:
+ if self.connection._connected is False:
+ self.login(self.connection.get_option("remote_user"), self.connection.get_option("password"))
+ self.connection.queue_message("debug", "Sending {0} request to {1}".format(method, self.connection._url + path))
+ response, response_data = self.connection.send(path, data, method=method)
+ self.connection.queue_message(
+ "debug", "Received response from {0} for {1} operation with HTTP: {2}".format(self.connection.get_option("host"), method, response.getcode())
+ )
+ except Exception as exc_response:
+ self.connection.queue_message("debug", "Connection to {0} has failed: {1}".format(self.connection.get_option("host"), exc_response))
+ if len(self.backup_hosts) == 0:
+ self.provided_hosts = None
+ self.connection._connected = False
+ error = dict(
+ code=-1, text="No hosts left in the cluster to continue operation! Error on final host {0}".format(self.connection.get_option("host"))
+ )
+ if "path" in dir(exc_response):
+ path = exc_response.path
+ return self._return_info("", method, self.validate_url(self.connection._url + path), str(exc_response), error=error)
+ else:
+ self.current_host = self.backup_hosts.pop(0)
+ self.connection.queue_message("debug", "Switching host from {0} to {1}".format(self.connection.get_option("host"), self.current_host))
+ self.connection.set_option("host", self.current_host)
+ # recurse through function for retrying the request
+ return self.send_request(method, path, data)
+ # return statement executed upon each successful response from the request function
+ return self._verify_response(response, method, path, response_data)
+
+ # Built-in-function
+ def handle_httperror(self, exc):
+ self.connection.queue_message("debug", "Failed to receive response from {0} with {1}".format(self.connection.get_option("host"), exc))
+ if exc.code == 401:
+ raise ConnectionError(exc)
+ elif exc.code == 403 and self.connection_parameters.get("private_key") is None:
+ self.connection._auth = None
+ self.login(self.connection.get_option("remote_user"), self.connection.get_option("password"))
+ return True
+ return exc
+
+ def validate_url(self, url):
+ validated_url = re.match(r"^.*?\.json|^.*?\.xml", url).group(0)
+ if self.connection_parameters.get("port") is None:
+ return validated_url.replace(re.match(r"(https?:\/\/.*)(:\d*)\/?(.*)", url).group(2), "")
+ else:
+ return validated_url
+
+ def _verify_response(self, response, method, path, response_data):
+ """Process the return code and response object from APIC"""
+ response_value = self._get_response_value(response_data)
+ response_code = response.getcode()
+ path = self.validate_url(response.url)
+ # Response check to remain consistent with fetch_url's response
+ if str(response) == "HTTP Error 400: Bad Request":
+ msg = "{0}".format(response)
+ else:
+ msg = "{0} ({1} bytes)".format(response.msg, len(response_value))
+ return self._return_info(response_code, method, path, msg, respond_data=response_value)
+
+ def _get_response_value(self, response_data):
+ """Extract string data from response_data returned from APIC"""
+ return to_text(response_data.getvalue())
+
+ def _response_to_json(self, response_text):
+ """Convert response_text to json format"""
+ try:
+ return json.loads(response_text) if response_text else {}
+ # JSONDecodeError only available on Python 3.5+
+ except Exception:
+ return "Invalid JSON response: {0}".format(response_text)
+
+ def _return_info(self, response_code, method, path, msg, respond_data=None, error=None):
+ """Format success/error data and return with consistent format"""
+ info = {}
+ info["status"] = response_code
+ info["method"] = method
+ info["url"] = path
+ info["msg"] = msg
+ if error is not None:
+ info["error"] = error
+ else:
+ info["error"] = {}
+ # Response check to trigger key error if response_data is invalid
+ if respond_data is not None:
+ info["body"] = respond_data
+ return info
+
+ def cert_auth(self, method, path, payload=""):
+ """Perform APIC signature-based authentication, not the expected SSL client certificate authentication."""
+
+ if payload is None:
+ payload = ""
+
+ headers = dict()
+
+ try:
+ if HAS_CRYPTOGRAPHY:
+ key = self.connection_parameters.get("private_key").encode()
+ sig_key = serialization.load_pem_private_key(
+ key,
+ password=None,
+ backend=default_backend(),
+ )
+ else:
+ sig_key = load_privatekey(FILETYPE_PEM, self.connection_parameters.get("private_key"))
+ except Exception:
+ if os.path.exists(self.connection_parameters.get("private_key")):
+ try:
+ permission = "r"
+ if HAS_CRYPTOGRAPHY:
+ permission = "rb"
+ with open(self.connection_parameters.get("private_key"), permission) as fh:
+ private_key_content = fh.read()
+ except Exception:
+ raise ConnectionError("Cannot open private key file {0}".format(self.connection_parameters.get("private_key")))
+ try:
+ if HAS_CRYPTOGRAPHY:
+ sig_key = serialization.load_pem_private_key(private_key_content, password=None, backend=default_backend())
+ else:
+ sig_key = load_privatekey(FILETYPE_PEM, private_key_content)
+ except Exception:
+ raise ConnectionError("Cannot load private key file {0}".format(self.connection_parameters.get("private_key")))
+ if self.connection_parameters.get("certificate_name") is None:
+ self.connection_parameters["certificate_name"] = os.path.basename(os.path.splitext(self.connection_parameters.get("private_key"))[0])
+ else:
+ raise ConnectionError(
+ "Provided private key {0} does not appear to be a private key or provided file does not exist.".format(
+ self.connection_parameters.get("private_key")
+ )
+ )
+ if self.connection_parameters.get("certificate_name") is None:
+ self.connection_parameters["certificate_name"] = self.connection.get_option("remote_user")
+ sig_request = method + path + payload
+ if HAS_CRYPTOGRAPHY:
+ sig_signature = sig_key.sign(sig_request.encode(), padding.PKCS1v15(), hashes.SHA256())
+ else:
+ sig_signature = sign(sig_key, sig_request, "sha256")
+ sig_dn = "uni/userext/user-{0}/usercert-{1}".format(self.connection.get_option("remote_user"), self.connection_parameters.get("certificate_name"))
+ headers["Cookie"] = (
+ "APIC-Certificate-Algorithm=v1.0; "
+ + "APIC-Certificate-DN={0}; ".format(sig_dn)
+ + "APIC-Certificate-Fingerprint=fingerprint; "
+ + "APIC-Request-Signature={0}".format(to_native(base64.b64encode(sig_signature)))
+ )
+ return headers
diff --git a/ansible_collections/cisco/aci/plugins/module_utils/aci.py b/ansible_collections/cisco/aci/plugins/module_utils/aci.py
index 5d95e5c1e..9c6e2db2d 100644
--- a/ansible_collections/cisco/aci/plugins/module_utils/aci.py
+++ b/ansible_collections/cisco/aci/plugins/module_utils/aci.py
@@ -13,6 +13,8 @@
# Copyright: (c) 2019, Rob Huelga (@RobW3LGA)
# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@cisco.com>
# Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com>
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com>
+# Copyright: (c) 2023, Shreyas Srish (@shrsr) <ssrish@cisco.com>
# All rights reserved.
# Redistribution and use in source and binary forms, with or without modification,
@@ -46,6 +48,7 @@ from copy import deepcopy
from ansible.module_utils.urls import fetch_url
from ansible.module_utils._text import to_bytes, to_native
from ansible.module_utils.basic import env_fallback
+from ansible.module_utils.connection import Connection
# Optional, only used for APIC signature-based authentication
try:
@@ -81,19 +84,24 @@ try:
except ImportError:
HAS_XMLJSON_COBRA = False
+try:
+ from ansible.module_utils.six.moves.urllib.parse import urlparse
+
+ HAS_URLPARSE = True
+except Exception:
+ HAS_URLPARSE = False
+
def aci_argument_spec():
return dict(
host=dict(
type="str",
- required=True,
aliases=["hostname"],
fallback=(env_fallback, ["ACI_HOST"]),
),
port=dict(type="int", required=False, fallback=(env_fallback, ["ACI_PORT"])),
username=dict(
type="str",
- default="admin",
aliases=["user"],
fallback=(env_fallback, ["ACI_USERNAME", "ANSIBLE_NET_USERNAME"]),
),
@@ -121,10 +129,10 @@ def aci_argument_spec():
choices=["debug", "info", "normal"],
fallback=(env_fallback, ["ACI_OUTPUT_LEVEL"]),
),
- timeout=dict(type="int", default=30, fallback=(env_fallback, ["ACI_TIMEOUT"])),
- use_proxy=dict(type="bool", default=True, fallback=(env_fallback, ["ACI_USE_PROXY"])),
- use_ssl=dict(type="bool", default=True, fallback=(env_fallback, ["ACI_USE_SSL"])),
- validate_certs=dict(type="bool", default=True, fallback=(env_fallback, ["ACI_VALIDATE_CERTS"])),
+ timeout=dict(type="int", fallback=(env_fallback, ["ACI_TIMEOUT"])),
+ use_proxy=dict(type="bool", fallback=(env_fallback, ["ACI_USE_PROXY"])),
+ use_ssl=dict(type="bool", fallback=(env_fallback, ["ACI_USE_SSL"])),
+ validate_certs=dict(type="bool", fallback=(env_fallback, ["ACI_VALIDATE_CERTS"])),
output_path=dict(type="str", fallback=(env_fallback, ["ACI_OUTPUT_PATH"])),
)
@@ -252,6 +260,66 @@ def route_control_profile_spec():
)
+def destination_epg_spec():
+ return dict(
+ tenant=dict(type="str", required=True, aliases=["tenant_name"]),
+ ap=dict(type="str", required=True, aliases=["ap_name", "app_profile", "app_profile_name"]),
+ epg=dict(type="str", required=True, aliases=["epg_name"]),
+ source_ip=dict(type="str", required=True),
+ destination_ip=dict(type="str", required=True),
+ span_version=dict(type="str", choices=["version_1", "version_2"]),
+ version_enforced=dict(type="bool"),
+ flow_id=dict(type="int"),
+ ttl=dict(type="int"),
+ mtu=dict(type="int"),
+ dscp=dict(
+ type="str",
+ choices=[
+ "CS0",
+ "CS1",
+ "CS2",
+ "CS3",
+ "CS4",
+ "CS5",
+ "CS6",
+ "CS7",
+ "EF",
+ "VA",
+ "AF11",
+ "AF12",
+ "AF13",
+ "AF21",
+ "AF22",
+ "AF23",
+ "AF31",
+ "AF32",
+ "AF33",
+ "AF41",
+ "AF42",
+ "AF43",
+ "unspecified",
+ ],
+ ),
+ )
+
+
+def ospf_spec():
+ return dict(
+ area_cost=dict(type="int"),
+ area_ctrl=dict(type="list", elements="str", choices=["redistribute", "summary", "suppress-fa", "unspecified"]),
+ area_id=dict(type="str"),
+ area_type=dict(type="str", choices=["nssa", "regular", "stub"]),
+ description=dict(type="str", aliases=["descr"]),
+ multipod_internal=dict(type="str", choices=["no", "yes"]),
+ name_alias=dict(type="str"),
+ )
+
+
+def integrate_url(httpapi_url, local_path):
+ parse_url = urlparse(httpapi_url)
+ return {"protocol": parse_url.scheme, "host": parse_url.netloc, "path": local_path}
+
+
class ACIModule(object):
def __init__(self, module):
self.module = module
@@ -259,6 +327,7 @@ class ACIModule(object):
self.result = dict(changed=False)
self.headers = dict()
self.child_classes = set()
+ self.connection = None
# error output
self.error = dict(code=None, text=None)
@@ -277,9 +346,11 @@ class ACIModule(object):
self.obj_filter = None
self.method = None
self.path = None
+ self.parent_path = None
self.response = None
self.status = None
self.url = None
+ self.httpapi_logs = list()
# aci_rest output
self.imdata = None
@@ -288,21 +359,30 @@ class ACIModule(object):
# Ensure protocol is set
self.define_protocol()
+ # Set Connection plugin
+ self.set_connection()
+
if self.module._debug:
self.module.warn("Enable debug output because ANSIBLE_DEBUG was set.")
self.params["output_level"] = "debug"
+ if self.params.get("port") is not None:
+ self.base_url = "{protocol}://{host}:{port}".format_map(self.params)
+ else:
+ self.base_url = "{protocol}://{host}".format_map(self.params)
+
if self.params.get("private_key"):
# Perform signature-based authentication, no need to log on separately
if not HAS_CRYPTOGRAPHY and not HAS_OPENSSL:
self.module.fail_json(msg="Cannot use signature-based authentication because cryptography (preferred) or pyopenssl are not available")
elif self.params.get("password") is not None:
self.module.warn("When doing ACI signatured-based authentication, providing parameter 'password' is not required")
- elif self.params.get("password"):
- # Perform password-based authentication, log on using password
- self.login()
- else:
- self.module.fail_json(msg="Either parameter 'password' or 'private_key' is required for authentication")
+ elif self.connection is None:
+ if self.params.get("password"):
+ # Perform password-based authentication, log on using password
+ self.login()
+ else:
+ self.module.fail_json(msg="Either parameter 'password' or 'private_key' is required for authentication")
def boolean(self, value, true="yes", false="no"):
"""Return an acceptable value back"""
@@ -335,39 +415,26 @@ class ACIModule(object):
"""Set protocol based on use_ssl parameter"""
# Set protocol for further use
- self.params["protocol"] = "https" if self.params.get("use_ssl", True) else "http"
+ self.params["protocol"] = "https" if self.params.get("use_ssl") or self.params.get("use_ssl") is None else "http"
- def define_method(self):
- """Set method based on state parameter"""
-
- # Set method for further use
- state_map = dict(absent="delete", present="post", query="get")
- self.params["method"] = state_map.get(self.params.get("state"))
+ def set_connection(self):
+ if self.connection is None and self.module._socket_path:
+ self.connection = Connection(self.module._socket_path)
def login(self):
"""Log in to APIC"""
# Perform login request
- if self.params.get("port") is not None:
- url = "%(protocol)s://%(host)s:%(port)s/api/aaaLogin.json" % self.params
- else:
- url = "%(protocol)s://%(host)s/api/aaaLogin.json" % self.params
+ url = "{0}/api/aaaLogin.json".format(self.base_url)
payload = {
"aaaUser": {
"attributes": {
- "name": self.params.get("username"),
+ "name": "admin" if self.params.get("username") is None else self.params.get("username"),
"pwd": self.params.get("password"),
}
}
}
- resp, auth = fetch_url(
- self.module,
- url,
- data=json.dumps(payload),
- method="POST",
- timeout=self.params.get("timeout"),
- use_proxy=self.params.get("use_proxy"),
- )
+ resp, auth = self.api_call("POST", url, data=json.dumps(payload), return_response=True)
# Handle APIC response
if auth.get("status") != 200:
@@ -376,10 +443,10 @@ class ACIModule(object):
try:
# APIC error
self.response_json(auth["body"])
- self.fail_json(msg="Authentication failed: %(code)s %(text)s" % self.error)
+ self.fail_json(msg="Authentication failed: {code} {text}".format_map(self.error))
except KeyError:
# Connection error
- self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % auth)
+ self.fail_json(msg="Connection failed for {url}. {msg}".format_map(auth))
# Retain cookie for later use
self.headers["Cookie"] = resp.headers.get("Set-Cookie")
@@ -418,7 +485,7 @@ class ACIModule(object):
with open(self.params.get("private_key"), permission) as fh:
private_key_content = fh.read()
except Exception:
- self.module.fail_json(msg="Cannot open private key file '%(private_key)s'." % self.params)
+ self.module.fail_json(msg="Cannot open private key file '{private_key}'.".format_map(self.params))
try:
if HAS_CRYPTOGRAPHY:
sig_key = serialization.load_pem_private_key(
@@ -429,26 +496,28 @@ class ACIModule(object):
else:
sig_key = load_privatekey(FILETYPE_PEM, private_key_content)
except Exception:
- self.module.fail_json(msg="Cannot load private key file '%(private_key)s'." % self.params)
+ self.module.fail_json(msg="Cannot load private key file '{private_key}'.".format_map(self.params))
if self.params.get("certificate_name") is None:
self.params["certificate_name"] = os.path.basename(os.path.splitext(self.params.get("private_key"))[0])
else:
- self.module.fail_json(msg="Provided private key '%(private_key)s' does not appear to be a private key." % self.params)
+ self.module.fail_json(
+ msg="Provided private key {private_key} does not appear to be a private key or provided file does not exist.".format_map(self.params)
+ )
if self.params.get("certificate_name") is None:
- self.params["certificate_name"] = self.params.get("username")
+ self.params["certificate_name"] = "admin" if self.params.get("username") is None else self.params.get("username")
# NOTE: ACI documentation incorrectly adds a space between method and path
sig_request = method + path + payload
if HAS_CRYPTOGRAPHY:
sig_signature = sig_key.sign(sig_request.encode(), padding.PKCS1v15(), hashes.SHA256())
else:
sig_signature = sign(sig_key, sig_request, "sha256")
- sig_dn = "uni/userext/user-%(username)s/usercert-%(certificate_name)s" % self.params
+ sig_dn = "uni/userext/user-{username}/usercert-{certificate_name}".format_map(self.params)
self.headers["Cookie"] = (
"APIC-Certificate-Algorithm=v1.0; "
- + "APIC-Certificate-DN=%s; " % sig_dn
+ + "APIC-Certificate-DN={0}; ".format(sig_dn)
+ "APIC-Certificate-Fingerprint=fingerprint; "
- + "APIC-Request-Signature=%s" % to_native(base64.b64encode(sig_signature))
+ + "APIC-Request-Signature={0}".format(to_native(base64.b64encode(sig_signature)))
)
def response_json(self, rawoutput):
@@ -457,7 +526,7 @@ class ACIModule(object):
jsondata = json.loads(rawoutput)
except Exception as e:
# Expose RAW output for troubleshooting
- self.error = dict(code=-1, text="Unable to parse output as JSON, see 'raw' output. %s" % e)
+ self.error = dict(code=-1, text="Unable to parse output as JSON, see 'raw' output. {0}".format(e))
self.result["raw"] = rawoutput
return
@@ -479,7 +548,7 @@ class ACIModule(object):
xmldata = cobra.data(xml)
except Exception as e:
# Expose RAW output for troubleshooting
- self.error = dict(code=-1, text="Unable to parse output as XML, see 'raw' output. %s" % e)
+ self.error = dict(code=-1, text="Unable to parse output as XML, see 'raw' output. {0}".format(e))
self.result["raw"] = rawoutput
return
@@ -487,7 +556,7 @@ class ACIModule(object):
self.imdata = xmldata.get("imdata", {}).get("children")
if self.imdata is None:
self.imdata = dict()
- self.totalCount = int(xmldata.get("imdata", {}).get("attributes", {}).get("totalCount"))
+ self.totalCount = int(xmldata.get("imdata", {}).get("attributes", {}).get("totalCount", -1))
# Handle possible APIC error information
self.response_error()
@@ -502,100 +571,6 @@ class ACIModule(object):
except (AttributeError, IndexError, KeyError):
pass
- def request(self, path, payload=None):
- """Perform a REST request"""
-
- # Ensure method is set (only do this once)
- self.define_method()
- self.path = path
-
- if self.params.get("port") is not None:
- self.url = "%(protocol)s://%(host)s:%(port)s/" % self.params + path.lstrip("/")
- else:
- self.url = "%(protocol)s://%(host)s/" % self.params + path.lstrip("/")
-
- # Sign and encode request as to APIC's wishes
- if self.params.get("private_key"):
- self.cert_auth(path=path, payload=payload)
-
- # Perform request
- resp, info = fetch_url(
- self.module,
- self.url,
- data=payload,
- headers=self.headers,
- method=self.params.get("method").upper(),
- timeout=self.params.get("timeout"),
- use_proxy=self.params.get("use_proxy"),
- )
-
- self.response = info.get("msg")
- self.status = info.get("status")
-
- # Handle APIC response
- if info.get("status") != 200:
- try:
- # APIC error
- self.response_json(info["body"])
- self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error)
- except KeyError:
- # Connection error
- self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info)
-
- self.response_json(resp.read())
-
- def query(self, path):
- """Perform a query with no payload"""
-
- self.path = path
-
- if self.params.get("port") is not None:
- self.url = "%(protocol)s://%(host)s:%(port)s/" % self.params + path.lstrip("/")
- else:
- self.url = "%(protocol)s://%(host)s/" % self.params + path.lstrip("/")
-
- # Sign and encode request as to APIC's wishes
- if self.params.get("private_key"):
- self.cert_auth(path=path, method="GET")
-
- # Perform request
- resp, query = fetch_url(
- self.module,
- self.url,
- data=None,
- headers=self.headers,
- method="GET",
- timeout=self.params.get("timeout"),
- use_proxy=self.params.get("use_proxy"),
- )
-
- # Handle APIC response
- if query.get("status") != 200:
- self.response = query.get("msg")
- self.status = query.get("status")
- try:
- # APIC error
- self.response_json(query["body"])
- self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error)
- except KeyError:
- # Connection error
- self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % query)
-
- query = json.loads(resp.read())
-
- return json.dumps(query.get("imdata"), sort_keys=True, indent=2) + "\n"
-
- def request_diff(self, path, payload=None):
- """Perform a request, including a proper diff output"""
- self.result["diff"] = dict()
- self.result["diff"]["before"] = self.query(path)
- self.request(path, payload=payload)
- # TODO: Check if we can use the request output for the 'after' diff
- self.result["diff"]["after"] = self.query(path)
-
- if self.result.get("diff", {}).get("before") != self.result.get("diff", {}).get("after"):
- self.result["changed"] = True
-
# TODO: This could be designed to update existing keys
def update_qs(self, params):
"""Append key-value pairs to self.filter_string"""
@@ -832,15 +807,7 @@ class ACIModule(object):
self.child_classes = set(child_classes)
if subclass_5 is not None:
- self._construct_url_6(
- root_class,
- subclass_1,
- subclass_2,
- subclass_3,
- subclass_4,
- subclass_5,
- config_only,
- )
+ self._construct_url_6(root_class, subclass_1, subclass_2, subclass_3, subclass_4, subclass_5, config_only)
elif subclass_4 is not None:
self._construct_url_5(root_class, subclass_1, subclass_2, subclass_3, subclass_4, config_only)
elif subclass_3 is not None:
@@ -878,6 +845,7 @@ class ACIModule(object):
if self.module.params.get("state") in ("absent", "present"):
# State is absent or present
self.path = "api/mo/uni/{0}.json".format(obj_rn)
+ self.parent_path = "api/mo/uni.json"
if config_only:
self.update_qs({"rsp-prop-include": "config-only"})
self.obj_filter = obj_filter
@@ -904,6 +872,7 @@ class ACIModule(object):
if self.module.params.get("state") in ("absent", "present"):
# State is absent or present
self.path = "api/mo/uni/{0}/{1}.json".format(parent_rn, obj_rn)
+ self.parent_path = "api/mo/uni/{0}.json".format(parent_rn)
if config_only:
self.update_qs({"rsp-prop-include": "config-only"})
self.obj_filter = obj_filter
@@ -941,6 +910,7 @@ class ACIModule(object):
if self.module.params.get("state") in ("absent", "present"):
# State is absent or present
self.path = "api/mo/uni/{0}/{1}/{2}.json".format(root_rn, parent_rn, obj_rn)
+ self.parent_path = "api/mo/uni/{0}/{1}.json".format(root_rn, parent_rn)
if config_only:
self.update_qs({"rsp-prop-include": "config-only"})
self.obj_filter = obj_filter
@@ -1010,6 +980,7 @@ class ACIModule(object):
if self.module.params.get("state") in ("absent", "present"):
# State is absent or present
self.path = "api/mo/uni/{0}/{1}/{2}/{3}.json".format(root_rn, sec_rn, parent_rn, obj_rn)
+ self.parent_path = "api/mo/uni/{0}/{1}/{2}.json".format(root_rn, sec_rn, parent_rn)
if config_only:
self.update_qs({"rsp-prop-include": "config-only"})
self.obj_filter = obj_filter
@@ -1065,6 +1036,7 @@ class ACIModule(object):
if self.module.params.get("state") in ("absent", "present"):
# State is absent or present
self.path = "api/mo/uni/{0}/{1}/{2}/{3}/{4}.json".format(root_rn, ter_rn, sec_rn, parent_rn, obj_rn)
+ self.parent_path = "api/mo/uni/{0}/{1}/{2}/{3}.json".format(root_rn, ter_rn, sec_rn, parent_rn)
if config_only:
self.update_qs({"rsp-prop-include": "config-only"})
self.obj_filter = obj_filter
@@ -1182,37 +1154,9 @@ class ACIModule(object):
if not self.existing:
return
-
elif not self.module.check_mode:
# Sign and encode request as to APIC's wishes
- if self.params["private_key"]:
- self.cert_auth(method="DELETE")
-
- resp, info = fetch_url(
- self.module,
- self.url,
- headers=self.headers,
- method="DELETE",
- timeout=self.params.get("timeout"),
- use_proxy=self.params.get("use_proxy"),
- )
-
- self.response = info.get("msg")
- self.status = info.get("status")
- self.method = "DELETE"
-
- # Handle APIC response
- if info.get("status") == 200:
- self.result["changed"] = True
- self.response_json(resp.read())
- else:
- try:
- # APIC error
- self.response_json(info["body"])
- self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error)
- except KeyError:
- # Connection error
- self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info)
+ self.api_call("DELETE", self.url, None, return_response=False)
else:
self.result["changed"] = True
self.method = "DELETE"
@@ -1222,7 +1166,6 @@ class ACIModule(object):
This method is used to get the difference between the proposed and existing configurations. Each module
should call the get_existing method before this method, and add the proposed config to the module results
using the module's config parameters. The new config will added to the self.result dictionary.
-
:param aci_class: Type str.
This is the root dictionary key for the MO's configuration body, or the ACI class of the MO.
"""
@@ -1259,7 +1202,6 @@ class ACIModule(object):
"""
This method is used to get the difference between a proposed and existing child configs. The get_nested_config()
method should be used to return the proposed and existing config portions of child.
-
:param child_class: Type str.
The root class (dict key) for the child dictionary.
:param proposed_child: Type dict.
@@ -1284,7 +1226,6 @@ class ACIModule(object):
"""
This method is used to retrieve the updated child configs by comparing the proposed children configs
against the objects existing children configs.
-
:param aci_class: Type str.
This is the root dictionary key for the MO's configuration body, or the ACI class of the MO.
:return: The list of updated child config dictionaries. None is returned if there are no changes to the child
@@ -1338,40 +1279,13 @@ class ACIModule(object):
"""
uri = self.url + self.filter_string
- # Sign and encode request as to APIC's wishes
- if self.params.get("private_key"):
- self.cert_auth(path=self.path + self.filter_string, method="GET")
-
- resp, info = fetch_url(
- self.module,
- uri,
- headers=self.headers,
- method="GET",
- timeout=self.params.get("timeout"),
- use_proxy=self.params.get("use_proxy"),
- )
- self.response = info.get("msg")
- self.status = info.get("status")
- self.method = "GET"
-
- # Handle APIC response
- if info.get("status") == 200:
- self.existing = json.loads(resp.read())["imdata"]
- else:
- try:
- # APIC error
- self.response_json(info["body"])
- self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error)
- except KeyError:
- # Connection error
- self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info)
+ self.api_call("GET", uri, data=None, return_response=False)
@staticmethod
def get_nested_config(proposed_child, existing_children):
"""
This method is used for stiping off the outer layers of the child dictionaries so only the configuration
key, value pairs are returned.
-
:param proposed_child: Type dict.
The dictionary that represents the child config.
:param existing_children: Type list.
@@ -1401,7 +1315,6 @@ class ACIModule(object):
def get_nested_children(proposed_child, existing_children):
"""
This method is used for stiping off the outer layers of the child dictionaries so only the children are returned.
-
:param proposed_child: Type dict.
The dictionary that represents the child config.
:param existing_children: Type list.
@@ -1436,7 +1349,6 @@ class ACIModule(object):
This method is used to dynamically build the proposed configuration dictionary from the config related parameters
passed into the module. All values that were not passed values from the playbook task will be removed so as to not
inadvertently change configurations.
-
:param aci_class: Type str
This is the root dictionary key for the MO's configuration body, or the ACI class of the MO.
:param class_config: Type dict
@@ -1474,7 +1386,7 @@ class ACIModule(object):
if children:
self.proposed[aci_class].update(dict(children=children))
- def post_config(self):
+ def post_config(self, parent_class=None):
"""
This method is used to handle the logic when the modules state is equal to present. The method only pushes a change if
the object has differences than what exists on the APIC, and if check_mode is False. A successful change will mark the
@@ -1484,35 +1396,14 @@ class ACIModule(object):
return
elif not self.module.check_mode:
# Sign and encode request as to APIC's wishes
- if self.params.get("private_key"):
- self.cert_auth(method="POST", payload=json.dumps(self.config))
-
- resp, info = fetch_url(
- self.module,
- self.url,
- data=json.dumps(self.config),
- headers=self.headers,
- method="POST",
- timeout=self.params.get("timeout"),
- use_proxy=self.params.get("use_proxy"),
- )
-
- self.response = info.get("msg")
- self.status = info.get("status")
- self.method = "POST"
-
- # Handle APIC response
- if info.get("status") == 200:
- self.result["changed"] = True
- self.response_json(resp.read())
- else:
- try:
- # APIC error
- self.response_json(info["body"])
- self.fail_json(msg="APIC Error %(code)s: %(text)s" % self.error)
- except KeyError:
- # Connection error
- self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info)
+ url = self.url
+ if parent_class is not None:
+ if self.params.get("port") is not None:
+ url = "{protocol}://{host}:{port}/{path}".format(path=self.parent_path, **self.module.params)
+ else:
+ url = "{protocol}://{host}/{path}".format(path=self.parent_path, **self.module.params)
+ self.config = {parent_class: {"attributes": {}, "children": [self.config]}}
+ self.api_call("POST", url, json.dumps(self.config), return_response=False)
else:
self.result["changed"] = True
self.method = "POST"
@@ -1539,6 +1430,8 @@ class ACIModule(object):
self.result["response"] = self.response
self.result["status"] = self.status
self.result["url"] = self.url
+ if self.httpapi_logs is not None:
+ self.result["httpapi_logs"] = self.httpapi_logs
if self.stdout:
self.result["stdout"] = self.stdout
@@ -1567,12 +1460,13 @@ class ACIModule(object):
if self.error.get("code") is not None and self.error.get("text") is not None:
self.result["error"] = self.error
+ if self.stdout:
+ self.result["stdout"] = self.stdout
+
if "state" in self.params:
if self.params.get("state") in ("absent", "present"):
if self.params.get("output_level") in ("debug", "info"):
self.result["previous"] = self.existing
- if self.stdout:
- self.result["stdout"] = self.stdout
# Return the gory details when we need it
if self.params.get("output_level") == "debug":
@@ -1589,6 +1483,8 @@ class ACIModule(object):
self.result["response"] = self.response
self.result["status"] = self.status
self.result["url"] = self.url
+ if self.httpapi_logs is not None:
+ self.result["httpapi_logs"] = self.httpapi_logs
if "state" in self.params:
if self.params.get("output_level") in ("debug", "info"):
@@ -1600,7 +1496,7 @@ class ACIModule(object):
def dump_json(self):
if self.params.get("state") in ("absent", "present"):
- dn_path = (self.url).split("/mo/")[-1]
+ dn_path = self.url.rsplit("/mo/", maxsplit=1)[-1]
if dn_path[-5:] == ".json":
dn_path = dn_path[:-5]
mo = {}
@@ -1622,3 +1518,63 @@ class ACIModule(object):
with open(output_path, "a") as output_file:
if self.result.get("changed") is True:
json.dump([mo], output_file)
+
+ def parsed_url_path(self, url):
+ if not HAS_URLPARSE:
+ self.fail_json(msg="urlparse is not installed")
+ parse_result = urlparse(url)
+ if parse_result.query == "":
+ return parse_result.path
+ else:
+ return parse_result.path + "?" + parse_result.query
+
+ def api_call(self, method, url, data=None, return_response=False):
+ resp = None
+ if self.connection is not None:
+ self.connection.set_params(self.params)
+ info = self.connection.send_request(method, self.parsed_url_path(url), data)
+ self.url = "{protocol}://{host}/{path}".format_map(integrate_url(info.get("url"), self.path))
+ self.error = info.get("error")
+ self.httpapi_logs.extend(self.connection.pop_messages())
+ else:
+ if self.params.get("private_key"):
+ self.cert_auth(path=self.parsed_url_path(url), payload=data, method=method)
+ resp, info = fetch_url(
+ self.module,
+ url,
+ data=data,
+ headers=self.headers,
+ method=method,
+ timeout=30 if self.params.get("timeout") is None else self.params.get("timeout"),
+ use_proxy=True if self.params.get("use_proxy") is None else self.params.get("use_proxy"),
+ )
+
+ self.response = info.get("msg")
+ self.status = info.get("status")
+ self.method = method
+
+ if return_response:
+ return resp, info
+ else:
+ # Handle APIC response
+ if info.get("status") == 200:
+ if method == "POST" or method == "DELETE":
+ self.result["changed"] = True
+ try:
+ if method == "GET":
+ self.existing = json.loads(resp.read())["imdata"]
+ else:
+ self.response_json(resp.read())
+ except AttributeError:
+ if method == "GET":
+ self.existing = json.loads(info.get("body"))["imdata"]
+ else:
+ self.response_json(info.get("body"))
+ else:
+ try:
+ # APIC error
+ self.response_json(info["body"])
+ self.fail_json(msg="APIC Error {code}: {text}".format_map(self.error))
+ except KeyError:
+ # Connection error
+ self.fail_json(msg="Connection failed for {url}. {msg}".format_map(info))
diff --git a/ansible_collections/cisco/aci/plugins/module_utils/constants.py b/ansible_collections/cisco/aci/plugins/module_utils/constants.py
index 72d7585a5..165b63431 100644
--- a/ansible_collections/cisco/aci/plugins/module_utils/constants.py
+++ b/ansible_collections/cisco/aci/plugins/module_utils/constants.py
@@ -1,3 +1,100 @@
VALID_IP_PROTOCOLS = ["eigrp", "egp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp", "unspecified"]
-FILTER_PORT_MAPPING = {"443": "https", "25": "smtp", "80": "http", "53": "dns", "22": "ssh", "110": "pop3", "554": "rtsp", "20": "ftpData", "ftp": "ftpData"}
+FILTER_PORT_MAPPING = {"443": "https", "25": "smtp", "80": "http", "53": "dns", "110": "pop3", "554": "rtsp", "20": "ftpData", "ftp": "ftpData"}
+
+VALID_ETHER_TYPES = ["arp", "fcoe", "ip", "ipv4", "ipv6", "mac_security", "mpls_ucast", "trill", "unspecified"]
+
+# mapping dicts are used to normalize the proposed data to what the APIC expects, which will keep diffs accurate
+ARP_FLAG_MAPPING = dict(arp_reply="reply", arp_request="req", unspecified="unspecified")
+
+# ICMPv4 Types Mapping
+ICMP4_MAPPING = dict(
+ dst_unreachable="dst-unreach", echo="echo", echo_reply="echo-rep", src_quench="src-quench", time_exceeded="time-exceeded", unspecified="unspecified"
+)
+
+# ICMPv6 Types Mapping
+ICMP6_MAPPING = dict(
+ dst_unreachable="dst-unreach",
+ echo_request="echo-req",
+ echo_reply="echo-rep",
+ neighbor_advertisement="nbr-advert",
+ neighbor_solicitation="nbr-solicit",
+ redirect="redirect",
+ time_exceeded="time-exceeded",
+ unspecified="unspecified",
+)
+
+TCP_FLAGS = dict(acknowledgment="ack", established="est", finish="fin", reset="rst", synchronize="syn", unspecified="unspecified")
+
+SUBNET_CONTROL_MAPPING = {"nd_ra_prefix": "nd", "no_default_gateway": "no-default-gateway", "querier_ip": "querier", "unspecified": ""}
+SUBNET_CONTROL_MAPPING_BD_SUBNET = {"nd_ra": "nd", "no_gw": "no-default-gateway", "querier_ip": "querier", "unspecified": ""}
+
+NODE_TYPE_MAPPING = {"tier_2": "tier-2-leaf", "remote": "remote-leaf-wan", "virtual": "virtual", "unspecified": "unspecified"}
+
+SPAN_DIRECTION_MAP = {"incoming": "in", "outgoing": "out", "both": "both"}
+
+MATCH_TYPE_MAPPING = {"all": "All", "at_least_one": "AtleastOne", "at_most_one": "AtmostOne", "none": "None"}
+
+IPV4_REGEX = r"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$"
+
+VM_PROVIDER_MAPPING = dict(
+ cloudfoundry="CloudFoundry",
+ kubernetes="Kubernetes",
+ microsoft="Microsoft",
+ openshift="OpenShift",
+ openstack="OpenStack",
+ redhat="Redhat",
+ vmware="VMware",
+)
+
+MATCH_TYPE_GROUP_MAPPING = {"all": "ALL", "all_in_pod": "ALL_IN_POD", "range": "range"}
+
+MATCH_FC_FILL_PATTERN_MAPPING = {"arbff": "ARBFF", "idle": "IDLE"}
+
+MATCH_FIRMWARE_NODES_TYPE_MAPPING = {
+ "c_apic_patch": "cApicPatch",
+ "catalog": "catalog",
+ "config": "config",
+ "controller": "controller",
+ "controller_patch": "controllerPatch",
+ "plugin": "plugin",
+ "plugin_package": "pluginPackage",
+ "switch": "switch",
+ "switch_patch": "switchPatch",
+ "vpod": "vpod",
+}
+
+MATCH_TRIGGER_MAPPING = {
+ "trigger": "trigger",
+ "trigger_immediate": "trigger-immediate",
+ "triggered": "triggered",
+ "untriggered": "untriggered",
+}
+
+INTERFACE_POLICY_FC_SPEED_LIST = ["auto", "unknown", "2G", "4G", "8G", "16G", "32G"]
+
+MATCH_RUN_MODE_MAPPING = dict(
+ pause_always_between_sets="pauseAlwaysBetweenSets",
+ pause_only_on_failures="pauseOnlyOnFailures",
+ pause_never="pauseNever",
+)
+
+MATCH_NOTIFY_CONDITION_MAPPING = dict(
+ notify_always_between_sets="notifyAlwaysBetweenSets",
+ notify_never="notifyNever",
+ notify_only_on_failures="notifyOnlyOnFailures",
+)
+
+MATCH_SMU_OPERATION_MAPPING = dict(smu_install="smuInstall", smu_uninstall="smuUninstall")
+
+MATCH_SMU_OPERATION_FLAGS_MAPPING = dict(smu_reload_immediate="smuReloadImmediate", smu_reload_skip="smuReloadSkip")
+
+MATCH_BEST_PATH_CONTROL_MAPPING = dict(enable="asPathMultipathRelax", disable="")
+
+MATCH_GRACEFUL_RESTART_CONTROLS_MAPPING = dict(helper="helper", complete="")
+
+EP_LOOP_PROTECTION_ACTION_MAPPING = {"bd": "bd-learn-disable", "port": "port-disable"}
+
+FABRIC_POD_SELECTOR_TYPE_MAPPING = dict(all="ALL", range="range")
+
+TLS_MAPPING = {"tls_v1.0": "TLSv1", "tls_v1.1": "TLSv1.1", "tls_v1.2": "TLSv1.2"}
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py
index 0768e4d0c..7b5c896ad 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py
@@ -153,7 +153,7 @@ EXAMPLES = r"""
state: present
delegate_to: localhost
-- name: Associate an interface access port selector to an Interface Policy Leaf Profile (w/o policy group) (check if this works)
+- name: Associate an interface access port selector to an Interface Policy Leaf Profile (w/o policy group)
cisco.aci.aci_access_port_to_interface_policy_leaf_profile:
host: apic
username: admin
@@ -224,6 +224,38 @@ EXAMPLES = r"""
fex_id: 105
state: present
delegate_to: localhost
+
+- name: Create and Bind Access Port Selector with PC Policy Group
+ cisco.aci.aci_access_port_to_interface_policy_leaf_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ interface_profile: leafintprfname
+ access_port_selector: accessportselectorname
+ policy_group: pcintprftest
+ access_port_selector_name: anstest_pc_accessportselector
+ interface_type: port_channel
+ port_blk: leafportblkname
+ from_port: 13
+ to_port: 13
+ state: present
+ delegate_to: localhost
+
+- name: Create and Bind Access Port Selector with VPC Policy Group
+ cisco.aci.aci_access_port_to_interface_policy_leaf_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ interface_profile: leafintprfname
+ access_port_selector: accessportselectorname
+ policy_group: vpcintprftest
+ access_port_selector_name: anstest_vpc_accessportselector
+ interface_type: vpc
+ port_blk: leafportblkname
+ from_port: 13
+ to_port: 13
+ state: present
+ delegate_to: localhost
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py
index 2da778c75..5b5c10aba 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py
@@ -74,11 +74,13 @@ options:
- The name of application profile.
type: str
required: true
+ aliases: [ ap_name, app_profile, app_profile_name ]
epg:
description:
- The name of the end point group.
type: str
required: true
+ aliases: [ epg_name ]
span_version:
description:
- The SPAN version.
@@ -214,6 +216,7 @@ EXAMPLES = r"""
destination_group: group1
state: query
delegate_to: localhost
+ register: query_result
- name: Query all Access SPAN destination groups
cisco.aci.aci_access_span_dst_group:
@@ -222,6 +225,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
state: query
delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
@@ -330,50 +334,7 @@ url:
"""
from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
-
-
-def destination_epg_spec():
- return dict(
- tenant=dict(type="str", required=True, aliases=["tenant_name"]),
- ap=dict(type="str", required=True),
- epg=dict(type="str", required=True),
- source_ip=dict(type="str", required=True),
- destination_ip=dict(type="str", required=True),
- span_version=dict(type="str", choices=["version_1", "version_2"]),
- version_enforced=dict(type="bool"),
- flow_id=dict(type="int"),
- ttl=dict(type="int"),
- mtu=dict(type="int"),
- dscp=dict(
- type="str",
- choices=[
- "CS0",
- "CS1",
- "CS2",
- "CS3",
- "CS4",
- "CS5",
- "CS6",
- "CS7",
- "EF",
- "VA",
- "AF11",
- "AF12",
- "AF13",
- "AF21",
- "AF22",
- "AF23",
- "AF31",
- "AF32",
- "AF33",
- "AF41",
- "AF42",
- "AF43",
- "unspecified",
- ],
- ),
- )
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec, destination_epg_spec
def access_interface_spec():
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py
index c715027f6..ff716d0ae 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py
@@ -72,6 +72,7 @@ EXAMPLES = r"""
filter_group: group1
state: query
delegate_to: localhost
+ register: query_result
- name: Query all Access SPAN filter groups
cisco.aci.aci_access_span_filter_group:
@@ -80,6 +81,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
state: query
delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py
index 5d5516751..ad7151cdf 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py
@@ -119,6 +119,7 @@ EXAMPLES = r"""
destination_ip: 2.2.2.2
state: query
delegate_to: localhost
+ register: query_result
- name: Query all Access SPAN filter groups
cisco.aci.aci_access_span_filter_group_entry:
@@ -127,6 +128,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
state: query
delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group.py
new file mode 100644
index 000000000..fb6f6a35b
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group.py
@@ -0,0 +1,326 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Akini Ross <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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_access_span_src_group
+short_description: Manage Access SPAN source groups (span:SrcGrp)
+description:
+- Manage SPAN source groups on Cisco ACI fabrics.
+options:
+ source_group:
+ description:
+ - The name of the Access SPAN source group.
+ type: str
+ aliases: [ name, src_group ]
+ description:
+ description:
+ - The description for Access SPAN source group.
+ type: str
+ aliases: [ descr ]
+ admin_state:
+ description:
+ - Enable C(true) or disable C(false) the SPAN sources.
+ - The APIC defaults to C(true) when unset during creation.
+ type: bool
+ filter_group:
+ description:
+ - The name of the Access SPAN filter group to associate with the source group.
+ type: str
+ destination_group:
+ description:
+ - The name of the Access SPAN destination group to associate with the source group.
+ type: str
+ aliases: [ dst_group ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The I(filter_group) and I(destination_group) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_access_span_filter_group) and M(cisco.aci.aci_access_span_dst_group) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_access_span_filter_group
+- module: cisco.aci.aci_access_span_dst_group
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(span:SrcGrp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Create a Access SPAN source group
+ cisco.aci.aci_access_span_src_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ destination_group: my_span_dest_group
+ state: present
+ delegate_to: localhost
+
+- name: Delete a Access SPAN source group
+ cisco.aci.aci_access_span_src_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ state: absent
+ delegate_to: localhost
+
+- name: Query all Access SPAN source groups
+ cisco.aci.aci_access_span_src_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Access SPAN source group
+ cisco.aci.aci_access_span_src_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ source_group=dict(type="str", aliases=["name", "src_group"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ admin_state=dict(type="bool"),
+ filter_group=dict(type="str"),
+ destination_group=dict(type="str", aliases=["dst_group"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["source_group"]],
+ ["state", "present", ["source_group", "destination_group"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ source_group = module.params.get("source_group")
+ description = module.params.get("description")
+ admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled")
+ filter_group = module.params.get("filter_group")
+ destination_group = module.params.get("destination_group")
+ state = module.params.get("state")
+ name_alias = module.params.get("name_alias")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="infra",
+ aci_rn="infra",
+ ),
+ subclass_1=dict(
+ aci_class="spanSrcGrp",
+ aci_rn="srcgrp-{0}".format(source_group),
+ module_object=source_group,
+ target_filter={"name": source_group},
+ ),
+ child_classes=["spanSpanLbl", "spanRsSrcGrpToFilterGrp"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ # Create new child configs payload
+ filter_group_tdn = "uni/infra/filtergrp-{0}".format(filter_group)
+ child_configs = [{"spanSpanLbl": {"attributes": {"name": destination_group}}}]
+ if filter_group:
+ child_configs.append({"spanRsSrcGrpToFilterGrp": {"attributes": {"tDn": filter_group_tdn}}})
+
+ # Validate if existing and remove child objects when do not match provided configuration
+ if isinstance(aci.existing, list) and len(aci.existing) > 0:
+ for child in aci.existing[0].get("spanSrcGrp", {}).get("children", {}):
+ if child.get("spanRsSrcGrpToFilterGrp") and child.get("spanRsSrcGrpToFilterGrp").get("attributes").get("tDn") != filter_group_tdn:
+ # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcGrpToFilterGrp is already attached.
+ # A seperate delete request to dn of the spanRsSrcGrpToFilterGrp is needed to remove the object prior to adding to child_configs.
+ # Failed child_config is displayed in below:
+ #
+ # child_configs.append(
+ # {
+ # "spanRsSrcGrpToFilterGrp": {
+ # "attributes": {
+ # "dn": "uni/infra/srcgrp-{0}/rssrcGrpToFilterGrp".format(source_group),
+ # "status": "deleted",
+ # }
+ # }
+ # }
+ # )
+ aci.api_call("DELETE", "/api/mo/uni/infra/srcgrp-{0}/rssrcGrpToFilterGrp.json".format(source_group))
+ elif child.get("spanSpanLbl") and child.get("spanSpanLbl").get("attributes").get("name") != destination_group:
+ child_configs.append(
+ {
+ "spanSpanLbl": {
+ "attributes": {
+ "dn": "uni/infra/srcgrp-{0}/spanlbl-{1}".format(source_group, child.get("spanSpanLbl").get("attributes").get("name")),
+ "status": "deleted",
+ }
+ }
+ }
+ )
+
+ aci.payload(
+ aci_class="spanSrcGrp",
+ class_config=dict(adminSt=admin_state, descr=description, name=source_group, nameAlias=name_alias),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="spanSrcGrp")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src.py
new file mode 100644
index 000000000..f8ff4349e
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src.py
@@ -0,0 +1,442 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Akini Ross <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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_access_span_src_group_src
+short_description: Manage Access SPAN sources (span:Src)
+description:
+- Manage Access SPAN sources on Cisco ACI fabrics.
+options:
+ description:
+ description:
+ - The description for Access SPAN source.
+ type: str
+ aliases: [ descr ]
+ source_group:
+ description:
+ - The name of the Access SPAN source group.
+ type: str
+ aliases: [ src_group ]
+ source:
+ description:
+ - The name of the Access SPAN source.
+ type: str
+ aliases: [ name, src ]
+ direction:
+ description:
+ - The direction of the SPAN source.
+ - The APIC defaults to C(both) when unset during creation.
+ type: str
+ choices: [ incoming, outgoing, both ]
+ filter_group:
+ description:
+ - The name of the Access SPAN filter group to associate with the source.
+ type: str
+ drop_packets:
+ description:
+ - Enable SPAN for only dropped packets.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ epg:
+ description:
+ - The SPAN source EPG details.
+ - The I(epg) and I(routed_outside) cannot be configured simultaneously.
+ type: dict
+ suboptions:
+ tenant:
+ description:
+ - The name of the SPAN source Tenant.
+ type: str
+ required: true
+ aliases: [ tenant_name ]
+ ap:
+ description:
+ - The name of the SPAN source AP.
+ type: str
+ required: true
+ aliases: [ ap_name, app_profile, app_profile_name ]
+ epg:
+ description:
+ - The name of the SPAN source EPG.
+ type: str
+ required: true
+ aliases: [ epg_name ]
+ routed_outside:
+ description:
+ - The Routed Outside details.
+ - The I(epg) and I(routed_outside) cannot be configured simultaneously.
+ type: dict
+ suboptions:
+ tenant:
+ description:
+ - The name of the SPAN source Tenant.
+ type: str
+ aliases: [ tenant_name ]
+ l3out:
+ description:
+ - The name of the SPAN source L3Out.
+ type: str
+ aliases: [ l3out_name ]
+ encap:
+ description:
+ - The VLAN associated with this Routed Outside.
+ type: int
+ aliases: [ vlan, vlan_id, encap_id ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The I(filter_group) and I(source_group) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_access_span_filter_group) and M(cisco.aci.aci_access_span_src_group) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_access_span_filter_group
+- module: cisco.aci.aci_access_span_src_group
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_ap
+- module: cisco.aci.aci_epg
+- module: cisco.aci.aci_l3out
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(span:Src).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Create a Access SPAN source
+ cisco.aci.aci_access_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ state: present
+ delegate_to: localhost
+
+- name: Delete a Access SPAN source
+ cisco.aci.aci_access_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ state: absent
+ delegate_to: localhost
+
+- name: Query all Access SPAN sources
+ cisco.aci.aci_access_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Access SPAN source
+ cisco.aci.aci_access_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import SPAN_DIRECTION_MAP
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ source=dict(type="str", aliases=["name", "src"]), # Not required for querying all objects
+ direction=dict(type="str", choices=list(SPAN_DIRECTION_MAP.keys())),
+ filter_group=dict(type="str"),
+ drop_packets=dict(type="bool"),
+ epg=dict(
+ type="dict",
+ options=dict(
+ epg=dict(type="str", required=True, aliases=["epg_name"]),
+ ap=dict(type="str", required=True, aliases=["ap_name", "app_profile", "app_profile_name"]),
+ tenant=dict(type="str", required=True, aliases=["tenant_name"]),
+ ),
+ ),
+ routed_outside=dict(
+ type="dict",
+ options=dict(
+ encap=dict(type="int", aliases=["vlan", "vlan_id", "encap_id"]),
+ l3out=dict(type="str", aliases=["l3out_name"]),
+ tenant=dict(type="str", aliases=["tenant_name"]),
+ ),
+ ),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["source_group", "source"]],
+ ["state", "present", ["source_group", "source"]],
+ ],
+ mutually_exclusive=[
+ ("epg", "routed_outside"),
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ description = module.params.get("description")
+ source_group = module.params.get("source_group")
+ source = module.params.get("source")
+ direction = module.params.get("direction")
+ filter_group = module.params.get("filter_group")
+ drop_packets = module.params.get("drop_packets")
+ epg = module.params.get("epg")
+ routed_outside = module.params.get("routed_outside")
+ state = module.params.get("state")
+ name_alias = module.params.get("name_alias")
+
+ if filter_group and drop_packets:
+ module.fail_json(msg="Setting 'drop_packets' to 'true' is not allowed when 'filter_group' is configured on the source.")
+ elif epg and drop_packets:
+ module.fail_json(msg="Setting 'drop_packets' to 'true' is not allowed when 'epg' is configured on the source.")
+ elif routed_outside and drop_packets:
+ module.fail_json(msg="Setting 'drop_packets' to 'true' is not allowed when 'routed_outside' is configured on the source.")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="infra",
+ aci_rn="infra",
+ ),
+ subclass_1=dict(
+ aci_class="spanSrcGrp",
+ aci_rn="srcgrp-{0}".format(source_group),
+ module_object=source_group,
+ target_filter={"name": source_group},
+ ),
+ subclass_2=dict(
+ aci_class="spanSrc",
+ aci_rn="src-{0}".format(source),
+ module_object=source,
+ target_filter={"name": source},
+ ),
+ child_classes=["spanRsSrcToFilterGrp", "spanRsSrcToEpg", "spanRsSrcToL3extOut"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ # Create new child configs payload
+ child_configs = []
+ filter_group_tdn = epg_dn = l3ext_out_dn = None
+
+ if filter_group:
+ filter_group_tdn = "uni/infra/filtergrp-{0}".format(filter_group)
+ child_configs.append({"spanRsSrcToFilterGrp": {"attributes": {"tDn": filter_group_tdn}}})
+ if epg:
+ epg_dn = "uni/tn-{0}/ap-{1}/epg-{2}".format(epg.get("tenant"), epg.get("ap"), epg.get("epg"))
+ child_configs.append({"spanRsSrcToEpg": {"attributes": {"tDn": epg_dn}}})
+ elif routed_outside:
+ # encap is set to unknown when not provided to ensure change is executed and detected properly on update
+ encap = "vlan-{0}".format(routed_outside.get("encap")) if routed_outside.get("encap") else "unknown"
+ if routed_outside.get("tenant") and routed_outside.get("l3out"):
+ l3ext_out_dn = "uni/tn-{0}/out-{1}".format(routed_outside.get("tenant"), routed_outside.get("l3out"))
+ else:
+ # tDn is set to "" when not provided to ensure change is executed and detected properly on update
+ l3ext_out_dn = ""
+ child_configs.append({"spanRsSrcToL3extOut": {"attributes": {"encap": encap, "tDn": l3ext_out_dn}}})
+
+ # Validate if existing and remove child objects when do not match provided configuration
+ if isinstance(aci.existing, list) and len(aci.existing) > 0:
+ # Commented validate code to avoid making additional API request which is handled by APIC error
+ # Keeping for informational purposes
+ # Validate drop_packets are set on parent correctly
+ # if aci.api_call("GET", "{0}/rssrcGrpToFilterGrp.json".format(source_group_path)) != [] and drop_packets:
+ # module.fail_json(msg="It is not allowed to configure 'drop_packets: true' when a filter group is configured on the source group.")
+
+ source_path = "/api/mo/uni/infra/srcgrp-{0}/src-{1}".format(source_group, source)
+ for child in aci.existing[0].get("spanSrc", {}).get("children", {}):
+ if child.get("spanRsSrcToFilterGrp") and child.get("spanRsSrcToFilterGrp").get("attributes").get("tDn") != filter_group_tdn:
+ # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcGrpToFilterGrp is already attached.
+ # A seperate delete request to dn of the spanRsSrcGrpToFilterGrp is needed to remove the object prior to adding to child_configs.
+ # Failed child_config is displayed in below:
+ #
+ # child_configs.append(
+ # {
+ # "spanRsSrcGrpToFilterGrp": {
+ # "attributes": {
+ # "dn": "uni/infra/srcgrp-{0}/src-{1}/rssrcGrpToFilterGrp".format(source_group, source),
+ # "status": "deleted",
+ # }
+ # }
+ # }
+ # )
+ aci.api_call("DELETE", "{0}/rssrcToFilterGrp.json".format(source_path))
+ elif child.get("spanRsSrcToEpg") and child.get("spanRsSrcToEpg").get("attributes").get("tDn") != epg_dn:
+ # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcToEpg is already attached.
+ aci.api_call("DELETE", "{0}/rssrcToEpg.json".format(source_path))
+ elif child.get("spanRsSrcToL3extOut") and child.get("spanRsSrcToL3extOut").get("attributes").get("tDn") != l3ext_out_dn:
+ # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcToL3extOut is already attached.
+ aci.api_call("DELETE", "{0}/rssrcToL3extOut.json".format(source_path))
+
+ aci.payload(
+ aci_class="spanSrc",
+ class_config=dict(
+ descr=description,
+ name=source,
+ dir=SPAN_DIRECTION_MAP.get(direction),
+ spanOnDrop=aci.boolean(drop_packets),
+ nameAlias=name_alias,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="spanSrc")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src_path.py
new file mode 100644
index 000000000..e99970905
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_src_group_src_path.py
@@ -0,0 +1,360 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Akini Ross <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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_access_span_src_group_src_path
+short_description: Manage Access SPAN source paths (span:RsSrcToPathEp)
+description:
+- Manage Access SPAN source paths on Cisco ACI fabrics.
+options:
+ source_group:
+ description:
+ - The name of the Access SPAN source group.
+ type: str
+ aliases: [ src_group ]
+ source:
+ description:
+ - The name of the Access SPAN source.
+ type: str
+ aliases: [ src ]
+ pod:
+ description:
+ - The pod id of the source access path.
+ type: int
+ aliases: [ pod_id, pod_number ]
+ nodes:
+ description:
+ - The node of the source access path.
+ - Provide one node to the list for port, direct_port_channel or vpc_component_pc types.
+ - Provide two nodes to the list for virtual_port_channel type.
+ type: list
+ elements: int
+ aliases: [ node_ids ]
+ path_ep:
+ description:
+ - The path of the source access path.
+ - When path is of type port a interface like C(eth1/7) must be provided.
+ - When path is of type direct_port_channel the name of a pc interface policy group like C(test_PolGrp) must be provided.
+ - When path is of type virtual_port_channel or vpc_component_pc the name of a vpc interface policy group like C(test_vPC_PolGrp) must be provided.
+ type: 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
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+
+notes:
+- The I(source_group) and I(source) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_access_span_src_group) and M(cisco.aci.aci_access_span_src_group_src) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_access_span_src_group
+- module: cisco.aci.aci_access_span_src_group_src
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(span:RsSrcToPathEp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Create a Access SPAN source path of type Port
+ cisco.aci.aci_access_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ nodes:
+ - 101
+ path_ep: eth1/1
+ state: present
+ delegate_to: localhost
+
+- name: Create a Access SPAN source path of type Direct Port Channel
+ cisco.aci.aci_access_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ nodes:
+ - 101
+ path_ep: test_PolGrp
+ state: present
+ delegate_to: localhost
+
+- name: Create a Access SPAN source path of type VPC component PC
+ cisco.aci.aci_access_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ nodes:
+ - 101
+ path_ep: test_vPC_PolGrp
+ state: present
+ delegate_to: localhost
+
+- name: Create a Access SPAN source path of type Virtual Port Channel
+ cisco.aci.aci_access_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ nodes:
+ - 101
+ - 102
+ path_ep: test_vPC_PolGrp
+ state: present
+ delegate_to: localhost
+
+- name: Delete a Access SPAN source path
+ cisco.aci.aci_access_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ nodes:
+ - 101
+ path_ep: test_vPC_PolGrp
+ state: absent
+ delegate_to: localhost
+
+- name: Query all Access SPAN source paths
+ cisco.aci.aci_access_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Access SPAN source path
+ cisco.aci.aci_access_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ nodes:
+ - 101
+ path_ep: test_vPC_PolGrp
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(
+ source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects
+ source=dict(type="str", aliases=["src"]), # Not required for querying all objects
+ pod=dict(type="int", aliases=["pod_id", "pod_number"]), # Not required for querying all objects
+ nodes=dict(type="list", elements="int", aliases=["node_ids"]), # Not required for querying all objects
+ path_ep=dict(type="str"), # Not required for querying all objects
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["source_group", "source", "pod", "nodes", "path_ep"]],
+ ["state", "present", ["source_group", "source", "pod", "nodes", "path_ep"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ source_group = module.params.get("source_group")
+ source = module.params.get("source")
+ pod = module.params.get("pod")
+ nodes = module.params.get("nodes")
+ path_ep = module.params.get("path_ep")
+ state = module.params.get("state")
+
+ tdn = None
+ if nodes:
+ if len(nodes) == 1:
+ tdn = "topology/pod-{0}/paths-{1}/pathep-[{2}]".format(pod, nodes[0], path_ep)
+ elif len(nodes) == 2:
+ tdn = "topology/pod-{0}/protpaths-{1}/pathep-[{2}]".format(pod, "-".join([str(node) for node in nodes]), path_ep)
+ else:
+ module.fail_json(msg="{0} nodes have been provided, where a maximum of 2 nodes is allowed.".format(len(nodes)))
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="infra",
+ aci_rn="infra",
+ ),
+ subclass_1=dict(
+ aci_class="spanSrcGrp",
+ aci_rn="srcgrp-{0}".format(source_group),
+ module_object=source_group,
+ target_filter={"name": source_group},
+ ),
+ subclass_2=dict(
+ aci_class="spanSrc",
+ aci_rn="src-{0}".format(source),
+ module_object=source,
+ target_filter={"name": source},
+ ),
+ subclass_3=dict(
+ aci_class="spanRsSrcToPathEp",
+ aci_rn="rssrcToPathEp-[{0}]".format(tdn),
+ module_object=tdn,
+ target_filter={"tDn": tdn},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(aci_class="spanRsSrcToPathEp", class_config=dict(tDn=tdn))
+
+ aci.get_diff(aci_class="spanRsSrcToPathEp")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py
index d3a8e9e1b..3977e63e3 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py
@@ -216,16 +216,7 @@ url:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
-
-VM_PROVIDER_MAPPING = dict(
- cloudfoundry="CloudFoundry",
- kubernetes="Kubernetes",
- microsoft="Microsoft",
- openshift="OpenShift",
- openstack="OpenStack",
- redhat="Redhat",
- vmware="VMware",
-)
+from ansible_collections.cisco.aci.plugins.module_utils.constants import VM_PROVIDER_MAPPING
def main():
@@ -236,7 +227,7 @@ def main():
domain=dict(type="str", aliases=["domain_name", "domain_profile"]), # Not required for querying all objects
domain_type=dict(type="str", choices=["fc", "l2dom", "l3dom", "phys", "vmm"], aliases=["type"]), # Not required for querying all objects
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
- vm_provider=dict(type="str", choices=["cloudfoundry", "kubernetes", "microsoft", "openshift", "openstack", "redhat", "vmware"]),
+ vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())),
)
module = AnsibleModule(
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py
index c10db5c8c..1819fe968 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py
@@ -91,6 +91,13 @@ options:
- The name of the Subnet.
type: str
aliases: [ name ]
+ ip_data_plane_learning:
+ description:
+ - Whether IP data plane learning is enabled or disabled.
+ - The APIC defaults to C(enabled) when unset during creation.
+ type: str
+ choices: [ enabled, disabled ]
+ aliases: [ ip_dataplane_learning ]
tenant:
description:
- The name of the Tenant.
@@ -339,15 +346,10 @@ url:
sample: https://10.11.12.13/api/mo/uni/tn-production.json
"""
+import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
-
-SUBNET_CONTROL_MAPPING = dict(
- nd_ra="nd",
- no_gw="no-default-gateway",
- querier_ip="querier",
- unspecified="",
-)
+from ansible_collections.cisco.aci.plugins.module_utils.constants import SUBNET_CONTROL_MAPPING_BD_SUBNET, IPV4_REGEX
def main():
@@ -365,10 +367,11 @@ def main():
route_profile=dict(type="str"),
route_profile_l3_out=dict(type="str"),
scope=dict(type="list", elements="str", choices=["private", "public", "shared"]),
- subnet_control=dict(type="str", choices=["nd_ra", "no_gw", "querier_ip", "unspecified"]),
+ subnet_control=dict(type="str", choices=list(SUBNET_CONTROL_MAPPING_BD_SUBNET.keys())),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
name_alias=dict(type="str"),
+ ip_data_plane_learning=dict(type="str", choices=["enabled", "disabled"], aliases=["ip_dataplane_learning"]),
)
module = AnsibleModule(
@@ -389,9 +392,14 @@ def main():
bd = module.params.get("bd")
gateway = module.params.get("gateway")
mask = module.params.get("mask")
- if mask is not None and mask not in range(0, 129):
- # TODO: split checks between IPv4 and IPv6 Addresses
- module.fail_json(msg="Valid Subnet Masks are 0 to 32 for IPv4 Addresses and 0 to 128 for IPv6 addresses")
+
+ if mask:
+ # Assumption for simplicity of code that when a valid IPv4 address is not provided the intend is IPv6.
+ if re.search(IPV4_REGEX, gateway) and mask not in range(0, 33):
+ module.fail_json(msg="Valid Subnet Masks are 0 to 32 for IPv4 Addresses")
+ elif mask not in range(0, 129):
+ module.fail_json(msg="Valid Subnet Masks are 0 to 128 for IPv6 Addresses")
+
if gateway is not None:
gateway = "{0}/{1}".format(gateway, str(mask))
subnet_name = module.params.get("subnet_name")
@@ -408,9 +416,9 @@ def main():
state = module.params.get("state")
subnet_control = module.params.get("subnet_control")
if subnet_control:
- subnet_control = SUBNET_CONTROL_MAPPING[subnet_control]
+ subnet_control = SUBNET_CONTROL_MAPPING_BD_SUBNET[subnet_control]
name_alias = module.params.get("name_alias")
-
+ ip_data_plane_learning = module.params.get("ip_data_plane_learning")
aci.construct_url(
root_class=dict(
aci_class="fvTenant",
@@ -436,18 +444,23 @@ def main():
aci.get_existing()
if state == "present":
+ class_config = dict(
+ ctrl=subnet_control,
+ descr=description,
+ ip=gateway,
+ name=subnet_name,
+ preferred=preferred,
+ scope=scope,
+ virtual=enable_vip,
+ nameAlias=name_alias,
+ )
+
+ if ip_data_plane_learning:
+ class_config["ipDPLearning"] = ip_data_plane_learning
+
aci.payload(
aci_class="fvSubnet",
- class_config=dict(
- ctrl=subnet_control,
- descr=description,
- ip=gateway,
- name=subnet_name,
- preferred=preferred,
- scope=scope,
- virtual=enable_vip,
- nameAlias=name_alias,
- ),
+ class_config=class_config,
child_configs=[
{"fvRsBDSubnetToProfile": {"attributes": {"tnL3extOutName": route_profile_l3_out, "tnRtctrlProfileName": route_profile}}},
{"fvRsNdPfxPol": {"attributes": {"tnNdPfxPolName": nd_prefix_policy}}},
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py
new file mode 100644
index 000000000..a8b5c0a6a
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py
@@ -0,0 +1,294 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_bgp_best_path_policy
+short_description: Manage BGP Best Path policy (bgp:BestPathCtrlPol)
+description:
+- Manage BGP Best Path policies for Tenants on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ bgp_best_path_policy:
+ description:
+ - The name of the best path policy.
+ type: str
+ aliases: [ bgp_best_path_policy_name, name ]
+ best_path_control:
+ description:
+ - The option to enable/disable to relax AS-Path restriction when choosing multipaths.
+ - When enabled, allow load sharing across providers with different AS paths.
+ - The APIC defaults to C(enable) when unset during creation.
+ type: str
+ choices: [enable, disable]
+ aliases: [as_path_control]
+ description:
+ description:
+ - Description for the bgp protocol profile.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) module can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(bgp:BestPathCtrlPol).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+"""
+
+EXAMPLES = r"""
+- name: Create a BGP best path policy
+ cisco.aci.aci_bgp_best_path_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ bgp_protocol_profile: my_bgp_best_path_policy
+ best_path_control: enable
+ tenant: production
+ state: present
+ delegate_to: localhost
+
+- name: Delete a BGP best path policy
+ cisco.aci.aci_bgp_best_path_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ bgp_protocol_profile: my_bgp_best_path_policy
+ tenant: production
+ state: absent
+ delegate_to: localhost
+
+- name: Query all BGP best path policies
+ cisco.aci.aci_bgp_best_path_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific BGP best path policy
+ cisco.aci.aci_bgp_best_path_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ bgp_protocol_profile: my_bgp_best_path_policy
+ tenant: production
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_BEST_PATH_CONTROL_MAPPING
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ bgp_best_path_policy=dict(type="str", aliases=["bgp_best_path_policy_name", "name"]), # Not required for querying all objects
+ best_path_control=dict(type="str", choices=["enable", "disable"], aliases=["as_path_control"]),
+ description=dict(type="str", aliases=["descr"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["bgp_best_path_policy", "tenant"]],
+ ["state", "present", ["bgp_best_path_policy", "tenant"]],
+ ],
+ )
+
+ bgp_best_path_policy = module.params.get("bgp_best_path_policy")
+ best_path_control = MATCH_BEST_PATH_CONTROL_MAPPING.get(module.params.get("best_path_control"))
+ description = module.params.get("description")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="bgpBestPathCtrlPol",
+ aci_rn="bestpath-{0}".format(bgp_best_path_policy),
+ module_object=bgp_best_path_policy,
+ target_filter={"name": bgp_best_path_policy},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="bgpBestPathCtrlPol",
+ class_config=dict(
+ name=bgp_best_path_policy,
+ ctrl=best_path_control,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="bgpBestPathCtrlPol")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py
new file mode 100644
index 000000000..c8d6c54b0
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py
@@ -0,0 +1,330 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_bgp_timers_policy
+short_description: Manage BGP timers policy (bgp:CtxPol)
+description:
+- Manage BGP timers policies for Tenants on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ bgp_timers_policy:
+ description:
+ - The name of the bgp timers policy.
+ type: str
+ aliases: [ bgp_timers_policy_name, name ]
+ graceful_restart_controls:
+ description:
+ - The property to determine whether the entity functions only as a graceful restart helper or whether graceful restart is enabled completely.
+ - The graceful restart helper option configures the local BGP router to support the graceful restart of a remote BGP peer.
+ - The complete graceful restart option allows BGP graceful restart to be enabled or disable for an individual neighbor.
+ - The APIC defaults to C(helper) when unset during creation.
+ type: str
+ choices: [ helper, complete ]
+ hold_interval:
+ description:
+ - The time period to wait before declaring the neighbor device down.
+ - The APIC defaults to C(180) when unset during creation.
+ type: int
+ keepalive_interval:
+ description:
+ - The interval time between sending keepalive messages.
+ - The APIC defaults to C(60) when unset during creation.
+ type: int
+ max_as_limit:
+ description:
+ - The maximum AS limit, to discard routes that have excessive AS numbers.
+ - The APIC defaults to C(0) when unset during creation.
+ type: int
+ stale_interval:
+ description:
+ - The maximum time that BGP keeps stale routes from the restarting BGP peer.
+ - The APIC defaults to C(default) which is equal to 300 sec when unset during creation.
+ type: int
+ description:
+ description:
+ - Description for the bgp protocol profile.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) module can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(bgp:CtxPol).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+"""
+
+EXAMPLES = r"""
+- name: Create a BGP timers policy
+ cisco.aci.aci_bgp_timers_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ bgp_protocol_profile: my_bgp_timers_policy
+ graceful_restart_controls: complete
+ hold_interval: 360
+ keepalive_interval: 120
+ max_as_limit: 1
+ stale_interval: 600
+ tenant: production
+ state: present
+ delegate_to: localhost
+
+- name: Delete a BGP timers policy
+ cisco.aci.aci_bgp_timers_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ bgp_protocol_profile: my_bgp_timers_policy
+ tenant: production
+ state: absent
+ delegate_to: localhost
+
+- name: Query all BGP timers policies
+ cisco.aci.aci_bgp_timers_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific BGP timers policy
+ cisco.aci.aci_bgp_timers_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ bgp_protocol_profile: my_bgp_timers_policy
+ tenant: production
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_GRACEFUL_RESTART_CONTROLS_MAPPING
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ bgp_timers_policy=dict(type="str", aliases=["bgp_timers_policy_name", "name"]), # Not required for querying all objects
+ graceful_restart_controls=dict(type="str", choices=["helper", "complete"]),
+ hold_interval=dict(type="int"),
+ keepalive_interval=dict(type="int"),
+ max_as_limit=dict(type="int"),
+ stale_interval=dict(type="int"),
+ description=dict(type="str", aliases=["descr"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["bgp_timers_policy", "tenant"]],
+ ["state", "present", ["bgp_timers_policy", "tenant"]],
+ ],
+ )
+
+ bgp_timers_policy = module.params.get("bgp_timers_policy")
+ graceful_restart_controls = MATCH_GRACEFUL_RESTART_CONTROLS_MAPPING.get(module.params.get("graceful_restart_controls"))
+ hold_interval = module.params.get("hold_interval")
+ keepalive_interval = module.params.get("keepalive_interval")
+ max_as_limit = module.params.get("max_as_limit")
+ stale_interval = module.params.get("stale_interval")
+ description = module.params.get("description")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="bgpCtxPol",
+ aci_rn="bgpCtxP-{0}".format(bgp_timers_policy),
+ module_object=bgp_timers_policy,
+ target_filter={"name": bgp_timers_policy},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="bgpCtxPol",
+ class_config=dict(
+ name=bgp_timers_policy,
+ grCtrl=graceful_restart_controls,
+ holdIntvl=hold_interval,
+ kaIntvl=keepalive_interval,
+ maxAsLimit=max_as_limit,
+ staleIntvl=stale_interval,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="bgpCtxPol")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py
index 6ae4d5d55..f2ed66eda 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py
@@ -503,7 +503,6 @@ def main():
if state == "present" or state == "absent":
for interface_config in interface_configs:
pod_id = interface_config.get("pod_id")
- leafs = interface_config.get("leafs")
interface = interface_config.get("interface")
extpaths = interface_config.get("extpaths")
@@ -517,24 +516,21 @@ def main():
if interface_type in ["fex", "fex_vpc", "fex_port_channel"] and extpaths is None:
aci.fail_json(msg="extpaths is required when interface_type is: {0}".format(interface_type))
- if leafs is not None:
- # Process leafs, and support dash-delimited leafs
- leafs = []
- for leaf in interface_config.get("leafs"):
- # Users are likely to use integers for leaf IDs, which would raise an exception when using the join method
- leafs.extend(str(leaf).split("-"))
- if len(leafs) == 1:
- if interface_type in ["vpc", "fex_vpc"]:
- aci.fail_json(msg='A interface_type of "vpc" requires 2 leafs')
- leafs = leafs[0]
- elif len(leafs) == 2:
- if interface_type not in ["vpc", "fex_vpc"]:
- aci.fail_json(
- msg='The interface_types "switch_port", "port_channel", and "fex" do not support using multiple leafs for a single binding'
- )
- leafs = "-".join(leafs)
- else:
- aci.fail_json(msg='The "leafs" parameter must not have more than 2 entries')
+ # Process leafs, and support dash-delimited leafs
+ leafs = []
+ for leaf in interface_config.get("leafs"):
+ # Users are likely to use integers for leaf IDs, which would raise an exception when using the join method
+ leafs.extend(str(leaf).split("-"))
+ if len(leafs) == 1:
+ if interface_type in ["vpc", "fex_vpc"]:
+ aci.fail_json(msg='A interface_type of "vpc" requires 2 leafs')
+ leafs = leafs[0]
+ elif len(leafs) == 2:
+ if interface_type not in ["vpc", "fex_vpc"]:
+ aci.fail_json(msg='The interface_types "switch_port", "port_channel", and "fex" do not support using multiple leafs for a single binding')
+ leafs = "-".join(leafs)
+ else:
+ aci.fail_json(msg='The "leafs" parameter must not have more than 2 entries')
if extpaths is not None:
# Process extpaths, and support dash-delimited extpaths
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py b/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py
index 86ee44d6e..6d14fd46c 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py
@@ -72,6 +72,9 @@ extends_documentation_fragment:
- cisco.aci.aci
- cisco.aci.annotation
+notes:
+- It is strongly recommended to add a pause task after creating a Snapshot.
+- Wait for the Snapshot to be finished before querying, comparing Snapshots or processing to Rollbacks.
seealso:
- module: cisco.aci.aci_config_snapshot
- name: APIC Management Information Model reference
@@ -79,6 +82,7 @@ seealso:
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Jacob McGill (@jmcgill298)
+- Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -92,6 +96,10 @@ EXAMPLES = r"""
state: present
delegate_to: localhost
+- name: Wait for snapshot to be finished before querying
+ pause:
+ seconds: 10
+
- name: Query Existing Snapshots
cisco.aci.aci_config_snapshot:
host: apic
@@ -189,7 +197,6 @@ url:
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_bytes
-from ansible.module_utils.urls import fetch_url
# Optional, only used for rollback preview
try:
@@ -276,11 +283,17 @@ def main():
aci.post_config()
elif state == "preview":
- aci.url = "%(protocol)s://%(host)s/mqapi2/snapshots.diff.xml" % module.params
+ aci.path = "mqapi2/snapshots.diff.xml"
+ preview_params = {"path": aci.path}
+ preview_params.update(module.params.items())
+ if aci.params.get("port") is not None:
+ aci.url = "{protocol}://{host}:{port}/{path}".format_map(preview_params)
+ else:
+ aci.url = "{protocol}://{host}/{path}".format_map(preview_params)
aci.filter_string = (
- "?s1dn=uni/backupst/snapshots-[uni/fabric/configexp-%(export_policy)s]/snapshot-%(snapshot)s&"
- "s2dn=uni/backupst/snapshots-[uni/fabric/configexp-%(compare_export_policy)s]/snapshot-%(compare_snapshot)s"
- ) % module.params
+ "?s1dn=uni/backupst/snapshots-[uni/fabric/configexp-{export_policy}]/snapshot-{snapshot}&"
+ "s2dn=uni/backupst/snapshots-[uni/fabric/configexp-{compare_export_policy}]/snapshot-{compare_snapshot}"
+ ).format_map(module.params)
# Generate rollback comparison
get_preview(aci)
@@ -293,19 +306,23 @@ def get_preview(aci):
This function is used to generate a preview between two snapshots and add the parsed results to the aci module return data.
"""
uri = aci.url + aci.filter_string
- resp, info = fetch_url(
- aci.module, uri, headers=aci.headers, method="GET", timeout=aci.module.params.get("timeout"), use_proxy=aci.module.params.get("use_proxy")
- )
- aci.method = "GET"
- aci.response = info.get("msg")
- aci.status = info.get("status")
+
+ resp, info = aci.api_call("GET", uri, data=None, return_response=True)
# Handle APIC response
if info.get("status") == 200:
- xml_to_json(aci, resp.read())
+ try:
+ xml_to_json(aci, resp.read())
+ except AttributeError:
+ xml_to_json(aci, info.get("body"))
else:
- aci.result["raw"] = resp.read()
- aci.fail_json(msg="Request failed: %(code)s %(text)s (see 'raw' output)" % aci.error)
+ try:
+ # APIC error
+ aci.response_xml(info["body"])
+ aci.fail_json(msg="APIC Error {code}: {text}".format_map(aci.error))
+ except KeyError:
+ # Connection error
+ aci.fail_json(msg="Connection failed for {url}. {msg}".format_map(info))
def xml_to_json(aci, response_data):
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py b/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py
index 020b456b4..bc36a5e62 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py
@@ -64,6 +64,8 @@ notes:
- The APIC does not provide a mechanism for naming the snapshots.
- 'Snapshot files use the following naming structure: ce_<config export policy name>-<yyyy>-<mm>-<dd>T<hh>:<mm>:<ss>.<mss>+<hh>:<mm>.'
- 'Snapshot objects use the following naming structure: run-<yyyy>-<mm>-<dd>T<hh>-<mm>-<ss>.'
+- It is strongly recommended to add a pause task after creating a Snapshot.
+- Wait for the Snapshot to be finished before querying, comparing Snapshots or processing to Rollbacks.
seealso:
- module: cisco.aci.aci_config_rollback
- name: APIC Management Information Model reference
@@ -71,6 +73,7 @@ seealso:
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Jacob McGill (@jmcgill298)
+- Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -85,6 +88,10 @@ EXAMPLES = r"""
description: Backups taken before new configs are applied.
delegate_to: localhost
+- name: Wait for snapshot to be finished before querying
+ pause:
+ seconds: 10
+
- name: Query all Snapshots
cisco.aci.aci_config_snapshot:
host: apic
@@ -272,8 +279,7 @@ def main():
target_filter={"name": export_policy},
),
)
- # Variable set for reuse of correct url in get_existing() triggered from exit_json() after query request
- reset_url = aci.url
+
aci.get_existing()
aci.payload(
@@ -295,13 +301,9 @@ def main():
aci.post_config()
# Query for job information and add to results
- # Change state to query else aci.request() will not execute a GET request but POST
- aci.params["state"] = "query"
- aci.request(path="/api/node/mo/uni/backupst/jobs-[uni/fabric/configexp-{0}].json".format(export_policy))
- aci.result["job_details"] = aci.imdata[0].get("configJobCont", {})
- # Reset state and url to display correct in output and trigger get_existing() function with correct url
- aci.url = reset_url
- aci.params["state"] = "present"
+ path = "api/node/mo/uni/backupst/jobs-[uni/fabric/configexp-{0}].json".format(export_policy)
+ aci.api_call("GET", url="{0}/{1}".format(aci.base_url, path))
+ aci.result["job_details"] = aci.existing[0].get("configJobCont", {})
else:
# Prefix the proper url to export_policy
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py
index a1dfc32e9..9bb8267dd 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, Tim Cragg (@timcragg)
+# Copyright: (c) 2023, Akini Ross (@akinross)
# 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
@@ -20,6 +21,7 @@ options:
tenant:
description:
- Name of an existing tenant.
+ - When tenant is not provided the module will be applied to the global (infra) policy.
type: str
name:
description:
@@ -50,6 +52,7 @@ seealso:
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
"""
EXAMPLES = r"""
@@ -64,6 +67,16 @@ EXAMPLES = r"""
state: present
delegate_to: localhost
+- name: Add a new global (infra) DHCP relay policy
+ cisco.aci.aci_dhcp_relay:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: my_dhcp_relay
+ description: via Ansible
+ state: present
+ delegate_to: localhost
+
- name: Remove a DHCP relay policy
cisco.aci.aci_dhcp_relay:
host: apic
@@ -74,6 +87,15 @@ EXAMPLES = r"""
state: absent
delegate_to: localhost
+- name: Remove a global (infra) DHCP relay policy
+ cisco.aci.aci_dhcp_relay:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: my_dhcp_relay
+ state: absent
+ delegate_to: localhost
+
- name: Query a DHCP relay policy
cisco.aci.aci_dhcp_relay:
host: apic
@@ -85,6 +107,16 @@ EXAMPLES = r"""
delegate_to: localhost
register: query_result
+- name: Query a global (infra) DHCP relay policy
+ cisco.aci.aci_dhcp_relay:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: my_dhcp_relay
+ state: query
+ delegate_to: localhost
+ register: query_result
+
- name: Query all DHCP relay policies in a specific tenant
cisco.aci.aci_dhcp_relay:
host: apic
@@ -94,6 +126,16 @@ EXAMPLES = r"""
state: query
delegate_to: localhost
register: query_result
+
+- name: Query all DHCP relay policies
+ cisco.aci.aci_dhcp_relay:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
"""
RETURN = r"""
@@ -219,8 +261,8 @@ def main():
argument_spec=argument_spec,
supports_check_mode=True,
required_if=[
- ["state", "absent", ["name", "tenant"]],
- ["state", "present", ["name", "tenant"]],
+ ["state", "absent", ["name"]],
+ ["state", "present", ["name"]],
],
)
@@ -230,29 +272,40 @@ def main():
tenant = module.params.get("tenant")
child_classes = ["dhcpRsProv"]
- aci = ACIModule(module)
- aci.construct_url(
- root_class=dict(
+ if tenant is None:
+ root_class = dict(
+ aci_class="dhcpRelayP",
+ aci_rn="infra/relayp-{0}".format(name),
+ module_object=name,
+ target_filter={"name": name},
+ )
+ subclass_1 = None
+ owner = "infra"
+ else:
+ root_class = dict(
aci_class="fvTenant",
aci_rn="tn-{0}".format(tenant),
module_object=tenant,
target_filter={"name": tenant},
- ),
- subclass_1=dict(
+ )
+ subclass_1 = dict(
aci_class="dhcpRelayP",
aci_rn="relayp-{0}".format(name),
module_object=name,
target_filter={"name": name},
- ),
- child_classes=child_classes,
- )
+ )
+ owner = "tenant"
+
+ aci = ACIModule(module)
+
+ aci.construct_url(root_class=root_class, subclass_1=subclass_1, child_classes=child_classes)
aci.get_existing()
if state == "present":
aci.payload(
aci_class="dhcpRelayP",
- class_config=dict(name=name, descr=description, owner="tenant"),
+ class_config=dict(name=name, descr=description, owner=owner),
)
aci.get_diff(aci_class="dhcpRelayP")
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py
index fefebb6ad..efb9e8e54 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2022, Tim Cragg (@timcragg)
+# Copyright: (c) 2023, Akini Ross (@akinross)
# 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
@@ -20,6 +21,7 @@ options:
tenant:
description:
- Name of the tenant the relay_policy is in.
+ - When tenant is not provided the module will be applied to the global (infra) policy.
type: str
relay_policy:
description:
@@ -29,14 +31,14 @@ options:
provider_tenant:
description:
- Name of the tenant the epg or external_epg is in
- - Only required if the epg or external_epg is in a different tenant than the relay_policy
+ - Required when epg or external_epg is in a different tenant than the relay_policy
+ - Required when global (infra) relay_policy is configured with epg or external_epg types
type: str
epg_type:
description:
- Type of EPG the DHCP server is in
type: str
choices: [ epg, l2_external, l3_external, dn ]
- required: true
anp:
description:
- Application Profile the EPG is in.
@@ -93,6 +95,7 @@ seealso:
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
"""
EXAMPLES = r"""
@@ -110,6 +113,20 @@ EXAMPLES = r"""
state: present
delegate_to: localhost
+- name: Add a new Global (infra) DHCP relay App EPG provider
+ cisco.aci.aci_dhcp_relay_provider:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ relay_policy: my_dhcp_relay
+ provider_tenant: Auto-Demo
+ epg_type: epg
+ anp: my_anp
+ epg: my_app_epg
+ dhcp_server_addr: 10.20.30.40
+ state: present
+ delegate_to: localhost
+
- name: Add a new DHCP relay L3out provider
cisco.aci.aci_dhcp_relay_provider:
host: apic
@@ -137,6 +154,19 @@ EXAMPLES = r"""
state: absent
delegate_to: localhost
+- name: Remove a Global (infra) DHCP relay provider
+ cisco.aci.aci_dhcp_relay_provider:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ relay_policy: my_dhcp_relay
+ provider_tenant: Auto-Demo
+ epg_type: epg
+ anp: my_anp
+ epg: my_app_epg
+ state: absent
+ delegate_to: localhost
+
- name: Query a DHCP relay provider
cisco.aci.aci_dhcp_relay_provider:
host: apic
@@ -150,6 +180,39 @@ EXAMPLES = r"""
state: query
delegate_to: localhost
register: query_result
+
+- name: Query a Global (infra) DHCP relay provider
+ cisco.aci.aci_dhcp_relay_provider:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ relay_policy: my_dhcp_relay
+ provider_tenant: Auto-Demo
+ epg_type: epg
+ anp: my_anp
+ epg: my_app_epg
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all DHCP relay providers in a specific tenant
+ cisco.aci.aci_dhcp_relay_provider:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: Auto-Demo
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all DHCP relay providers
+ cisco.aci.aci_dhcp_relay_provider:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
@@ -266,7 +329,7 @@ def main():
argument_spec.update(aci_annotation_spec())
argument_spec.update(
relay_policy=dict(type="str", aliases=["relay_policy_name"]),
- epg_type=dict(type="str", required=True, choices=["epg", "l2_external", "l3_external", "dn"]),
+ epg_type=dict(type="str", choices=["epg", "l2_external", "l3_external", "dn"]),
anp=dict(type="str"),
epg=dict(type="str", aliases=["app_epg"]),
l2out_name=dict(type="str"),
@@ -283,8 +346,8 @@ def main():
argument_spec=argument_spec,
supports_check_mode=True,
required_if=[
- ["state", "absent", ["relay_policy", "tenant"]],
- ["state", "present", ["relay_policy", "tenant"]],
+ ["state", "absent", ["relay_policy", "epg_type"]],
+ ["state", "present", ["relay_policy", "epg_type"]],
["epg_type", "epg", ["anp", "epg"]],
["epg_type", "l2_external", ["l2out_name", "external_epg"]],
["epg_type", "l3_external", ["l3out_name", "external_epg"]],
@@ -322,6 +385,9 @@ def main():
if provider_tenant is None:
provider_tenant = tenant
+ if epg_type is not None and epg_type != "dn" and provider_tenant is None:
+ module.fail_json(msg="provider_tenant is required when epg_type is {0}".format(epg_type))
+
if epg_type == "epg":
tdn = "uni/tn-{0}/ap-{1}/epg-{2}".format(provider_tenant, anp, epg)
elif epg_type == "l2_external":
@@ -330,28 +396,46 @@ def main():
tdn = "uni/tn-{0}/out-{1}/instP-{2}".format(provider_tenant, l3out_name, external_epg)
elif epg_type == "dn":
tdn = dn
+ else:
+ tdn = None
- aci = ACIModule(module)
- aci.construct_url(
- root_class=dict(
+ if tenant is None:
+ root_class = dict(
+ aci_class="dhcpRelayP",
+ aci_rn="infra/relayp-{0}".format(relay_policy),
+ module_object=relay_policy,
+ target_filter={"name": relay_policy},
+ )
+ subclass_1 = dict(
+ aci_class="dhcpRsProv",
+ aci_rn="rsprov-[{0}]".format(tdn),
+ module_object=tdn,
+ target_filter={"tDn": tdn},
+ )
+ subclass_2 = None
+ else:
+ root_class = dict(
aci_class="fvTenant",
aci_rn="tn-{0}".format(tenant),
module_object=tenant,
target_filter={"name": tenant},
- ),
- subclass_1=dict(
+ )
+ subclass_1 = dict(
aci_class="dhcpRelayP",
aci_rn="relayp-{0}".format(relay_policy),
module_object=relay_policy,
target_filter={"name": relay_policy},
- ),
- subclass_2=dict(
+ )
+ subclass_2 = dict(
aci_class="dhcpRsProv",
aci_rn="rsprov-[{0}]".format(tdn),
module_object=tdn,
target_filter={"tDn": tdn},
- ),
- )
+ )
+
+ aci = ACIModule(module)
+
+ aci.construct_url(root_class=root_class, subclass_1=subclass_1, subclass_2=subclass_2)
aci.get_existing()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py
index 1fe5e8638..bf573d208 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py
@@ -237,16 +237,8 @@ url:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import VM_PROVIDER_MAPPING
-VM_PROVIDER_MAPPING = dict(
- cloudfoundry="CloudFoundry",
- kubernetes="Kubernetes",
- microsoft="Microsoft",
- openshift="OpenShift",
- openstack="OpenStack",
- redhat="Redhat",
- vmware="VMware",
-)
POOL_MAPPING = dict(
vlan=dict(
@@ -274,7 +266,7 @@ def main():
pool=dict(type="str", aliases=["pool_name"]), # Not required for querying all objects
pool_allocation_mode=dict(type="str", aliases=["allocation_mode", "mode"], choices=["dynamic", "static"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
- vm_provider=dict(type="str", choices=["cloudfoundry", "kubernetes", "microsoft", "openshift", "openstack", "redhat", "vmware"]),
+ vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())),
)
module = AnsibleModule(
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py
index 28008b712..802f2f3a3 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py
@@ -253,16 +253,7 @@ url:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
-
-VM_PROVIDER_MAPPING = dict(
- cloudfoundry="CloudFoundry",
- kubernetes="Kubernetes",
- microsoft="Microsoft",
- openshift="OpenShift",
- openstack="OpenStack",
- redhat="Redhat",
- vmware="VMware",
-)
+from ansible_collections.cisco.aci.plugins.module_utils.constants import VM_PROVIDER_MAPPING
def main():
@@ -274,7 +265,7 @@ def main():
pool=dict(type="str", aliases=["pool_name", "vlan_pool"]), # Not required for querying all objects
pool_allocation_mode=dict(type="str", required=True, aliases=["allocation_mode", "mode"], choices=["dynamic", "static"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
- vm_provider=dict(type="str", choices=["cloudfoundry", "kubernetes", "microsoft", "openshift", "openstack", "redhat", "vmware"]),
+ vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())),
)
module = AnsibleModule(
@@ -320,7 +311,7 @@ def main():
domain_class = "physDomP"
domain_mo = "uni/phys-{0}".format(domain)
domain_rn = "phys-{0}".format(domain)
- elif domain_type == "vmm":
+ else:
domain_class = "vmmDomP"
domain_mo = "uni/vmmp-{0}/dom-{1}".format(VM_PROVIDER_MAPPING[vm_provider], domain)
domain_rn = "vmmp-{0}/dom-{1}".format(VM_PROVIDER_MAPPING[vm_provider], domain)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py
index 577fc996c..43d34e78d 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py
@@ -380,7 +380,7 @@ def main():
elif pool_type == "vxlan":
if not 5000 <= encap_id <= 16777215:
module.fail_json(msg='vxlan pools must have "range_start" and "range_end" values between 5000 and 16777215')
- elif pool_type == "vsan":
+ else:
if not 1 <= encap_id <= 4093:
module.fail_json(msg='vsan pools must have "range_start" and "range_end" values between 1 and 4093')
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_epg_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_subnet.py
new file mode 100644
index 000000000..67a3dda5d
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_subnet.py
@@ -0,0 +1,470 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard MICOL (@gmicol) <gmicol@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_epg_subnet
+short_description: Manage EPG Subnets (fv:Subnet)
+description:
+- Manage EPG Subnets on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of the Tenant.
+ type: str
+ aliases: [ tenant_name ]
+ ap:
+ description:
+ - Name of an existing application network profile, that will contain the EPGs.
+ type: str
+ aliases: [ app_profile, app_profile_name ]
+ epg:
+ description:
+ - Name of the end point group.
+ type: str
+ aliases: [ epg_name, name ]
+ description:
+ description:
+ - The description for the Subnet.
+ type: str
+ aliases: [ descr ]
+ enable_vip:
+ description:
+ - Determines if the Subnet should be treated as a VIP (virtual IP address).
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ gateway:
+ description:
+ - The IPv4 or IPv6 gateway address for the Subnet.
+ type: str
+ aliases: [ gateway_ip ]
+ mask:
+ description:
+ - The subnet mask for the Subnet.
+ - This is the number associated with CIDR notation.
+ - For IPv4 addresses, accepted values range between C(0) and C(32).
+ - For IPv6 addresses, accepted Values range between C(0) and C(128).
+ type: int
+ aliases: [ subnet_mask ]
+ nd_prefix_policy:
+ description:
+ - The IPv6 Neighbor Discovery Prefix Policy to associate with the Subnet.
+ type: str
+ preferred:
+ description:
+ - Determines if the Subnet is preferred over all available Subnets. Only one Subnet per Address Family (IPv4/IPv6) can be preferred in the Bridge Domain.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ route_profile:
+ description:
+ - The Route Profile to the associate with the Subnet.
+ type: str
+ route_profile_l3out:
+ description:
+ - The L3Out that contains the associated Route Profile.
+ type: str
+ scope:
+ description:
+ - Determines the scope of the Subnet.
+ - The C(private) option only allows communication with hosts in the same VRF.
+ - The C(public) option allows the Subnet to be advertised outside of the ACI Fabric, and allows communication with hosts in other VRFs.
+ - The shared option limits communication to hosts in either the same VRF or the shared VRF.
+ - The value is a list of options, C(private) and C(public) are mutually exclusive, but both can be used with C(shared).
+ - The APIC defaults to C(private) when unset during creation.
+ type: list
+ elements: str
+ choices: [ private, public, shared ]
+ subnet_control:
+ description:
+ - Determines the Subnet's Control State.
+ - The C(querier_ip) option is used to treat the gateway_ip as an IGMP querier source IP.
+ - The C(nd_ra) option is used to treat the gateway_ip address as a Neighbor Discovery Router Advertisement Prefix.
+ - The C(no_gw) option is used to remove default gateway functionality from the gateway address.
+ - The APIC defaults to C(nd_ra) when unset during creation.
+ type: str
+ choices: [ nd_ra_prefix, no_default_gateway, querier_ip, unspecified ]
+ subnet_name:
+ description:
+ - The name of the Subnet.
+ type: str
+ aliases: [ name ]
+ ip_data_plane_learning:
+ description:
+ - Whether IP data plane learning is enabled or disabled.
+ - The APIC defaults to C(enabled) when unset during creation.
+ type: str
+ choices: [ enabled, disabled ]
+ aliases: [ ip_dataplane_learning ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+
+notes:
+- The C(gateway) parameter is the root key used to access the Subnet (not name), so the C(gateway)
+ is required when the state is C(absent) or C(present).
+- The C(tenant), C(ap) and C(epg) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) module, M(cisco.aci.aci_ap) and M(cisco.aci.aci_epg) can be used for these.
+seealso:
+- module: cisco.aci.aci_epg
+- module: cisco.aci.aci_ap
+- module: cisco.aci.aci_tenant
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(fv:Subnet).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard MICOL (@gmicol)
+"""
+
+EXAMPLES = r"""
+- name: Create a subnet
+ cisco.aci.aci_epg_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ ap: intranet
+ epg: web_epg
+ gateway: 10.1.1.1
+ mask: 24
+ state: present
+ delegate_to: localhost
+
+- name: Create a subnet with options
+ cisco.aci.aci_epg_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ ap: intranet
+ epg: web_epg
+ subnet_name: sql
+ gateway: 10.1.2.1
+ mask: 23
+ description: SQL Servers
+ scope: public
+ route_profile_l3out: corp
+ route_profile: corp_route_profile
+ state: present
+ delegate_to: localhost
+
+- name: Update a subnets scope to private and shared
+ cisco.aci.aci_epg_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ ap: intranet
+ epg: web_epg
+ gateway: 10.1.1.1
+ mask: 24
+ scope: [private, shared]
+ state: present
+ delegate_to: localhost
+
+- name: Get all subnets
+ cisco.aci.aci_epg_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+
+- name: Get all subnets of specific gateway in specified tenant
+ cisco.aci.aci_epg_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ gateway: 10.1.1.1
+ mask: 24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Get specific subnet
+ cisco.aci.aci_epg_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ ap: intranet
+ epg: web_epg
+ gateway: 10.1.1.1
+ mask: 24
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Delete a subnet
+ cisco.aci.aci_epg_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ ap: intranet
+ epg: web_epg
+ gateway: 10.1.1.1
+ mask: 24
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.constants import SUBNET_CONTROL_MAPPING
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ epg=dict(type="str", aliases=["epg_name", "name"]), # Not required for querying all objects
+ ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ enable_vip=dict(type="bool"),
+ gateway=dict(type="str", aliases=["gateway_ip"]), # Not required for querying all objects
+ mask=dict(type="int", aliases=["subnet_mask"]), # Not required for querying all objects
+ subnet_name=dict(type="str", aliases=["name"]),
+ nd_prefix_policy=dict(type="str"),
+ preferred=dict(type="bool"),
+ route_profile=dict(type="str"),
+ route_profile_l3out=dict(type="str"),
+ scope=dict(type="list", elements="str", choices=["private", "public", "shared"]),
+ subnet_control=dict(type="str", choices=list(SUBNET_CONTROL_MAPPING.keys())),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ ip_data_plane_learning=dict(type="str", choices=["enabled", "disabled"], aliases=["ip_dataplane_learning"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_together=[["gateway", "mask"], ["route_profile_l3out", "route_profile"]],
+ required_if=[
+ ["state", "present", ["epg", "ap", "gateway", "mask", "tenant"]],
+ ["state", "absent", ["epg", "ap", "gateway", "mask", "tenant"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ description = module.params.get("description")
+ enable_vip = aci.boolean(module.params.get("enable_vip"))
+ epg = module.params.get("epg")
+ ap = module.params.get("ap")
+ tenant = module.params.get("tenant")
+ gateway = module.params.get("gateway")
+ mask = module.params.get("mask")
+ if mask is not None and mask not in range(0, 129):
+ # TODO: split checks between IPv4 and IPv6 Addresses
+ module.fail_json(msg="Valid Subnet Masks are 0 to 32 for IPv4 Addresses and 0 to 128 for IPv6 addresses")
+ if gateway is not None:
+ gateway = "{0}/{1}".format(gateway, mask)
+ subnet_name = module.params.get("subnet_name")
+ nd_prefix_policy = module.params.get("nd_prefix_policy")
+ preferred = aci.boolean(module.params.get("preferred"))
+ route_profile = module.params.get("route_profile")
+ route_profile_l3out = module.params.get("route_profile_l3out")
+ scope = module.params.get("scope")
+ ip_data_plane_learning = module.params.get("ip_data_plane_learning")
+
+ if scope is not None:
+ if "private" in scope and "public" in scope:
+ module.fail_json(msg="Parameter 'scope' cannot be both 'private' and 'public', got: %s" % scope)
+ else:
+ scope = ",".join(sorted(scope))
+ state = module.params.get("state")
+ subnet_control = module.params.get("subnet_control")
+ if subnet_control:
+ subnet_control = SUBNET_CONTROL_MAPPING[subnet_control]
+ name_alias = module.params.get("name_alias")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="fvAp",
+ aci_rn="ap-{0}".format(ap),
+ module_object=ap,
+ target_filter={"name": ap},
+ ),
+ subclass_2=dict(
+ aci_class="fvAEPg",
+ aci_rn="epg-{0}".format(epg),
+ module_object=epg,
+ target_filter={"name": epg},
+ ),
+ subclass_3=dict(
+ aci_class="fvSubnet",
+ aci_rn="subnet-[{0}]".format(gateway),
+ module_object=gateway,
+ target_filter={"ip": gateway},
+ ),
+ child_classes=["fvRsBDSubnetToProfile", "fvRsNdPfxPol"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="fvSubnet",
+ class_config=dict(
+ ctrl=subnet_control,
+ descr=description,
+ ip=gateway,
+ name=subnet_name,
+ preferred=preferred,
+ scope=scope,
+ virtual=enable_vip,
+ nameAlias=name_alias,
+ ipDPLearning=ip_data_plane_learning,
+ ),
+ child_configs=[
+ {"fvRsBDSubnetToProfile": {"attributes": {"tnL3extOutName": route_profile_l3out, "tnRtctrlProfileName": route_profile}}},
+ {"fvRsNdPfxPol": {"attributes": {"tnNdPfxPolName": nd_prefix_policy}}},
+ ],
+ )
+
+ aci.get_diff(aci_class="fvSubnet")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py
new file mode 100644
index 000000000..4048187db
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py
@@ -0,0 +1,364 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Sabari Jaganathan <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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_interface_policy_group
+short_description: Manage Fabric Interface Policy Groups (fabric:LePortPGrp, fabric:SpPortPGrp)
+description:
+- Manage Fabric Interface Policy Groups on Cisco ACI fabrics.
+options:
+ name:
+ description:
+ - The name of the Fabric Leaf or Spine Interface Policy Group.
+ type: str
+ aliases: [ policy_group ]
+ description:
+ description:
+ - The description of the Fabric Leaf or Spine Interface Policy Group.
+ type: str
+ aliases: [ descr ]
+ type:
+ description:
+ - The type of the Fabric Leaf or Spine Interface Policy Group.
+ - Use C(leaf) to create a Fabric Leaf Interface Policy Group.
+ - Use C(spine) to create a Fabric Spine Interface Policy Group.
+ type: str
+ aliases: [ policy_group_type ]
+ choices: [ leaf, spine ]
+ required: true
+ dwdm_policy:
+ description:
+ - The name of the DWDM policy to bind to the Fabric Leaf or Spine Interface Policy Group.
+ type: str
+ link_level_policy:
+ description:
+ - The name of the Link Level policy to bind to the Fabric Leaf or Spine Interface Policy Group.
+ type: str
+ link_flap_policy:
+ description:
+ - The name of the Link Flap policy to bind to the Fabric Leaf or Spine Interface Policy Group.
+ type: str
+ l3_interface_policy:
+ description:
+ - The name of the L3 Interface policy to bind to the Fabric Leaf or Spine Interface Policy Group.
+ type: str
+ macsec_policy:
+ description:
+ - The name of the MACSec policy to bind to the Fabric Leaf or Spine Interface Policy Group.
+ type: str
+ monitoring_policy:
+ description:
+ - The name of the Monitoring policy to bind to the Fabric Leaf or Spine Interface Policy Group.
+ type: str
+ transceiver_policy_tdn:
+ description:
+ - The target Dn of the Transceiver policy to bind to the Fabric Leaf or Spine Interface Policy Group.
+ - The Transceiver policy group is only compatible with ACI versions 6.0(2h) and higher.
+ type: 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(fabric:LePortPGrp, fabric:SpPortPGrp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Sabari Jaganathan (@sajagana)
+"""
+
+EXAMPLES = r"""
+- name: Add a Fabric Leaf Policy Group
+ cisco.aci.aci_fabric_interface_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: leaf_policy_group
+ type: leaf
+ state: present
+ delegate_to: localhost
+
+- name: Query a Fabric Leaf Policy Group with name
+ cisco.aci.aci_fabric_interface_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: leaf_policy_group
+ type: leaf
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Fabric Leaf Policy Groups
+ cisco.aci.aci_fabric_interface_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ type: leaf
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Remove a Fabric Leaf Policy Group
+ cisco.aci.aci_fabric_interface_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: leaf_policy_group
+ type: leaf
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ name=dict(type="str", aliases=["policy_group"]),
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ type=dict(type="str", aliases=["policy_group_type"], choices=["leaf", "spine"], required=True),
+ dwdm_policy=dict(type="str"),
+ link_level_policy=dict(type="str"),
+ link_flap_policy=dict(type="str"),
+ l3_interface_policy=dict(type="str"),
+ macsec_policy=dict(type="str"),
+ monitoring_policy=dict(type="str"),
+ transceiver_policy_tdn=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["name"]],
+ ["state", "present", ["name"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ name = module.params.get("name")
+ description = module.params.get("description")
+ state = module.params.get("state")
+ name_alias = module.params.get("name_alias")
+ policy_group_type = module.params.get("type")
+ dwdm_policy = module.params.get("dwdm_policy")
+ link_level_policy = module.params.get("link_level_policy")
+ link_flap_policy = module.params.get("link_flap_policy")
+ l3_interface_policy = module.params.get("l3_interface_policy")
+ macsec_policy = module.params.get("macsec_policy")
+ monitoring_policy = module.params.get("monitoring_policy")
+ transceiver_policy_tdn = module.params.get("transceiver_policy_tdn")
+
+ if policy_group_type == "leaf":
+ policy_group_class_name = "fabricLePortPGrp"
+ policy_group_class_rn = "leportgrp-{0}".format(name)
+ else:
+ policy_group_class_name = "fabricSpPortPGrp"
+ policy_group_class_rn = "spportgrp-{0}".format(name)
+
+ child_classes = [
+ "fabricRsDwdmFabIfPol",
+ "fabricRsFIfPol",
+ "fabricRsFLinkFlapPol",
+ "fabricRsL3IfPol",
+ "fabricRsMacsecFabIfPol",
+ "fabricRsMonIfFabricPol",
+ ]
+
+ if transceiver_policy_tdn is not None:
+ child_classes.append("fabricRsOpticsFabIfPol")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fabric",
+ aci_rn="fabric",
+ ),
+ subclass_1=dict(
+ aci_class="fabricFuncP",
+ aci_rn="funcprof",
+ ),
+ subclass_2=dict(
+ aci_class=policy_group_class_name,
+ aci_rn=policy_group_class_rn,
+ module_object=name,
+ target_filter={"name": name},
+ ),
+ child_classes=child_classes,
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ child_configs = []
+ if dwdm_policy is not None:
+ child_configs.append(dict(fabricRsDwdmFabIfPol=dict(attributes=dict(tnDwdmFabIfPolName=dwdm_policy))))
+ if link_level_policy is not None:
+ child_configs.append(dict(fabricRsFIfPol=dict(attributes=dict(tnFabricFIfPolName=link_level_policy))))
+ if link_flap_policy is not None:
+ child_configs.append(dict(fabricRsFLinkFlapPol=dict(attributes=dict(tnFabricFLinkFlapPolName=link_flap_policy))))
+ if l3_interface_policy is not None:
+ child_configs.append(dict(fabricRsL3IfPol=dict(attributes=dict(tnL3IfPolName=l3_interface_policy))))
+ if macsec_policy is not None:
+ child_configs.append(dict(fabricRsMacsecFabIfPol=dict(attributes=dict(tnMacsecFabIfPolName=macsec_policy))))
+ if monitoring_policy is not None:
+ child_configs.append(dict(fabricRsMonIfFabricPol=dict(attributes=dict(tnMonFabricPolName=monitoring_policy))))
+ if transceiver_policy_tdn is not None:
+ child_configs.append(dict(fabricRsOpticsFabIfPol=dict(attributes=dict(tDn=transceiver_policy_tdn))))
+
+ aci.payload(
+ aci_class=policy_group_class_name,
+ class_config=dict(
+ name=name,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class=policy_group_class_name)
+
+ aci.post_config()
+
+ if state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py
index defa4f783..bd3ac0277 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com>
+# Copyright: (c) 2023, Gaspard Micol <gmicol@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
@@ -46,6 +47,18 @@ options:
type: str
aliases: [ role_name ]
choices: [ leaf, spine, unspecified ]
+ node_type:
+ description:
+ - Type for the new Fabric Node Member.
+ type: str
+ choices: [ tier_2, remote, virtual, unspecified ]
+ remote_leaf_pool_id:
+ description:
+ - External Pool Id of the remote leaf.
+ - I(remote_leaf_pool_id) is incompatible with I(node_type) other than C(remote).
+ - I(remote_leaf_pool_id) is required if I(node_type) is C(remote).
+ type: str
+ aliases: [ pool_id ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -67,6 +80,7 @@ seealso:
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Bruno Calogero (@brunocalogero)
+- Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -207,6 +221,7 @@ url:
"""
from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.constants import NODE_TYPE_MAPPING
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
@@ -222,6 +237,8 @@ def main():
node_id=dict(type="int"), # Not required for querying all objects
pod_id=dict(type="int"),
role=dict(type="str", choices=["leaf", "spine", "unspecified"], aliases=["role_name"]),
+ node_type=dict(type="str", choices=list(NODE_TYPE_MAPPING.keys())),
+ remote_leaf_pool_id=dict(type="str", aliases=["pool_id"]),
serial=dict(type="str", aliases=["serial_number"]), # Not required for querying all objects
switch=dict(type="str", aliases=["name", "switch_name"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
@@ -234,6 +251,7 @@ def main():
required_if=[
["state", "absent", ["node_id", "serial"]],
["state", "present", ["node_id", "serial"]],
+ ["node_type", "remote", ["remote_leaf_pool_id"]],
],
)
@@ -243,10 +261,18 @@ def main():
switch = module.params.get("switch")
description = module.params.get("description")
role = module.params.get("role")
+ node_type = module.params.get("node_type")
+ remote_leaf_pool_id = module.params.get("remote_leaf_pool_id")
state = module.params.get("state")
name_alias = module.params.get("name_alias")
aci = ACIModule(module)
+
+ if node_type != "remote" and remote_leaf_pool_id:
+ module.fail_json(msg="External Pool Id is not compatible with a node type other than 'remote'.")
+
+ node_type = NODE_TYPE_MAPPING.get(node_type)
+
aci.construct_url(
root_class=dict(
aci_class="fabricNodeIdentP",
@@ -267,10 +293,10 @@ def main():
nodeId=node_id,
podId=pod_id,
# NOTE: Originally we were sending 'rn', but now we need 'dn' for idempotency
- # FIXME: Did this change with ACI version ?
dn="uni/controller/nodeidentpol/nodep-{0}".format(serial),
- # rn='nodep-{0}'.format(serial),
role=role,
+ nodeType=node_type,
+ extPoolId=remote_leaf_pool_id,
serial=serial,
nameAlias=name_alias,
),
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node_control.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node_control.py
new file mode 100644
index 000000000..eef1d510b
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node_control.py
@@ -0,0 +1,268 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_node_control
+short_description: Manage Fabric Node Controls (fabric:NodeControl)
+description:
+- Manage Fabric Node Controls on ACI fabrics.
+options:
+ name:
+ description:
+ - The name of the Fabric Node Control.
+ type: str
+ aliases: [ fabric_node_control ]
+ description:
+ description:
+ - The description of the Fabric Node Control.
+ type: str
+ enable_dom:
+ description:
+ - Whether to enable digital optical monitoring (DOM) for the fabric node control.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ feature_selection:
+ description:
+ - The feature selection for the Node Control.
+ - The APIC defaults to C(telemetry) when unset during creation.
+ type: str
+ choices: [ analytics, netflow, telemetry ]
+ aliases: [ feature ]
+ 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
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(fabric:NodeControl).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+
+author:
+- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Create Fabric Node Control
+ cisco.aci.aci_fabric_node_control:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: ans_fab_node_control
+ enable_dom: true
+ feature_selection: netflow
+ state: present
+ delegate_to: localhost
+
+- name: Delete Fabric Node Control
+ cisco.aci.aci_fabric_node_control:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: ans_fab_node_control
+ state: absent
+ delegate_to: localhost
+
+- name: Query Fabric Node Control
+ cisco.aci.aci_fabric_node_control:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: ans_fab_node_control
+ state: query
+ delegate_to: localhost
+
+- name: Query All Fabric Node Controls
+ cisco.aci.aci_fabric_node_control:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ name=dict(type="str", aliases=["fabric_node_control"]),
+ description=dict(type="str"),
+ enable_dom=dict(type="bool"),
+ feature_selection=dict(type="str", choices=["analytics", "netflow", "telemetry"], aliases=["feature"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+ aci = ACIModule(module)
+
+ name = module.params.get("name")
+ description = module.params.get("description")
+ enable_dom = aci.boolean(module.params.get("enable_dom"), "Dom", "None")
+ feature_selection = module.params.get("feature_selection")
+ state = module.params.get("state")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fabricNodeControl",
+ aci_rn="fabric/nodecontrol-{0}".format(name),
+ module_object=name,
+ target_filter={"name": name},
+ ),
+ )
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="fabricNodeControl",
+ class_config=dict(
+ name=name,
+ descr=description,
+ control=enable_dom,
+ featureSel=feature_selection,
+ ),
+ )
+
+ aci.get_diff(aci_class="fabricNodeControl")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py
new file mode 100644
index 000000000..6c95f32df
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py
@@ -0,0 +1,406 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <timcragg@cisco.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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_pod_selector
+short_description: Manage Fabric Pod Selectors (fabric:PodS)
+description:
+- Manage Fabric Pod Selectors on Cisco ACI fabrics.
+options:
+ pod_profile:
+ description:
+ - The name of the Pod Profile that contains the Selector.
+ type: str
+ name:
+ description:
+ - The name of the Pod Selector.
+ type: str
+ aliases: [ selector, pod_selector ]
+ description:
+ description:
+ - The description for the Fabric Pod Selector.
+ type: str
+ aliases: [ descr ]
+ type:
+ description:
+ - The type of the Pod Selector.
+ type: str
+ choices: [ all, range ]
+ blocks:
+ description:
+ - The pod id(s) associated with the Pod Selector.
+ - Existing blocks will be removed when they are not matching provided blocks.
+ - A comma-separated string of pod ids or ranges of pod ids. (ex. 1,3-4)
+ type: str
+ aliases: [ pod_id, pod_id_range ]
+ policy_group:
+ description:
+ - The Fabric Policy Group to bind to this Pod Selector.
+ - Provide an empty string C("") to remove the Fabric Policy Group binding.
+ type: 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(pod_profile) must exist before using this module in your playbook.
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(fabric:PodS).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
+"""
+
+# TODO add to notes section when cisco.aci.aci_pod_profile is implemented:
+# The M(cisco.aci.aci_pod_profile) module can be used for this.
+
+EXAMPLES = r"""
+- name: Add a new pod selector with type all
+ cisco.aci.aci_fabric_pod_selector:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ pod_profile: default
+ name: ans_pod_selector
+ type: all
+ policy_group: ansible_policy_group
+ state: present
+ delegate_to: localhost
+
+- name: Add a new pod selector with type range and blocks
+ cisco.aci.aci_fabric_pod_selector:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ pod_profile: default
+ name: ans_pod_selector
+ type: range
+ blocks: 1,3-4
+ policy_group: ansible_policy_group
+ state: present
+ delegate_to: localhost
+
+- name: Remove a policy_group from an existing pod selector
+ cisco.aci.aci_fabric_pod_selector:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ pod_profile: default
+ type: all
+ name: ans_pod_selector
+ policy_group: ""
+ state: present
+ delegate_to: localhost
+
+- name: Remove a pod selector type all
+ cisco.aci.aci_fabric_pod_selector:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ pod_profile: default
+ type: all
+ name: ans_pod_selector
+ state: absent
+ delegate_to: localhost
+
+- name: Remove a pod selector type range
+ cisco.aci.aci_fabric_pod_selector:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ pod_profile: default
+ type: range
+ name: ans_pod_selector
+ state: absent
+ delegate_to: localhost
+
+- name: Query a pod selector
+ cisco.aci.aci_fabric_pod_selector:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ pod_profile: default
+ name: ans_pod_selector
+ type: all
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all pod selectors
+ cisco.aci.aci_fabric_pod_selector:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+import binascii
+import os
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import FABRIC_POD_SELECTOR_TYPE_MAPPING
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ description=dict(type="str", aliases=["descr"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ pod_profile=dict(type="str"),
+ name=dict(type="str", aliases=["selector", "pod_selector"]),
+ type=dict(type="str", choices=["all", "range"]),
+ blocks=dict(type="str", aliases=["pod_id", "pod_id_range"]),
+ policy_group=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["pod_profile", "name", "type"]],
+ ["state", "present", ["pod_profile", "name", "type"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ name_alias = module.params.get("name_alias")
+ pod_profile = module.params.get("pod_profile")
+ name = module.params.get("name")
+ policy_group = module.params.get("policy_group")
+ description = module.params.get("description")
+ selector_type = FABRIC_POD_SELECTOR_TYPE_MAPPING.get(module.params.get("type"))
+ blocks = [i.strip().split("-") for i in module.params.get("blocks").split(",")] if module.params.get("blocks") else []
+ state = module.params.get("state")
+
+ if state == "present" and selector_type == "range" and not blocks:
+ module.fail_json(msg="The 'blocks' parameter is required when the 'type' parameter is set to 'range' and 'state' parameter is set to 'present'.")
+
+ child_classes = ["fabricRsPodPGrp", "fabricPodBlk"]
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fabricPodP",
+ aci_rn="fabric/podprof-{0}".format(pod_profile),
+ module_object=pod_profile,
+ target_filter={"name": pod_profile},
+ ),
+ subclass_1=dict(
+ aci_class="fabricPodS",
+ aci_rn="pods-{0}-typ-{1}".format(name, selector_type),
+ module_object=name,
+ target_filter={"name": name, "type": selector_type},
+ ),
+ child_classes=child_classes,
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ child_configs = []
+
+ if policy_group is not None:
+ child_configs.append(
+ {
+ "fabricRsPodPGrp": {
+ "attributes": {"status": "deleted"} if policy_group == "" else {"tDn": "uni/fabric/funcprof/podpgrp-{0}".format(policy_group)}
+ }
+ }
+ )
+
+ if blocks:
+ if isinstance(aci.existing, list) and len(aci.existing) > 0:
+ for child in aci.existing[0].get("fabricPodS", {}).get("children", {}):
+ if child.get("fabricPodBlk"):
+ from_ = child.get("fabricPodBlk").get("attributes").get("from_")
+ to_ = child.get("fabricPodBlk").get("attributes").get("to_")
+ if [from_, to_] in blocks:
+ blocks.remove([from_, to_])
+ elif (from_ == to_) and [from_] in blocks:
+ blocks.remove([from_])
+ else:
+ child_configs.append(
+ {
+ "fabricPodBlk": {
+ "attributes": {
+ "dn": "uni/fabric/podprof-{0}/pods-{1}-typ-{2}/podblk-{3}".format(
+ pod_profile, name, selector_type, child.get("fabricPodBlk").get("attributes").get("name")
+ ),
+ "status": "deleted",
+ }
+ }
+ }
+ )
+
+ for block in blocks:
+ child_configs.append(
+ {
+ "fabricPodBlk": {
+ "attributes": {
+ "name": binascii.b2a_hex(os.urandom(8)).decode("utf-8"),
+ "from_": block[0],
+ "to_": block[1] if len(block) > 1 else block[0],
+ }
+ }
+ }
+ )
+
+ aci.payload(
+ aci_class="fabricPodS",
+ class_config=dict(
+ name=name,
+ descr=description,
+ nameAlias=name_alias,
+ type=selector_type,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="fabricPodS")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py
index c0297d846..364bbc7f5 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py
@@ -9,16 +9,12 @@ __metaclass__ = type
ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
-DOCUMENTATION = """
+DOCUMENTATION = r"""
---
module: aci_fabric_scheduler
-
-short_description: This modules creates ACI schedulers.
-
-
+short_description: This module creates ACI schedulers (trig:SchedP)
description:
- - With the module you can create schedule policies that can be a shell, onetime execution or recurring
-
+- With the module you can create schedule policies that can be a shell, one-time execution or recurring.
options:
name:
description:
@@ -32,44 +28,44 @@ options:
aliases: [ descr ]
recurring:
description:
- - If you want to make the Scheduler a recurring it would be a "True" and for a
- oneTime execution it would be "False". For a shell just exclude this option from
- the task
+ - If you want to make the scheduler a recurring operation, it should be set C(True) and for a one-time execution it should be C(False).
+ - For a shell, just exclude this option from the task.
type: bool
windowname:
description:
- - This is the name for your what recurring or oneTime execution
+ - The name of the schedule window.
+ - This is mandatory for the child class object B(trig:AbsWinddowP)
type: str
concurCap:
description:
- - This is the amount of devices that can be executed on at a time
+ - The amount of devices that can be executed on at a time.
type: int
maxTime:
description:
- - This is the amount MAX amount of time a process can be executed
+ - The maximum amount of time a process can be executed.
type: str
date:
description:
- - This is the date and time that the scheduler will execute
+ - The date and time that the scheduler will execute.
type: str
hour:
description:
- - This set the hour of execution
+ - The number of hours of execution.
type: int
minute:
description:
- - This sets the minute of execution, used in conjunction with hour
+ - The number of minutes of execution, used in conjunction with hour.
type: int
day:
description:
- - This sets the day when execution will take place
+ - The number of days when execution will take place.
type: str
- default: "every-day"
- choices: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday', 'even-day', 'odd-day', 'every-day']
+ default: every-day
+ choices: [ Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday, even-day, odd-day, every-day ]
state:
description:
- - Use C(present) or C(absent) for adding or removing.
- - Use C(query) for listing an object or multiple objects.
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
type: str
default: present
choices: [ absent, present, query ]
@@ -82,56 +78,63 @@ extends_documentation_fragment:
- cisco.aci.annotation
- cisco.aci.owner
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(trig:SchedP).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- - Steven Gerhart (@sgerhart)
+- Steven Gerhart (@sgerhart)
"""
EXAMPLES = r"""
- - name: Simple Scheduler (Empty)
- cisco.aci.aci_fabric_scheduler:
- host: "{{ inventory_hostname }}"
- username: "{{ user }}"
- password: "{{ pass }}"
- validate_certs: false
- name: simpleScheduler
- state: present
- - name: Remove Simple Scheduler
- cisco.aci.aci_fabric_scheduler:
- host: "{{ inventory_hostname }}"
- username: "{{ user }}"
- password: "{{ pass }}"
- validate_certs: false
- name: simpleScheduler
- state: absent
- - name: One Time Scheduler
- cisco.aci.aci_fabric_scheduler:
- host: "{{ inventory_hostname }}"
- username: "{{ user }}"
- password: "{{ pass }}"
- validate_certs: false
- name: OneTime
- windowname: OneTime
- recurring: False
- concurCap: 20
- date: "2018-11-20T24:00:00"
- state: present
- - name: Recurring Scheduler
- cisco.aci.aci_fabric_scheduler:
- host: "{{ inventory_hostname }}"
- username: "{{ user }}"
- password: "{{ pass }}"
- validate_certs: false
- name: Recurring
- windowname: Recurring
- recurring: True
- concurCap: 20
- hour: 13
- minute: 30
- day: Tuesday
- state: present
+- name: Simple Scheduler (Empty)
+ cisco.aci.aci_fabric_scheduler:
+ host: "{{ inventory_hostname }}"
+ username: "{{ user }}"
+ password: "{{ pass }}"
+ validate_certs: false
+ name: simpleScheduler
+ state: present
+
+- name: Remove Simple Scheduler
+ cisco.aci.aci_fabric_scheduler:
+ host: "{{ inventory_hostname }}"
+ username: "{{ user }}"
+ password: "{{ pass }}"
+ validate_certs: false
+ name: simpleScheduler
+ state: absent
+
+- name: One Time Scheduler
+ cisco.aci.aci_fabric_scheduler:
+ host: "{{ inventory_hostname }}"
+ username: "{{ user }}"
+ password: "{{ pass }}"
+ validate_certs: false
+ name: OneTime
+ windowname: OneTime
+ recurring: False
+ concurCap: 20
+ date: "2018-11-20T24:00:00"
+ state: present
+
+- name: Recurring Scheduler
+ cisco.aci.aci_fabric_scheduler:
+ host: "{{ inventory_hostname }}"
+ username: "{{ user }}"
+ password: "{{ pass }}"
+ validate_certs: false
+ name: Recurring
+ windowname: Recurring
+ recurring: True
+ concurCap: 20
+ hour: 13
+ minute: 30
+ day: Tuesday
+ state: present
"""
-RETURN = """
+RETURN = r"""
current:
description: The existing configuration from the APIC after the module has finished
returned: success
@@ -285,6 +288,11 @@ def main():
description = module.params.get("description")
name_alias = module.params.get("name_alias")
+ child_classes = [
+ "trigRecurrWindowP",
+ "trigAbsWindowP",
+ ]
+
if recurring:
child_configs = [
dict(
@@ -324,6 +332,7 @@ def main():
target_filter={"name": name},
module_object=name,
),
+ child_classes=child_classes,
)
aci.get_existing()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_dst_group.py
new file mode 100644
index 000000000..8ae062c3a
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_dst_group.py
@@ -0,0 +1,361 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Akini Ross <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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_span_dst_group
+short_description: Manage Fabric SPAN destination groups (span:DestGrp)
+description:
+- Manage Fabric SPAN destination groups on Cisco ACI fabrics.
+options:
+ destination_group:
+ description:
+ - The name of the Fabric SPAN destination group.
+ type: str
+ aliases: [ name, dst_group ]
+ description:
+ description:
+ - The description of the Fabric SPAN destination group.
+ type: str
+ aliases: [ descr ]
+ destination_epg:
+ description:
+ - The destination end point group.
+ type: dict
+ suboptions:
+ tenant:
+ description:
+ - The name of the tenant.
+ type: str
+ required: true
+ aliases: [ tenant_name ]
+ ap:
+ description:
+ - The name of application profile.
+ type: str
+ required: true
+ aliases: [ ap_name, app_profile, app_profile_name ]
+ epg:
+ description:
+ - The name of the end point group.
+ type: str
+ required: true
+ aliases: [ epg_name ]
+ span_version:
+ description:
+ - The SPAN version.
+ - The APIC defaults to C(version_2) when unset during creation.
+ type: str
+ choices: [ version_1, version_2 ]
+ version_enforced:
+ description:
+ - Enforce SPAN version.
+ type: bool
+ source_ip:
+ description:
+ - The source IP address or prefix.
+ type: str
+ required: true
+ destination_ip:
+ description:
+ - The destination IP address.
+ type: str
+ required: true
+ flow_id:
+ description:
+ - The flow ID of the SPAN packet.
+ - The APIC defaults to C(1) when unset during creation.
+ type: int
+ ttl:
+ description:
+ - The time to live of the SPAN session packets.
+ - The APIC defaults to C(64) when unset during creation.
+ type: int
+ mtu:
+ description:
+ - The MTU truncation size for the packets.
+ - The APIC defaults to C(1518) when unset during creation.
+ type: int
+ dscp:
+ description:
+ - The DSCP value for sending the monitored packets using ERSPAN.
+ - The APIC defaults to C(unspecified) when unset during creation.
+ type: str
+ choices: [ CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43, unspecified ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(span:DestGrp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Add a Fabric SPAN destination group
+ cisco.aci.aci_fabric_span_dst_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ destination_group: group1
+ description: Test span
+ destination_epg:
+ tenant: Test1
+ ap: ap1
+ epg: ep1
+ span_version: version_1
+ version_enforced: false
+ destination_ip: 10.0.0.1
+ source_ip: 10.0.2.1
+ ttl: 2
+ mtu: 1500
+ flow_id: 1
+ dscp: CS1
+ state: present
+ delegate_to: localhost
+
+- name: Remove a Fabric SPAN destination group
+ cisco.aci.aci_fabric_span_dst_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ destination_group: group1
+ state: absent
+ delegate_to: localhost
+
+- name: Query a Fabric SPAN destination group
+ cisco.aci.aci_fabric_span_dst_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ destination_group: group1
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query all Fabric SPAN destination groups
+ cisco.aci.aci_fabric_span_dst_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec, destination_epg_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ destination_group=dict(type="str", aliases=["name", "dst_group"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ destination_epg=dict(type="dict", options=destination_epg_spec()),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["destination_group"]],
+ ["state", "present", ["destination_group", "destination_epg"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ destination_group = module.params.get("destination_group")
+ description = module.params.get("description")
+ destination_epg = module.params.get("destination_epg")
+ state = module.params.get("state")
+ name_alias = module.params.get("name_alias")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fabric",
+ aci_rn="fabric",
+ ),
+ subclass_1=dict(
+ aci_class="spanDestGrp",
+ aci_rn="destgrp-{0}".format(destination_group),
+ module_object=destination_group,
+ target_filter={"name": destination_group},
+ ),
+ child_classes=["spanDest", "spanRsDestEpg", "spanRsDestPathEp"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ attributes = dict(
+ tDn="uni/tn-{0}/ap-{1}/epg-{2}".format(destination_epg.get("tenant"), destination_epg.get("ap"), destination_epg.get("epg")),
+ ip=destination_epg.get("destination_ip"),
+ srcIpPrefix=destination_epg.get("source_ip"),
+ )
+ if destination_epg.get("span_version") is not None:
+ attributes["ver"] = "ver1" if destination_epg.get("span_version") == "version_1" else "ver2"
+ if destination_epg.get("version_enforced") is not None:
+ attributes["verEnforced"] = "yes" if destination_epg.get("version_enforced") else "no"
+ if destination_epg.get("ttl") is not None:
+ attributes["ttl"] = str(destination_epg.get("ttl"))
+ if destination_epg.get("mtu") is not None:
+ attributes["mtu"] = str(destination_epg.get("mtu"))
+ if destination_epg.get("flow_id") is not None:
+ attributes["flowId"] = str(destination_epg.get("flow_id"))
+ if destination_epg.get("dscp") is not None:
+ attributes["dscp"] = destination_epg.get("dscp")
+ span_rs_dest = dict(spanRsDestEpg=dict(attributes=attributes))
+
+ aci.payload(
+ aci_class="spanDestGrp",
+ class_config=dict(name=destination_group, descr=description, nameAlias=name_alias),
+ child_configs=[dict(spanDest=dict(attributes=dict(name=destination_group), children=[span_rs_dest]))],
+ )
+
+ aci.get_diff(aci_class="spanDestGrp")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group.py
new file mode 100644
index 000000000..284ed3677
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group.py
@@ -0,0 +1,300 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Akini Ross <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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_span_src_group
+short_description: Manage Fabric SPAN source groups (span:SrcGrp)
+description:
+- Manage SPAN source groups on Cisco ACI fabrics.
+options:
+ source_group:
+ description:
+ - The name of the Fabric SPAN source group.
+ type: str
+ aliases: [ name, src_group ]
+ description:
+ description:
+ - The description for Fabric SPAN source group.
+ type: str
+ aliases: [ descr ]
+ admin_state:
+ description:
+ - Enable C(true) or disable C(false) the SPAN sources.
+ - The APIC defaults to C(true) when unset during creation.
+ type: bool
+ destination_group:
+ description:
+ - The name of the Fabric SPAN destination group to associate with the source group.
+ type: str
+ aliases: [ dst_group ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The I(destination_group) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_fabric_span_dst_group) module can be used for this.
+seealso:
+- module: cisco.aci.aci_fabric_span_dst_group
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(span:SrcGrp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Create a Fabric SPAN source group
+ cisco.aci.aci_fabric_span_src_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ destination_group: my_span_dest_group
+ state: present
+ delegate_to: localhost
+
+- name: Delete a Fabric SPAN source group
+ cisco.aci.aci_fabric_span_src_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ state: absent
+ delegate_to: localhost
+
+- name: Query all Fabric SPAN source groups
+ cisco.aci.aci_fabric_span_src_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Fabric SPAN source group
+ cisco.aci.aci_fabric_span_src_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ source_group=dict(type="str", aliases=["name", "src_group"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ admin_state=dict(type="bool"),
+ destination_group=dict(type="str", aliases=["dst_group"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["source_group"]],
+ ["state", "present", ["source_group", "destination_group"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ source_group = module.params.get("source_group")
+ description = module.params.get("description")
+ admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled")
+ destination_group = module.params.get("destination_group")
+ state = module.params.get("state")
+ name_alias = module.params.get("name_alias")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fabric",
+ aci_rn="fabric",
+ ),
+ subclass_1=dict(
+ aci_class="spanSrcGrp",
+ aci_rn="srcgrp-{0}".format(source_group),
+ module_object=source_group,
+ target_filter={"name": source_group},
+ ),
+ child_classes=["spanSpanLbl"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ # Create new child configs payload
+ child_configs = [{"spanSpanLbl": {"attributes": {"name": destination_group}}}]
+
+ # Validate if existing and remove child object when is does not match provided configuration
+ if isinstance(aci.existing, list) and len(aci.existing) > 0:
+ for child in aci.existing[0].get("spanSrcGrp", {}).get("children", {}):
+ if child.get("spanSpanLbl") and child.get("spanSpanLbl").get("attributes").get("name") != destination_group:
+ child_configs.append(
+ {
+ "spanSpanLbl": {
+ "attributes": {
+ "dn": "uni/fabric/srcgrp-{0}/spanlbl-{1}".format(source_group, child.get("spanSpanLbl").get("attributes").get("name")),
+ "status": "deleted",
+ }
+ }
+ }
+ )
+
+ aci.payload(
+ aci_class="spanSrcGrp",
+ class_config=dict(adminSt=admin_state, descr=description, name=source_group, nameAlias=name_alias),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="spanSrcGrp")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src.py
new file mode 100644
index 000000000..b4b722e41
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src.py
@@ -0,0 +1,416 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Akini Ross <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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_span_src_group_src
+short_description: Manage Fabric SPAN sources (span:Src)
+description:
+- Manage Fabric SPAN sources on Cisco ACI fabrics.
+options:
+ description:
+ description:
+ - The description for Fabric SPAN source.
+ type: str
+ aliases: [ descr ]
+ source_group:
+ description:
+ - The name of the Fabric SPAN source group.
+ type: str
+ aliases: [ src_group ]
+ source:
+ description:
+ - The name of the Fabric SPAN source.
+ type: str
+ aliases: [ name, src ]
+ direction:
+ description:
+ - The direction of the SPAN source.
+ - The APIC defaults to C(both) when unset during creation.
+ type: str
+ choices: [ incoming, outgoing, both ]
+ drop_packets:
+ description:
+ - Enable SPAN for only dropped packets.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ vrf:
+ description:
+ - The SPAN source VRF details.
+ - The I(vrf) and I(bd) cannot be configured simultaneously.
+ type: dict
+ suboptions:
+ tenant:
+ description:
+ - The name of the SPAN source Tenant.
+ type: str
+ required: true
+ aliases: [ tenant_name ]
+ vrf:
+ description:
+ - The name of the SPAN source VRF.
+ type: str
+ required: true
+ aliases: [ vrf_name ]
+ bd:
+ description:
+ - The SPAN source BD details.
+ - The I(vrf) and I(bd) cannot be configured simultaneously.
+ type: dict
+ suboptions:
+ tenant:
+ description:
+ - The name of the SPAN source Tenant.
+ type: str
+ required: true
+ aliases: [ tenant_name ]
+ bd:
+ description:
+ - The name of the SPAN source BD.
+ type: str
+ required: true
+ aliases: [ bd_name, bridge_domain ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The I(source_group) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_fabric_span_src_group) module can be used for this.
+seealso:
+- module: cisco.aci.aci_fabric_span_src_group
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_vrf
+- module: cisco.aci.aci_bd
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(span:Src).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Create a Fabric SPAN source
+ cisco.aci.aci_fabric_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ state: present
+ delegate_to: localhost
+
+- name: Create a Fabric SPAN source with bd
+ cisco.aci.aci_fabric_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ bd:
+ tenant: my_tenant
+ bd: my_bd
+ state: present
+ delegate_to: localhost
+
+- name: Create a Fabric SPAN source with vrf
+ cisco.aci.aci_fabric_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ vrf:
+ tenant: my_tenant
+ vrf: my_vrf
+ state: present
+ delegate_to: localhost
+
+- name: Delete a Fabric SPAN source
+ cisco.aci.aci_fabric_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ state: absent
+ delegate_to: localhost
+
+- name: Query all Fabric SPAN sources
+ cisco.aci.aci_fabric_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Fabric SPAN source
+ cisco.aci.aci_fabric_span_src_group_src:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import SPAN_DIRECTION_MAP
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ source=dict(type="str", aliases=["name", "src"]), # Not required for querying all objects
+ direction=dict(type="str", choices=list(SPAN_DIRECTION_MAP.keys())),
+ drop_packets=dict(type="bool"),
+ vrf=dict(
+ type="dict",
+ options=dict(
+ vrf=dict(type="str", required=True, aliases=["vrf_name"]),
+ tenant=dict(type="str", required=True, aliases=["tenant_name"]),
+ ),
+ ),
+ bd=dict(
+ type="dict",
+ options=dict(
+ bd=dict(type="str", required=True, aliases=["bd_name", "bridge_domain"]),
+ tenant=dict(type="str", required=True, aliases=["tenant_name"]),
+ ),
+ ),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["source_group", "source"]],
+ ["state", "present", ["source_group", "source"]],
+ ],
+ mutually_exclusive=[
+ ("vrf", "bd"),
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ description = module.params.get("description")
+ source_group = module.params.get("source_group")
+ source = module.params.get("source")
+ direction = module.params.get("direction")
+ drop_packets = module.params.get("drop_packets")
+ vrf = module.params.get("vrf")
+ bd = module.params.get("bd")
+ state = module.params.get("state")
+ name_alias = module.params.get("name_alias")
+
+ if vrf and drop_packets:
+ module.fail_json(msg="It is not allowed to configure 'drop_packets: true' when 'vrf' is configured on the source.")
+ elif bd and drop_packets:
+ module.fail_json(msg="It is not allowed to configure 'drop_packets: true' when 'bd' is configured on the source.")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fabric",
+ aci_rn="fabric",
+ ),
+ subclass_1=dict(
+ aci_class="spanSrcGrp",
+ aci_rn="srcgrp-{0}".format(source_group),
+ module_object=source_group,
+ target_filter={"name": source_group},
+ ),
+ subclass_2=dict(
+ aci_class="spanSrc",
+ aci_rn="src-{0}".format(source),
+ module_object=source,
+ target_filter={"name": source},
+ ),
+ child_classes=["spanRsSrcToCtx", "spanRsSrcToBD"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ # Create new child configs payload
+ child_configs = []
+ vrf_dn = bd_dn = None
+
+ if vrf:
+ vrf_dn = "uni/tn-{0}/ctx-{1}".format(vrf.get("tenant"), vrf.get("vrf"))
+ child_configs.append({"spanRsSrcToCtx": {"attributes": {"tDn": vrf_dn}}})
+ elif bd:
+ bd_dn = "uni/tn-{0}/BD-{1}".format(bd.get("tenant"), bd.get("bd"))
+ child_configs.append({"spanRsSrcToBD": {"attributes": {"tDn": bd_dn}}})
+
+ # Validate if existing and remove child objects when do not match provided configuration
+ if isinstance(aci.existing, list) and len(aci.existing) > 0:
+ source_path = "/api/mo/uni/fabric/srcgrp-{0}/src-{1}".format(source_group, source)
+ for child in aci.existing[0].get("spanSrc", {}).get("children", {}):
+ if child.get("spanRsSrcToCtx") and child.get("spanRsSrcToCtx").get("attributes").get("tDn") != vrf_dn:
+ # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcToCtx is already attached.
+ aci.api_call("DELETE", "{0}/rssrcToCtx.json".format(source_path))
+ elif child.get("spanRsSrcToBD") and child.get("spanRsSrcToBD").get("attributes").get("tDn") != bd_dn:
+ # Appending to child_config list not possible because of APIC Error 103: child (Rn) of class spanRsSrcToBD is already attached.
+ aci.api_call("DELETE", "{0}/rssrcToBD.json".format(source_path))
+
+ aci.payload(
+ aci_class="spanSrc",
+ class_config=dict(
+ descr=description,
+ name=source,
+ dir=SPAN_DIRECTION_MAP.get(direction),
+ spanOnDrop=aci.boolean(drop_packets),
+ nameAlias=name_alias,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="spanSrc")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_node.py
new file mode 100644
index 000000000..513966c13
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_node.py
@@ -0,0 +1,295 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Akini Ross <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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_span_src_group_src_node
+short_description: Manage Fabric SPAN source nodes (span:RsSrcToNode)
+description:
+- Manage Fabric SPAN source nodes on Cisco ACI fabrics.
+- Source nodes are only configurable when the I(source_group) provided has I(drop_packets) set to C(true).
+options:
+ source_group:
+ description:
+ - The name of the Fabric SPAN source group.
+ type: str
+ aliases: [ src_group ]
+ source:
+ description:
+ - The name of the Fabric SPAN source.
+ type: str
+ aliases: [ src ]
+ pod:
+ description:
+ - The pod id of the source access node.
+ type: int
+ aliases: [ pod_id, pod_number ]
+ node:
+ description:
+ - The node id of the source access node.
+ type: int
+ aliases: [ node_id ]
+ 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
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+
+notes:
+- The I(source_group), and I(source) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_fabric_span_src_group) and M(cisco.aci.aci_fabric_span_src_group_src) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_fabric_span_src_group
+- module: cisco.aci.aci_fabric_span_src_group_src
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(span:RsSrcToNode).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Create a Fabric SPAN source node
+ cisco.aci.aci_fabric_span_src_group_src_node:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ node: 101
+ state: present
+ delegate_to: localhost
+
+- name: Delete a Fabric SPAN source node
+ cisco.aci.aci_fabric_span_src_group_src_node:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ node: 101
+ state: absent
+ delegate_to: localhost
+
+- name: Query all Fabric SPAN source nodes
+ cisco.aci.aci_fabric_span_src_group_src_node:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Fabric SPAN source node
+ cisco.aci.aci_fabric_span_src_group_src_node:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ nodes: 101
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(
+ source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects
+ source=dict(type="str", aliases=["src"]), # Not required for querying all objects
+ pod=dict(type="int", aliases=["pod_id", "pod_number"]), # Not required for querying all objects
+ node=dict(type="int", aliases=["node_id"]), # Not required for querying all objects
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["source_group", "source", "pod", "node"]],
+ ["state", "present", ["source_group", "source", "pod", "node"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ source_group = module.params.get("source_group")
+ source = module.params.get("source")
+ pod = module.params.get("pod")
+ node = module.params.get("node")
+ state = module.params.get("state")
+
+ tdn = None
+ if pod and node:
+ tdn = "topology/pod-{0}/node-{1}".format(pod, node)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fabric",
+ aci_rn="fabric",
+ ),
+ subclass_1=dict(
+ aci_class="spanSrcGrp",
+ aci_rn="srcgrp-{0}".format(source_group),
+ module_object=source_group,
+ target_filter={"name": source_group},
+ ),
+ subclass_2=dict(
+ aci_class="spanSrc",
+ aci_rn="src-{0}".format(source),
+ module_object=source,
+ target_filter={"name": source},
+ ),
+ subclass_3=dict(
+ aci_class="spanRsSrcToNode",
+ aci_rn="rssrcToNode-[{0}]".format(tdn),
+ module_object=tdn,
+ target_filter={"tDn": tdn},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(aci_class="spanRsSrcToNode", class_config=dict(tDn=tdn))
+
+ aci.get_diff(aci_class="spanRsSrcToNode")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_path.py
new file mode 100644
index 000000000..041a807ef
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_span_src_group_src_path.py
@@ -0,0 +1,304 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Akini Ross <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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_span_src_group_src_path
+short_description: Manage Fabric SPAN source paths (span:RsSrcToPathEp)
+description:
+- Manage Fabric SPAN source paths on Cisco ACI fabrics.
+options:
+ source_group:
+ description:
+ - The name of the Fabric SPAN source group.
+ type: str
+ aliases: [ src_group ]
+ source:
+ description:
+ - The name of the Fabric SPAN source.
+ type: str
+ aliases: [ src ]
+ pod:
+ description:
+ - The pod id of the source access path.
+ type: int
+ aliases: [ pod_id, pod_number ]
+ node:
+ description:
+ - The node id of the source access path.
+ type: int
+ aliases: [ node_id ]
+ path_ep:
+ description:
+ - The path of the source access path.
+ - An interface like C(eth1/7) must be provided.
+ type: 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
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+
+notes:
+- The I(source_group), and I(source) must exist before using this module in your playbook.
+ The M(cisco.aci.aci_fabric_span_src_group) and M(cisco.aci.aci_fabric_span_src_group_src) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_fabric_span_src_group
+- module: cisco.aci.aci_fabric_span_src_group_src
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(span:RsSrcToPathEp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Create a Fabric SPAN source path of type path
+ cisco.aci.aci_fabric_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ node: 101
+ path_ep: eth1/1
+ state: present
+ delegate_to: localhost
+
+- name: Delete a Fabric SPAN source path
+ cisco.aci.aci_fabric_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ node: 101
+ path_ep: eth1/1
+ state: absent
+ delegate_to: localhost
+
+- name: Query all Fabric SPAN source paths
+ cisco.aci.aci_fabric_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Fabric SPAN source path
+ cisco.aci.aci_fabric_span_src_group_src_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ source_group: my_span_source_group
+ source: my_source
+ pod: 1
+ node: 101
+ path_ep: eth1/1
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(
+ source_group=dict(type="str", aliases=["src_group"]), # Not required for querying all objects
+ source=dict(type="str", aliases=["src"]), # Not required for querying all objects
+ pod=dict(type="int", aliases=["pod_id", "pod_number"]), # Not required for querying all objects
+ node=dict(type="int", aliases=["node_id"]), # Not required for querying all objects
+ path_ep=dict(type="str"), # Not required for querying all objects
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["source_group", "source", "pod", "node", "path_ep"]],
+ ["state", "present", ["source_group", "source", "pod", "node", "path_ep"]],
+ ],
+ )
+
+ aci = ACIModule(module)
+
+ source_group = module.params.get("source_group")
+ source = module.params.get("source")
+ pod = module.params.get("pod")
+ node = module.params.get("node")
+ path_ep = module.params.get("path_ep")
+ state = module.params.get("state")
+
+ tdn = None
+ if pod and node and path_ep:
+ tdn = "topology/pod-{0}/paths-{1}/pathep-[{2}]".format(pod, node, path_ep)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fabric",
+ aci_rn="fabric",
+ ),
+ subclass_1=dict(
+ aci_class="spanSrcGrp",
+ aci_rn="srcgrp-{0}".format(source_group),
+ module_object=source_group,
+ target_filter={"name": source_group},
+ ),
+ subclass_2=dict(
+ aci_class="spanSrc",
+ aci_rn="src-{0}".format(source),
+ module_object=source,
+ target_filter={"name": source},
+ ),
+ subclass_3=dict(
+ aci_class="spanRsSrcToPathEp",
+ aci_rn="rssrcToPathEp-[{0}]".format(tdn),
+ module_object=tdn,
+ target_filter={"tDn": tdn},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(aci_class="spanRsSrcToPathEp", class_config=dict(tDn=tdn))
+
+ aci.get_diff(aci_class="spanRsSrcToPathEp")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py
new file mode 100644
index 000000000..1bc7c28eb
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py
@@ -0,0 +1,318 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_fabric_wide_settings
+short_description: Manage Fabric Wide Settings (infra:SetPol)
+description:
+- Manage Fabric Wide Settings on Cisco ACI fabrics.
+options:
+ disable_remote_ep_learning:
+ description:
+ - Whether to disable remote endpoint learning in VRFs containing external bridged/routed domains.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ enforce_subnet_check:
+ description:
+ - Whether to disable IP address learning on the outside of subnets configured in a VRF, for all VRFs.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ enforce_epg_vlan_validation:
+ description:
+ - Whether to perform a validation check that prevents overlapping VLAN pools from being associated to an EPG.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ enforce_domain_validation:
+ description:
+ - Whether to perform a validation check if a static path is added but no domain is associated to an EPG.
+ - Asking for domain validation is a one time operation. Once enabled, it cannot be disabled.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ spine_opflex_client_auth:
+ description:
+ - Whether to enforce Opflex client certificate authentication on spine switches for GOLF and Linux.
+ - The APIC defaults to C(true) when unset during creation.
+ type: bool
+ leaf_opflex_client_auth:
+ description:
+ - Whether to enforce Opflex client certificate authentication on leaf switches for GOLF and Linux.
+ - The APIC defaults to C(true) when unset during creation.
+ type: bool
+ spine_ssl_opflex:
+ description:
+ - Whether to enable SSL Opflex transport for spine switches.
+ - The APIC defaults to C(true) when unset during creation.
+ type: bool
+ leaf_ssl_opflex:
+ description:
+ - Whether to enable SSL Opflex transport for leaf switches.
+ - The APIC defaults to C(true) when unset during creation.
+ type: bool
+ opflex_ssl_versions:
+ description:
+ - Which versions of TLS to enable for Opflex.
+ - When setting any of the TLS versions, you must explicitly set the state for all of them.
+ type: list
+ elements: str
+ choices: [ tls_v1.0, tls_v1.1, tls_v1.2 ]
+ reallocate_gipo:
+ description:
+ - Whether to reallocate some non-stretched BD gipos to make room for stretched BDs.
+ - Asking for gipo reallocation is a one time operation. Once enabled, it cannot be disabled.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ restrict_infra_vlan_traffic:
+ description:
+ - Whether to restrict infra VLAN traffic to only specified network paths. These enabled network paths are defined by infra security entry policies.
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
+ state:
+ description:
+ - Use C(present) for updating configuration.
+ - Use C(query) for showing current configuration.
+ type: str
+ choices: [ present, query ]
+ default: present
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(infra:SetPol).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Update Fabric Wide Settings
+ cisco.aci.aci_fabric_wide_settings:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ disable_remote_ep_learning: true
+ enforce_epg_vlan_validation: true
+ state: present
+ delegate_to: localhost
+
+- name: Update Opflex SSL versions
+ cisco.aci.aci_fabric_wide_settings:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ opflex_ssl_versions: [ tls_v1.2 ]
+ state: present
+ delegate_to: localhost
+
+- name: Query Fabric Wide Settings
+ cisco.aci.aci_fabric_wide_settings:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import TLS_MAPPING
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ disable_remote_ep_learning=dict(type="bool"),
+ enforce_subnet_check=dict(type="bool"),
+ enforce_epg_vlan_validation=dict(type="bool"),
+ enforce_domain_validation=dict(type="bool"),
+ spine_opflex_client_auth=dict(type="bool"),
+ leaf_opflex_client_auth=dict(type="bool"),
+ spine_ssl_opflex=dict(type="bool"),
+ leaf_ssl_opflex=dict(type="bool"),
+ opflex_ssl_versions=dict(type="list", choices=list(TLS_MAPPING.keys()), elements="str"),
+ reallocate_gipo=dict(type="bool"),
+ restrict_infra_vlan_traffic=dict(type="bool"),
+ state=dict(type="str", default="present", choices=["present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ aci = ACIModule(module)
+
+ disable_remote_ep_learning = aci.boolean(module.params.get("disable_remote_ep_learning"))
+ enforce_subnet_check = aci.boolean(module.params.get("enforce_subnet_check"))
+ enforce_epg_vlan_validation = aci.boolean(module.params.get("enforce_epg_vlan_validation"))
+ enforce_domain_validation = aci.boolean(module.params.get("enforce_domain_validation"))
+ spine_opflex_client_auth = aci.boolean(module.params.get("spine_opflex_client_auth"))
+ leaf_opflex_client_auth = aci.boolean(module.params.get("leaf_opflex_client_auth"))
+ spine_ssl_opflex = aci.boolean(module.params.get("spine_ssl_opflex"))
+ leaf_ssl_opflex = aci.boolean(module.params.get("leaf_ssl_opflex"))
+ opflex_ssl_versions = module.params.get("opflex_ssl_versions")
+ reallocate_gipo = aci.boolean(module.params.get("reallocate_gipo"))
+ restrict_infra_vlan_traffic = aci.boolean(module.params.get("restrict_infra_vlan_traffic"))
+ state = module.params.get("state")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="infraSetPol",
+ aci_rn="infra/settings",
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ class_config = dict(
+ unicastXrEpLearnDisable=disable_remote_ep_learning,
+ enforceSubnetCheck=enforce_subnet_check,
+ validateOverlappingVlans=enforce_epg_vlan_validation,
+ domainValidation=enforce_domain_validation,
+ opflexpAuthenticateClients=spine_opflex_client_auth,
+ leafOpflexpAuthenticateClients=leaf_opflex_client_auth,
+ opflexpUseSsl=spine_ssl_opflex,
+ leafOpflexpUseSsl=leaf_ssl_opflex,
+ reallocateGipo=reallocate_gipo,
+ restrictInfraVLANTraffic=restrict_infra_vlan_traffic,
+ )
+ if opflex_ssl_versions is not None:
+ class_config["opflexpSslProtocols"] = ",".join([TLS_MAPPING.get(tls) for tls in sorted(opflex_ssl_versions)])
+
+ aci.payload(
+ aci_class="infraSetPol",
+ class_config=class_config,
+ )
+
+ aci.get_diff(aci_class="infraSetPol")
+
+ aci.post_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_file_remote_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_file_remote_path.py
new file mode 100644
index 000000000..37365b769
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_file_remote_path.py
@@ -0,0 +1,357 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_file_remote_path
+short_description: Manage Import/Export File Remote Paths (file:RemotePath)
+description:
+- Manage Import/Export File Remote Paths on Cisco ACI fabrics.
+options:
+ name:
+ description:
+ - The name of the File Remote Path.
+ type: str
+ description:
+ description:
+ - The description of the File Remote Path.
+ type: str
+ remote_host:
+ description:
+ - The hostname or IP Address of the remote host.
+ type: str
+ remote_port:
+ description:
+ - The port to access the remote host.
+ type: int
+ remote_protocol:
+ description:
+ - The protocol to use to connect to the remote host.
+ type: str
+ choices: [ ftp, scp, sftp ]
+ auth_type:
+ description:
+ - The authentication type for the remote host.
+ - Cannot be set to ssh_key if protocol is ftp.
+ type: str
+ choices: [ password, ssh_key ]
+ remote_user:
+ description:
+ - The username to access the remote host.
+ type: str
+ remote_password:
+ description:
+ - The password to access the remote host.
+ - Only used if auth_type is password.
+ type: str
+ remote_ssh_key:
+ description:
+ - The private SSH key used to access the remote host.
+ - Only used if auth_type is ssh_key.
+ type: str
+ aliases: [ remote_key ]
+ remote_ssh_passphrase:
+ description:
+ - The Pass phrase used to decode private_key.
+ - Only used if auth_type is ssh_key.
+ type: str
+ aliases: [ passphrase ]
+ remote_path:
+ description:
+ - The path on which the data will reside on the remote host.
+ type: str
+ management_epg:
+ description:
+ - The management EPG to connect to the remote host on.
+ type: 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
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(file:RemotePath).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+
+author:
+- Tim Cragg (@timcragg)
+"""
+
+EXAMPLES = r"""
+- name: Add a Remote Path
+ cisco.aci.aci_file_remote_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: ans_remote_path
+ remote_host: test.example.com
+ remote_port: 22
+ remote_protocol: scp
+ remote_user: test_user
+ auth_type: password
+ remote_password: test_pass
+ remote_path: /tmp
+ state: present
+ delegate_to: localhost
+
+- name: Query a Remote Path
+ cisco.aci.aci_file_remote_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: ans_remote_path
+ state: query
+ delegate_to: localhost
+
+- name: Query all Remote Paths
+ cisco.aci.aci_file_remote_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+
+- name: Remove a Remote Path
+ cisco.aci.aci_file_remote_path:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: ans_remote_path
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(
+ name=dict(type="str"),
+ description=dict(type="str"),
+ remote_host=dict(type="str"),
+ remote_port=dict(type="int"),
+ remote_protocol=dict(type="str", choices=["ftp", "scp", "sftp"]),
+ remote_path=dict(type="str"),
+ auth_type=dict(type="str", choices=["password", "ssh_key"]),
+ remote_user=dict(type="str"),
+ remote_password=dict(type="str", no_log=True),
+ remote_ssh_key=dict(type="str", no_log=True, aliases=["remote_key"]),
+ remote_ssh_passphrase=dict(type="str", no_log=True, aliases=["passphrase"]),
+ management_epg=dict(type="str"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "present", ["name", "auth_type"]],
+ ["state", "absent", ["name"]],
+ ],
+ required_together=[["remote_host", "remote_port"]],
+ )
+
+ name = module.params.get("name")
+ description = module.params.get("description")
+ remote_host = module.params.get("remote_host")
+ remote_port = module.params.get("remote_port")
+ remote_protocol = module.params.get("remote_protocol")
+ remote_path = module.params.get("remote_path")
+ auth_type = module.params.get("auth_type")
+ remote_user = module.params.get("remote_user")
+ remote_password = module.params.get("remote_password")
+ remote_key = module.params.get("remote_ssh_key")
+ passphrase = module.params.get("remote_ssh_passphrase")
+ management_epg = module.params.get("management_epg")
+ state = module.params.get("state")
+
+ aci = ACIModule(module)
+
+ if auth_type == "password":
+ if remote_key is not None:
+ aci.fail_json(msg="remote_key cannot be set if auth_type is password")
+ if passphrase is not None:
+ aci.fail_json(msg="passphrase cannot be set if auth_type is password")
+ auth = "usePassword"
+ elif auth_type == "ssh_key":
+ if remote_password is not None:
+ aci.fail_json(msg="remote_password cannot be set if auth_type is ssh_key")
+ auth = "useSshKeyContents"
+ else:
+ auth = None
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fileRemotePath",
+ aci_rn="fabric/path-{0}".format(name),
+ module_object=name,
+ target_filter={"name": name},
+ ),
+ child_classes=["fileRsARemoteHostToEpg"],
+ )
+ aci.get_existing()
+
+ if state == "present":
+ child_configs = []
+ if management_epg is not None:
+ child_configs.append(
+ dict(
+ fileRsARemoteHostToEpg=dict(
+ attributes=dict(tDn=("uni/tn-mgmt/mgmtp-default/{0}".format(management_epg))),
+ )
+ )
+ )
+ aci.payload(
+ aci_class="fileRemotePath",
+ class_config=dict(
+ name=name,
+ descr=description,
+ authType=auth,
+ host=remote_host,
+ protocol=remote_protocol,
+ remotePath=remote_path,
+ remotePort=remote_port,
+ userName=remote_user,
+ userPasswd=remote_password,
+ identityPrivateKeyContents=remote_key,
+ identityPrivateKeyPassphrase=passphrase,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="fileRemotePath")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py b/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py
index 50587e198..584bb1012 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py
@@ -27,24 +27,63 @@ options:
- Description for the Filter Entry.
type: str
aliases: [ descr ]
- dst_port:
+ destination_port:
description:
- Used to set both destination start and end ports to the same value when ip_protocol is tcp or udp.
- Accepted values are any valid TCP/UDP port range.
- The APIC defaults to C(unspecified) when unset during creation.
type: str
- dst_port_end:
+ aliases: [ dst_port ]
+ destination_port_end:
description:
- Used to set the destination end port when ip_protocol is tcp or udp.
- Accepted values are any valid TCP/UDP port range.
- The APIC defaults to C(unspecified) when unset during creation.
type: str
- dst_port_start:
+ aliases: [ dst_port_end ]
+ destination_port_start:
description:
- Used to set the destination start port when ip_protocol is tcp or udp.
- Accepted values are any valid TCP/UDP port range.
- The APIC defaults to C(unspecified) when unset during creation.
type: str
+ aliases: [ dst_port_start ]
+ source_port:
+ description:
+ - Used to set both source start and end ports to the same value when ip_protocol is tcp or udp.
+ - Accepted values are any valid TCP/UDP port range.
+ - The APIC defaults to C(unspecified) when unset during creation.
+ type: str
+ aliases: [ src_port ]
+ source_port_end:
+ description:
+ - Used to set the source end port when ip_protocol is tcp or udp.
+ - Accepted values are any valid TCP/UDP port range.
+ - The APIC defaults to C(unspecified) when unset during creation.
+ type: str
+ aliases: [ src_port_end ]
+ source_port_start:
+ description:
+ - Used to set the source start port when ip_protocol is tcp or udp.
+ - Accepted values are any valid TCP/UDP port range.
+ - The APIC defaults to C(unspecified) when unset during creation.
+ type: str
+ aliases: [ src_port_start ]
+ tcp_flags:
+ description:
+ - The TCP flags of the filter entry.
+ - The TCP C(established) cannot be combined with other tcp rules.
+ - The APIC defaults to C(unspecified) when unset during creation.
+ type: list
+ elements: str
+ choices: [ acknowledgment, established, finish, reset, synchronize, unspecified ]
+ match_only_fragments:
+ description:
+ - The match only packet fragments of the filter entry.
+ - When enabled C(true) the rule applies to any fragments with offset greater than 0 (all fragments except first).
+ - When disabled C(false) it applies to all packets (including all fragments)
+ - The APIC defaults to C(false) when unset during creation.
+ type: bool
entry:
description:
- Then name of the Filter Entry.
@@ -128,6 +167,25 @@ EXAMPLES = r"""
ip_protocol: tcp
dst_port_start: 443
dst_port_end: 443
+ source_port_start: 20
+ source_port_end: 22
+ tcp_flags:
+ - acknowledgment
+ - finish
+ state: present
+ delegate_to: localhost
+
+- name: Create a filter entry with the match only packet fragments enabled
+ cisco.aci.aci_filter_entry:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ entry: https_allow
+ filter: web_filter
+ tenant: prod
+ ether_type: ip
+ ip_protocol: tcp
+ match_only_fragments: true
state: present
delegate_to: localhost
@@ -271,45 +329,14 @@ url:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
-from ansible_collections.cisco.aci.plugins.module_utils.constants import VALID_IP_PROTOCOLS, FILTER_PORT_MAPPING
-
-
-VALID_ARP_FLAGS = ["arp_reply", "arp_request", "unspecified"]
-VALID_ETHER_TYPES = ["arp", "fcoe", "ip", "ipv4", "ipv6", "mac_security", "mpls_ucast", "trill", "unspecified"]
-VALID_ICMP_TYPES = ["dst_unreachable", "echo", "echo_reply", "src_quench", "time_exceeded", "unspecified"]
-VALID_ICMP6_TYPES = [
- "dst_unreachable",
- "echo_request",
- "echo_reply",
- "neighbor_advertisement",
- "neighbor_solicitation",
- "redirect",
- "time_exceeded",
- "unspecified",
-]
-
-# mapping dicts are used to normalize the proposed data to what the APIC expects, which will keep diffs accurate
-ARP_FLAG_MAPPING = dict(arp_reply="reply", arp_request="req", unspecified=None)
-
-ICMP_MAPPING = {
- "dst_unreachable": "dst-unreach",
- "echo": "echo",
- "echo_reply": "echo-rep",
- "src_quench": "src-quench",
- "time_exceeded": "time-exceeded",
- "unspecified": "unspecified",
- "echo-rep": "echo-rep",
- "dst-unreach": "dst-unreach",
-}
-ICMP6_MAPPING = dict(
- dst_unreachable="dst-unreach",
- echo_request="echo-req",
- echo_reply="echo-rep",
- neighbor_advertisement="nbr-advert",
- neighbor_solicitation="nbr-solicit",
- redirect="redirect",
- time_exceeded="time-exceeded",
- unspecified="unspecified",
+from ansible_collections.cisco.aci.plugins.module_utils.constants import (
+ VALID_IP_PROTOCOLS,
+ FILTER_PORT_MAPPING,
+ VALID_ETHER_TYPES,
+ ARP_FLAG_MAPPING,
+ ICMP4_MAPPING,
+ ICMP6_MAPPING,
+ TCP_FLAGS,
)
@@ -317,16 +344,21 @@ def main():
argument_spec = aci_argument_spec()
argument_spec.update(aci_annotation_spec())
argument_spec.update(
- arp_flag=dict(type="str", choices=VALID_ARP_FLAGS),
+ arp_flag=dict(type="str", choices=list(ARP_FLAG_MAPPING.keys())),
description=dict(type="str", aliases=["descr"]),
- dst_port=dict(type="str"),
- dst_port_end=dict(type="str"),
- dst_port_start=dict(type="str"),
+ destination_port=dict(type="str", aliases=["dst_port"]),
+ destination_port_end=dict(type="str", aliases=["dst_port_end"]),
+ destination_port_start=dict(type="str", aliases=["dst_port_start"]),
+ source_port=dict(type="str", aliases=["src_port"]),
+ source_port_end=dict(type="str", aliases=["src_port_end"]),
+ source_port_start=dict(type="str", aliases=["src_port_start"]),
+ tcp_flags=dict(type="list", elements="str", choices=list(TCP_FLAGS.keys())),
+ match_only_fragments=dict(type="bool"),
entry=dict(type="str", aliases=["entry_name", "filter_entry", "name"]), # Not required for querying all objects
ether_type=dict(choices=VALID_ETHER_TYPES, type="str"),
filter=dict(type="str", aliases=["filter_name"]), # Not required for querying all objects
- icmp_msg_type=dict(type="str", choices=VALID_ICMP_TYPES),
- icmp6_msg_type=dict(type="str", choices=VALID_ICMP6_TYPES),
+ icmp_msg_type=dict(type="str", choices=list(ICMP4_MAPPING.keys())),
+ icmp6_msg_type=dict(type="str", choices=list(ICMP6_MAPPING.keys())),
ip_protocol=dict(choices=VALID_IP_PROTOCOLS, type="str"),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
stateful=dict(type="bool"),
@@ -349,21 +381,21 @@ def main():
if arp_flag is not None:
arp_flag = ARP_FLAG_MAPPING.get(arp_flag)
description = module.params.get("description")
- dst_port = module.params.get("dst_port")
+ dst_port = module.params.get("destination_port")
if FILTER_PORT_MAPPING.get(dst_port) is not None:
dst_port = FILTER_PORT_MAPPING.get(dst_port)
- dst_end = module.params.get("dst_port_end")
- if FILTER_PORT_MAPPING.get(dst_end) is not None:
- dst_end = FILTER_PORT_MAPPING.get(dst_end)
- dst_start = module.params.get("dst_port_start")
- if FILTER_PORT_MAPPING.get(dst_start) is not None:
- dst_start = FILTER_PORT_MAPPING.get(dst_start)
+ dst_port_end = module.params.get("destination_port_end")
+ if FILTER_PORT_MAPPING.get(dst_port_end) is not None:
+ dst_port_end = FILTER_PORT_MAPPING.get(dst_port_end)
+ dst_port_start = module.params.get("destination_port_start")
+ if FILTER_PORT_MAPPING.get(dst_port_start) is not None:
+ dst_port_start = FILTER_PORT_MAPPING.get(dst_port_start)
entry = module.params.get("entry")
ether_type = module.params.get("ether_type")
filter_name = module.params.get("filter")
icmp_msg_type = module.params.get("icmp_msg_type")
if icmp_msg_type is not None:
- icmp_msg_type = ICMP_MAPPING.get(icmp_msg_type)
+ icmp_msg_type = ICMP4_MAPPING.get(icmp_msg_type)
icmp6_msg_type = module.params.get("icmp6_msg_type")
if icmp6_msg_type is not None:
icmp6_msg_type = ICMP6_MAPPING.get(icmp6_msg_type)
@@ -373,12 +405,46 @@ def main():
tenant = module.params.get("tenant")
name_alias = module.params.get("name_alias")
- # validate that dst_port is not passed with dst_start or dst_end
- if dst_port is not None and (dst_end is not None or dst_start is not None):
- module.fail_json(msg="Parameter 'dst_port' cannot be used with 'dst_end' and 'dst_start'")
+ source_port = module.params.get("source_port")
+ if FILTER_PORT_MAPPING.get(source_port) is not None:
+ source_port = FILTER_PORT_MAPPING.get(source_port)
+ source_port_end = module.params.get("source_port_end")
+ if FILTER_PORT_MAPPING.get(source_port_end) is not None:
+ source_port_end = FILTER_PORT_MAPPING.get(source_port_end)
+ source_port_start = module.params.get("source_port_start")
+ if FILTER_PORT_MAPPING.get(source_port_start) is not None:
+ source_port_start = FILTER_PORT_MAPPING.get(source_port_start)
+
+ # validate that dst_port is not passed with dst_port_end or dst_port_start
+ if dst_port is not None and (dst_port_end is not None or dst_port_start is not None):
+ module.fail_json(msg="Parameter 'dst_port' cannot be used with 'dst_port_end' and 'dst_port_start'")
+ elif dst_port_end is not None and dst_port_start is None:
+ module.fail_json(msg="Parameter 'dst_port_end' cannot be configured when the 'dst_port_start' is not defined")
elif dst_port is not None:
- dst_end = dst_port
- dst_start = dst_port
+ dst_port_end = dst_port
+ dst_port_start = dst_port
+
+ # validate that source_port is not passed with source_port_end or source_port_start
+ if source_port is not None and (source_port_end is not None or source_port_start is not None):
+ module.fail_json(msg="Parameter 'source_port' cannot be used with 'source_port_end' and 'source_port_start'")
+ elif source_port_end is not None and source_port_start is None:
+ module.fail_json(msg="Parameter 'source_port_end' cannot be configured when the 'source_port_start' is not defined")
+ elif source_port is not None:
+ source_port_end = source_port
+ source_port_start = source_port
+
+ tcp_flags = module.params.get("tcp_flags")
+ tcp_flags_list = list()
+ if tcp_flags is not None:
+ if len(tcp_flags) >= 2 and "established" in tcp_flags:
+ module.fail_json(msg="TCP established cannot be combined with other tcp rules")
+ else:
+ for tcp_flag in tcp_flags:
+ tcp_flags_list.append(TCP_FLAGS.get(tcp_flag))
+
+ match_only_fragments = aci.boolean(module.params.get("match_only_fragments"))
+ if match_only_fragments == "yes" and (dst_port or source_port or source_port_start or source_port_end or dst_port_start or dst_port_end):
+ module.fail_json(msg="Parameter 'match_only_fragments' cannot be used with 'Layer 4 Port' value")
aci.construct_url(
root_class=dict(
@@ -409,8 +475,8 @@ def main():
class_config=dict(
arpOpc=arp_flag,
descr=description,
- dFromPort=dst_start,
- dToPort=dst_end,
+ dFromPort=dst_port_start,
+ dToPort=dst_port_end,
etherT=ether_type,
icmpv4T=icmp_msg_type,
icmpv6T=icmp6_msg_type,
@@ -418,6 +484,10 @@ def main():
prot=ip_protocol,
stateful=stateful,
nameAlias=name_alias,
+ applyToFrag=match_only_fragments,
+ sFromPort=source_port_start,
+ sToPort=source_port_end,
+ tcpRules=",".join(tcp_flags_list),
),
)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py
index 1a9db1a6c..9687b05f3 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py
@@ -1,5 +1,7 @@
#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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
@@ -8,43 +10,61 @@ __metaclass__ = type
ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
-DOCUMENTATION = """
+DOCUMENTATION = r"""
---
module: aci_firmware_group
-
-short_description: This module creates a firmware group
-
-
+short_description: Manage firmware groups (firmware:FwGrp)
description:
- - This module creates a firmware group, so that you can apply firmware policy to nodes.
+- This module creates a firmware group, so that you can apply firmware policy to nodes.
options:
group:
description:
- - This the name of the firmware group
+ - Name of the firmware group.
+ type: str
+ policy:
+ description:
+ - Name of the firmware policy
+ - It is important that you use the same name as the policy created with M(cisco.aci.aci_firmware_policy).
+ type: str
+ aliases: [ firmwarepol ]
+ type_group:
+ description:
+ - Type of the firmware group.
+ - The APIC defaults to C(range) when unset during creation.
type: str
- firmwarepol:
+ choices: [ all, all_in_pod, range ]
+ description:
description:
- - This is the name of the firmware policy, which was create by aci_firmware_policy. It is important that
- - you use the same name as the policy created with aci_firmware_policy
+ - Description of the firmware group.
type: str
+ aliases: [ descr ]
state:
description:
- - Use C(present) or C(absent) for adding or removing.
- - Use C(query) for listing an object or multiple objects.
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
type: str
default: present
choices: [ absent, present, query ]
name_alias:
description:
- - The alias for the current object. This relates to the nameAlias field in ACI.
+ - The alias for the current object. This relates to the nameAlias field in ACI.
type: str
extends_documentation_fragment:
- cisco.aci.aci
- cisco.aci.annotation
- cisco.aci.owner
+notes:
+- The C(policy) must exist before using this module in your playbook.
+- The M(cisco.aci.aci_firmware_policy) module can be used for this.
+seealso:
+- module: cisco.aci.aci_firmware_policy
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(firmware:FwGrp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Steven Gerhart (@sgerhart)
+ - Gaspard Micol (@gmicol)
"""
@@ -55,7 +75,7 @@ EXAMPLES = r"""
username: admin
password: SomeSecretPassword
group: fmgroup
- firmwarepol: fmpolicy1
+ policy: fmpolicy1
state: present
delegate_to: localhost
@@ -88,7 +108,7 @@ EXAMPLES = r"""
register: query_result
"""
-RETURN = """
+RETURN = r"""
current:
description: The existing configuration from the APIC after the module has finished
returned: success
@@ -195,6 +215,7 @@ url:
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_TYPE_GROUP_MAPPING
def main():
@@ -203,7 +224,9 @@ def main():
argument_spec.update(aci_owner_spec())
argument_spec.update(
group=dict(type="str"), # Not required for querying all objects
- firmwarepol=dict(type="str"), # Not required for querying all objects
+ policy=dict(type="str", aliases=["firmwarepol"]), # Not required for querying all objects
+ type_group=dict(type="str", choices=list(MATCH_TYPE_GROUP_MAPPING.keys())),
+ description=dict(type="str", aliases=["descr"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
)
@@ -213,13 +236,15 @@ def main():
supports_check_mode=True,
required_if=[
["state", "absent", ["group"]],
- ["state", "present", ["group", "firmwarepol"]],
+ ["state", "present", ["group", "policy"]],
],
)
state = module.params.get("state")
group = module.params.get("group")
- firmwarepol = module.params.get("firmwarepol")
+ policy = module.params.get("policy")
+ type_group = MATCH_TYPE_GROUP_MAPPING.get(module.params.get("type_group"))
+ description = module.params.get("description")
name_alias = module.params.get("name_alias")
aci = ACIModule(module)
@@ -240,13 +265,15 @@ def main():
aci_class="firmwareFwGrp",
class_config=dict(
name=group,
+ descr=description,
+ type=type_group,
nameAlias=name_alias,
),
child_configs=[
dict(
firmwareRsFwgrpp=dict(
attributes=dict(
- tnFirmwareFwPName=firmwarepol,
+ tnFirmwareFwPName=policy,
),
),
),
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py
index b41a2601b..893eaa867 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py
@@ -1,50 +1,54 @@
#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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 = """
+DOCUMENTATION = r"""
---
module: aci_firmware_group_node
-
-short_description: This modules adds and remove nodes from the firmware group
-
-
+short_description: Manage firmware group nodes (fabric:NodeBlk)
description:
- - This module addes/deletes a node to the firmware group. This modules assigns 1 node at a time.
-
+- This module adds/deletes a node to the firmware group.
options:
group:
description:
- - This is the name of the firmware group
+ - This is the name of the firmware group.
type: str
node:
description:
- - The node to be added to the firmware group - the value equals the NodeID
+ - The node to be added to the firmware group.
+ - The value equals the NodeID.
type: str
state:
description:
- - Use C(present) or C(absent) for adding or removing.
- - Use C(query) for listing an object or multiple objects.
+ - Use C(present) or C(absent) for adding or removing.
+ - Use C(query) for listing an object or multiple objects.
type: str
default: present
choices: [ absent, present, query ]
name_alias:
description:
- - The alias for the current object. This relates to the nameAlias field in ACI.
+ - The alias for the current object. This relates to the nameAlias field in ACI.
type: str
extends_documentation_fragment:
- cisco.aci.aci
- cisco.aci.annotation
+seealso:
+- module: cisco.aci.aci_firmware_group
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(l3ext:Out).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Steven Gerhart (@sgerhart)
+ - Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -89,7 +93,7 @@ EXAMPLES = r"""
register: query_result
"""
-RETURN = """
+RETURN = r"""
current:
description: The existing configuration from the APIC after the module has finished
returned: success
@@ -221,6 +225,9 @@ def main():
group = module.params.get("group")
node = module.params.get("node")
name_alias = module.params.get("name_alias")
+ block_name = None
+ if node is not None:
+ block_name = "blk{0}-{0}".format(node)
aci = ACIModule(module)
aci.construct_url(
@@ -233,7 +240,7 @@ def main():
subclass_1=dict(
aci_class="fabricNodeBlk",
aci_rn="nodeblk-blk{0}-{0}".format(node),
- target_filter={"name": node},
+ target_filter={"name": block_name},
module_object=node,
),
)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py
index bbe37ecfd..50b41a960 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
+# -*- coding: utf-8 -*-
-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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
@@ -10,69 +11,124 @@ __metaclass__ = type
ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
-DOCUMENTATION = """
+DOCUMENTATION = r"""
---
module: aci_firmware_policy
-
-short_description: This creates a firmware policy
-
-
+short_description: Manage firmware policies (firmware:FwP)
description:
- - This module creates a firmware policy for firmware groups. The firmware policy is create first and then
- - referenced by the firmware group. You will assign the firmware and specify if you want to ignore the compatibility
- - check
+- This module creates a firmware policy for firmware groups.
+- The compatibility check can be explicitly ignored while assigning the firmware.
options:
name:
description:
- - Name of the firmware policy
+ - The name of the firmware policy
+ type: str
+ effective_on_reboot:
+ description:
+ - A property that indicates if the selected firmware version will be active after reboot.
+ - The firmware must be effective on an unplanned reboot before the scheduled maintenance operation.
+ type: bool
+ ignore_compat:
+ description:
+ - Check if compatibility checks should be ignored
+ type: bool
+ aliases: [ ignoreCompat ]
+ sr_upgrade:
+ description:
+ - The SR firware upgrade.
+ type: bool
+ sr_version:
+ description:
+ - The SR version of the firmware associated with this policy.
type: str
version:
description:
- - The version of the firmware associated with this policy. This value is very import as well as constructing
- - it correctly. The syntax for this field is n9000-xx.x. If you look at the firmware repository using the UI
- - each version will have a "Full Version" column, this is the value you need to use. So, if the Full Version
- - is 13.1(1i), the value for this field would be n9000-13.1(1i)
+ - The version of the firmware associated with this policy.
+ - The syntax for this field is n9000-xx.x.
+ - if the Full Version is 13.1(1i), the value for this field would be n9000-13.1(1i).
type: str
- ignoreCompat:
+ version_check_override:
description:
- - Check if compatibility checks should be ignored
- type: bool
+ - The version check override.
+ - This is a directive to ignore the version check for the next install.
+ - The version check, which occurs during a maintenance window, checks to see if the desired version matches the running version.
+ - If the versions do not match, the install is performed. If the versions do match, the install is not performed.
+ - The version check override is a one-time override that performs the install whether or not the versions match.
+ - The APIC defaults to C(untriggered) when unset during creation.
+ type: str
+ choices: [ trigger, trigger_immediate, triggered, untriggered ]
+ description:
+ description:
+ - Description for the firmware policy.
+ type: str
+ aliases: [ descr ]
state:
description:
- - Use C(present) or C(absent) for adding or removing.
- - Use C(query) for listing an object or multiple objects.
+ - 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
name_alias:
description:
- - The alias for the current object. This relates to the nameAlias field in ACI.
+ - The alias for the current object. This relates to the nameAlias field in ACI.
type: str
extends_documentation_fragment:
- cisco.aci.aci
- cisco.aci.annotation
- cisco.aci.owner
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(firmware:FwP).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Steven Gerhart (@sgerhart)
+ - Gaspard Micol (@gmicol)
"""
-# FIXME: Add more, better examples
EXAMPLES = r"""
- - name: firmware policy
- cisco.aci.aci_firmware_policy:
- host: "{{ inventory_hostname }}"
- username: "{{ user }}"
- password: "{{ pass }}"
- validate_certs: false
- name: test2FrmPol
- version: n9000-13.2(1m)
- ignoreCompat: False
- state: present
+- name: Create a firmware policy
+ cisco.aci.aci_firmware_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: my_firmware_policy
+ version: n9000-13.2(1m)
+ ignore_compat: False
+ state: present
+ delegate_to: localhost
+- name: Delete a firmware policy
+ cisco.aci.aci_firmware_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: my_firmware_policy
+ state: absent
+ delegate_to: localhost
+
+- name: Query all maintenance policies
+ cisco.aci.aci_firmware_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific firmware policy
+ cisco.aci.aci_firmware_policy:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: my_firmware_policy
+ state: query
+ delegate_to: localhost
+ register: query_result
"""
-RETURN = """
+RETURN = r"""
current:
description: The existing configuration from the APIC after the module has finished
returned: success
@@ -180,6 +236,7 @@ url:
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_TRIGGER_MAPPING
def main():
@@ -188,8 +245,13 @@ def main():
argument_spec.update(aci_owner_spec())
argument_spec.update(
name=dict(type="str"), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
version=dict(type="str"),
- ignoreCompat=dict(type="bool"),
+ effective_on_reboot=dict(type="bool"),
+ ignore_compat=dict(type="bool", aliases=["ignoreCompat"]),
+ sr_upgrade=dict(type="bool"),
+ sr_version=dict(type="str"),
+ version_check_override=dict(type="str", choices=list(MATCH_TRIGGER_MAPPING.keys())),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
)
@@ -202,18 +264,19 @@ def main():
["state", "present", ["name", "version"]],
],
)
+ aci = ACIModule(module)
state = module.params.get("state")
name = module.params.get("name")
+ description = module.params.get("description")
version = module.params.get("version")
+ effective_on_reboot = aci.boolean(module.params.get("effective_on_reboot"), "yes", "no")
+ ignore_compat = aci.boolean(module.params.get("ignore_compat"), "yes", "no")
+ sr_version = module.params.get("sr_version")
+ sr_upgrade = aci.boolean(module.params.get("sr_upgrade"), "yes", "no")
+ version_check_override = MATCH_TRIGGER_MAPPING.get(module.params.get("version_check_override"))
name_alias = module.params.get("name_alias")
- if module.params.get("ignoreCompat"):
- ignore = "yes"
- else:
- ignore = "no"
-
- aci = ACIModule(module)
aci.construct_url(
root_class=dict(
aci_class="firmwareFwP",
@@ -230,8 +293,13 @@ def main():
aci_class="firmwareFwP",
class_config=dict(
name=name,
+ descr=description,
version=version,
- ignoreCompat=ignore,
+ effectiveOnReboot=effective_on_reboot,
+ ignoreCompat=ignore_compat,
+ srUpgrade=sr_upgrade,
+ srVersion=sr_version,
+ versionCheckOverride=version_check_override,
nameAlias=name_alias,
),
)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py
index 7e9b165bd..06e661183 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py
@@ -14,9 +14,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported
DOCUMENTATION = r"""
---
module: aci_interface_config
-short_description: Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(5)+
+short_description: Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(7)+
description:
-- Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(5)+
+- Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(7)+
options:
policy_group:
description:
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py
index eaf8e1249..2e61a177d 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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
@@ -32,6 +33,35 @@ options:
- The APIC defaults to C(f) when unset during creation.
type: str
choices: [ f, np ]
+ auto_max_speed:
+ description:
+ - The maximum automatic CPU or port speed.
+ - The APIC defaults to C(32G) when unset during creation.
+ type: str
+ choices: [ 2G, 4G, 8G, 16G, 32G ]
+ fill_pattern:
+ description:
+ - Fill Pattern for native FC ports.
+ - The APIC defaults to C(IDLE) when unset during creation.
+ type: str
+ choices: [ arbff, idle ]
+ buffer_credits:
+ description:
+ - Receive buffer credits for native FC ports.
+ - The APIC defaults to C(64) when unset during creation.
+ type: int
+ speed:
+ description:
+ - The CPU or port speed.
+ - The APIC defaults to C(auto) when unset during creation.
+ type: str
+ choices: [ auto, unknown, 2G, 4G, 8G, 16G, 32G ]
+ trunk_mode:
+ description:
+ - Trunking on/off for native FC ports.
+ - The APIC defaults to C(trunk-off) when unset during creation.
+ type: str
+ choices: [ auto, trunk-off, trunk-on, un-init ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -54,6 +84,7 @@ seealso:
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Dag Wieers (@dagwieers)
+- Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -202,6 +233,7 @@ url:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_FC_FILL_PATTERN_MAPPING, INTERFACE_POLICY_FC_SPEED_LIST
def main():
@@ -212,6 +244,11 @@ def main():
fc_policy=dict(type="str", aliases=["name"]), # Not required for querying all objects
description=dict(type="str", aliases=["descr"]),
port_mode=dict(type="str", choices=["f", "np"]), # No default provided on purpose
+ auto_max_speed=dict(type="str", choices=INTERFACE_POLICY_FC_SPEED_LIST[2:]),
+ fill_pattern=dict(type="str", choices=list(MATCH_FC_FILL_PATTERN_MAPPING.keys())),
+ buffer_credits=dict(type="int"),
+ speed=dict(type="str", choices=INTERFACE_POLICY_FC_SPEED_LIST),
+ trunk_mode=dict(type="str", choices=["auto", "trunk-off", "trunk-on", "un-init"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
)
@@ -227,6 +264,11 @@ def main():
fc_policy = module.params.get("fc_policy")
port_mode = module.params.get("port_mode")
+ auto_max_speed = module.params.get("auto_max_speed")
+ fill_pattern = MATCH_FC_FILL_PATTERN_MAPPING.get(module.params.get("fill_pattern"))
+ buffer_credits = module.params.get("buffer_credits")
+ speed = module.params.get("speed")
+ trunk_mode = module.params.get("trunk_mode")
description = module.params.get("description")
state = module.params.get("state")
name_alias = module.params.get("name_alias")
@@ -250,6 +292,11 @@ def main():
name=fc_policy,
descr=description,
portMode=port_mode,
+ automaxspeed=auto_max_speed,
+ fillPattern=fill_pattern,
+ rxBBCredit=buffer_credits,
+ speed=speed,
+ trunkMode=trunk_mode,
nameAlias=name_alias,
),
)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py
new file mode 100644
index 000000000..6c3436459
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py
@@ -0,0 +1,366 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, 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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_interface_policy_leaf_fc_policy_group
+short_description: Manage Fibre Channel (FC) interface policy groups (infra:FcAccBndlGrp, infra:FcAccPortGrp)
+description:
+- Manage Fibre Channel (FC) interface policy groups on Cisco ACI fabrics.
+options:
+ policy_group:
+ description:
+ - The name of the Fibre Channel (FC) interface policy groups.
+ type: str
+ aliases: [ name, policy_group_name ]
+ description:
+ description:
+ - The description of the Fibre Channel (FC) interface policy group.
+ type: str
+ aliases: [ descr ]
+ lag_type:
+ description:
+ - Selector for the type of Fibre Channel (FC) interface policy group.
+ - C(port) for Fiber Channel (FC)
+ - C(port_channel) for Fiber Channel Port Channel (FC PC)
+ type: str
+ required: true
+ choices: [ port, port_channel ]
+ aliases: [ lag_type_name ]
+ fibre_channel_interface_policy:
+ description:
+ - The name of the fibre channel interface policy used by the Fibre Channel (FC) interface policy group.
+ type: str
+ aliases: [ fibre_channel_interface_policy_name ]
+ port_channel_policy:
+ description:
+ - The name of the port channel policy used by the Fibre Channel (FC) interface policy group.
+ type: str
+ aliases: [ port_channel_policy_name ]
+ attached_entity_profile:
+ description:
+ - The name of the attached entity profile (AEP) used by the Fibre Channel (FC) interface policy group.
+ type: str
+ aliases: [ aep_name, aep ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- When using the module please select the appropriate link_aggregation_type (lag_type).
+- C(port) for Fiber Channel(FC), C(port_channel) for Fiber Channel Port Channel(VPC).
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC classes B(infra:FcAccPortGrp) and B(infra:FcAccBndlGrp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Anvitha Jain (@anvjain)
+"""
+
+EXAMPLES = r"""
+- name: Create a Fiber Channel (FC) Interface Policy Group
+ cisco.aci.aci_interface_policy_leaf_fc_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ lag_type: port
+ fibre_channel_interface_policy: fcinterfacepolicy
+ description: policygroupname description
+ attached_entity_profile: aep
+ state: present
+ delegate_to: localhost
+
+- name: Create a Fiber Channel Port Channel (FC PC) Interface Policy Group
+ cisco.aci.aci_interface_policy_leaf_fc_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ lag_type: port_channel
+ fibre_channel_interface_policy: fcinterfacepolicy
+ description: policygroupname description
+ attached_entity_profile: aep
+ port_channel_policy: lacppolicy
+ state: present
+ delegate_to: localhost
+
+- name: Query all Leaf Access Port Policy Groups of type link
+ cisco.aci.aci_interface_policy_leaf_fc_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ lag_type: port_channel
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Lead Access Port Policy Group
+ cisco.aci.aci_interface_policy_leaf_fc_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ lag_type: port
+ policy_group: policygroupname
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Delete an Interface policy Leaf Policy Group
+ cisco.aci.aci_interface_policy_leaf_fc_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ lag_type: port
+ policy_group: policygroupname
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ # NOTE: Since this module needs to include both infra:FcAccPortGrp (for FC) and infra:FcAccBndlGrp (for FC PC):
+ # NOTE: The user(s) can make a choice between (port(FC), port_channel(FC PC))
+ lag_type=dict(type="str", required=True, aliases=["lag_type_name"], choices=["port", "port_channel"]),
+ policy_group=dict(type="str", aliases=["name", "policy_group_name"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ fibre_channel_interface_policy=dict(type="str", aliases=["fibre_channel_interface_policy_name"]),
+ port_channel_policy=dict(type="str", aliases=["port_channel_policy_name"]),
+ attached_entity_profile=dict(type="str", aliases=["aep_name", "aep"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["policy_group"]],
+ ["state", "present", ["policy_group"]],
+ ],
+ )
+
+ policy_group = module.params.get("policy_group")
+ description = module.params.get("description")
+ lag_type = module.params.get("lag_type")
+ fibre_channel_interface_policy = module.params.get("fibre_channel_interface_policy")
+ port_channel_policy = module.params.get("port_channel_policy")
+ attached_entity_profile = module.params.get("attached_entity_profile")
+ state = module.params.get("state")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ if lag_type == "port":
+ aci_class_name = "infraFcAccPortGrp"
+ dn_name = "fcaccportgrp"
+ elif lag_type == "port_channel":
+ aci_class_name = "infraFcAccBndlGrp"
+ dn_name = "fcaccbundle"
+
+ class_config_dict = dict(
+ name=policy_group,
+ descr=description,
+ nameAlias=name_alias,
+ )
+
+ child_configs = []
+ if fibre_channel_interface_policy is not None:
+ child_configs.append(
+ dict(
+ infraRsFcL2IfPol=dict(
+ attributes=dict(
+ tnFcIfPolName=fibre_channel_interface_policy,
+ ),
+ ),
+ )
+ )
+ if attached_entity_profile is not None:
+ child_configs.append(
+ dict(
+ infraRsFcAttEntP=dict(
+ attributes=dict(
+ tDn="uni/infra/attentp-{0}".format(attached_entity_profile),
+ ),
+ ),
+ )
+ )
+
+ # Add infraRsFcLagPol binding only when port_channel_policy is defined
+ if lag_type == "port_channel" and port_channel_policy is not None:
+ child_configs.append(
+ dict(
+ infraRsFcLagPol=dict(
+ attributes=dict(
+ tnLacpLagPolName=port_channel_policy,
+ ),
+ ),
+ )
+ )
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class=aci_class_name,
+ aci_rn="infra/funcprof/{0}-{1}".format(dn_name, policy_group),
+ module_object=policy_group,
+ target_filter={"name": policy_group},
+ ),
+ child_classes=[
+ "infraRsFcL2IfPol",
+ "infraRsFcLagPol",
+ "infraRsFcAttEntP",
+ ],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class=aci_class_name,
+ class_config=class_config_dict,
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class=aci_class_name)
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py
index 1a1ad21b2..da2e90adf 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py
@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com>
+# Copyright: (c) 2023, 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
@@ -19,17 +20,17 @@ description:
options:
policy_group:
description:
- - Name of the leaf policy group to be added/deleted.
+ - The name of the leaf interface policy group.
type: str
aliases: [ name, policy_group_name ]
description:
description:
- - Description for the leaf policy group to be created.
+ - The description of the leaf interface policy group.
type: str
aliases: [ descr ]
lag_type:
description:
- - Selector for the type of leaf policy group we want to create.
+ - Selector for the type of leaf interface policy group.
- C(leaf) for Leaf Access Port Policy Group
- C(link) for Port Channel (PC)
- C(node) for Virtual Port Channel (VPC)
@@ -39,82 +40,142 @@ options:
aliases: [ lag_type_name ]
link_level_policy:
description:
- - Choice of link_level_policy to be used as part of the leaf policy group to be created.
+ - The name of the link level policy used by the leaf interface policy group.
type: str
aliases: [ link_level_policy_name ]
cdp_policy:
description:
- - Choice of cdp_policy to be used as part of the leaf policy group to be created.
+ - The name of the cdp policy used by the leaf interface policy group.
type: str
aliases: [ cdp_policy_name ]
mcp_policy:
description:
- - Choice of mcp_policy to be used as part of the leaf policy group to be created.
+ - The name of the mcp policy used by the leaf interface policy group.
type: str
aliases: [ mcp_policy_name ]
lldp_policy:
description:
- - Choice of lldp_policy to be used as part of the leaf policy group to be created.
+ - The name of the lldp policy used by the leaf interface policy group.
type: str
aliases: [ lldp_policy_name ]
stp_interface_policy:
description:
- - Choice of stp_interface_policy to be used as part of the leaf policy group to be created.
+ - The name of the stp interface policy used by the leaf interface policy group.
type: str
aliases: [ stp_interface_policy_name ]
egress_data_plane_policing_policy:
description:
- - Choice of egress_data_plane_policing_policy to be used as part of the leaf policy group to be created.
+ - The name of the egress data plane policing policy used by the leaf interface policy group.
type: str
aliases: [ egress_data_plane_policing_policy_name ]
ingress_data_plane_policing_policy:
description:
- - Choice of ingress_data_plane_policing_policy to be used as part of the leaf policy group to be created.
+ - The name of the ingress data plane policing policy used by the leaf interface policy group.
type: str
aliases: [ ingress_data_plane_policing_policy_name ]
priority_flow_control_policy:
description:
- - Choice of priority_flow_control_policy to be used as part of the leaf policy group to be created.
+ - The name of the priority flow control policy used by the leaf interface policy group.
type: str
aliases: [ priority_flow_control_policy_name ]
fibre_channel_interface_policy:
description:
- - Choice of fibre_channel_interface_policy to be used as part of the leaf policy group to be created.
+ - The name of the fibre channel interface policy used by the leaf interface policy group.
type: str
aliases: [ fibre_channel_interface_policy_name ]
slow_drain_policy:
description:
- - Choice of slow_drain_policy to be used as part of the leaf policy group to be created.
+ - The name of the slow drain policy used by the leaf interface policy group.
type: str
aliases: [ slow_drain_policy_name ]
port_channel_policy:
description:
- - Choice of port_channel_policy to be used as part of the leaf policy group to be created.
+ - The name of the port channel policy used by the leaf interface policy group.
type: str
aliases: [ port_channel_policy_name ]
monitoring_policy:
description:
- - Choice of monitoring_policy to be used as part of the leaf policy group to be created.
+ - The name of the monitoring policy used by the leaf interface policy group.
type: str
aliases: [ monitoring_policy_name ]
storm_control_interface_policy:
description:
- - Choice of storm_control_interface_policy to be used as part of the leaf policy group to be created.
+ - The name of the storm control interface policy used by the leaf interface policy group.
type: str
aliases: [ storm_control_interface_policy_name ]
l2_interface_policy:
description:
- - Choice of l2_interface_policy to be used as part of the leaf policy group to be created.
+ - The name of the l2 interface policy used by the leaf interface policy group.
type: str
aliases: [ l2_interface_policy_name ]
port_security_policy:
description:
- - Choice of port_security_policy to be used as part of the leaf policy group to be created.
+ - The name of the port security policy used by the leaf interface policy group.
type: str
aliases: [ port_security_policy_name ]
+ link_flap_policy:
+ description:
+ - The name of the link flap policy used by the leaf interface policy group.
+ type: str
+ aliases: [ link_flap_policy_name ]
+ link_level_flow_control:
+ description:
+ - The name of the link level flow control used by the leaf interface policy group.
+ type: str
+ aliases: [ link_level_flow_control_name ]
+ mac_sec_interface_policy:
+ description:
+ - The name of the mac sec interface policy used by the leaf interface policy group.
+ type: str
+ aliases: [ mac_sec_interface_policy_name ]
+ copp_policy:
+ description:
+ - The name of the copp policy used by the leaf interface policy group.
+ type: str
+ aliases: [ copp_policy_name ]
+ sync_e_interface_policy:
+ description:
+ - The name of the syncE interface policy used by the leaf interface policy group.
+ - Only availavle in APIC version 5.2 or later.
+ type: str
+ aliases: [ sync_e_interface_policy_name ]
+ port_authentication:
+ description:
+ - The name of the port authentication used by the leaf interface policy group.
+ type: str
+ aliases: [ port_authentication_name ]
+ dwdm:
+ description:
+ - The name of the dwdm used by the leaf interface policy group.
+ type: str
+ aliases: [ dwdm_name ]
+ poe_interface_policy:
+ description:
+ - The name of the poe interface policy used by the leaf interface policy group.
+ type: str
+ aliases: [ poe_interface_policy_name ]
+ transceiver_policy:
+ description:
+ - The name of the transceiver policy used by the leaf interface policy group.
+ - Only availavle in APIC version 6.0(2h) or later.
+ type: dict
+ suboptions:
+ type:
+ description:
+ - The type of the transceiver policy.
+ type: str
+ required: true
+ aliases: [ transceiver_policy_type ]
+ choices: [ zr, zrp]
+ name:
+ description:
+ - The name of the transceiver policy.
+ type: str
+ required: true
+ aliases: [ transceiver_policy_name ]
aep:
description:
- - Choice of attached_entity_profile (AEP) to be used as part of the leaf policy group to be created.
+ - The name of the attached entity profile (AEP) used by the leaf interface policy group.
type: str
aliases: [ aep_name ]
state:
@@ -135,7 +196,7 @@ extends_documentation_fragment:
notes:
- When using the module please select the appropriate link_aggregation_type (lag_type).
- C(link) for Port Channel(PC), C(node) for Virtual Port Channel(VPC) and C(leaf) for Leaf Access Port Policy Group.
+- C(link) for Port Channel(PC), C(node) for Virtual Port Channel(VPC) and C(leaf) for Leaf Access Port Policy Group.
seealso:
- name: APIC Management Information Model reference
description: More information about the internal APIC classes B(infra:AccBndlGrp) and B(infra:AccPortGrp).
@@ -153,10 +214,10 @@ EXAMPLES = r"""
lag_type: link
policy_group: policygroupname
description: policygroupname description
- link_level_policy: whateverlinklevelpolicy
- cdp_policy: whatevercdppolicy
- lldp_policy: whateverlldppolicy
- port_channel_policy: whateverlacppolicy
+ link_level_policy: linklevelpolicy
+ cdp_policy: cdppolicy
+ lldp_policy: lldppolicy
+ port_channel_policy: lacppolicy
state: present
delegate_to: localhost
@@ -167,10 +228,10 @@ EXAMPLES = r"""
password: SomeSecretPassword
lag_type: node
policy_group: policygroupname
- link_level_policy: whateverlinklevelpolicy
- cdp_policy: whatevercdppolicy
- lldp_policy: whateverlldppolicy
- port_channel_policy: whateverlacppolicy
+ link_level_policy: linklevelpolicy
+ cdp_policy: cdppolicy
+ lldp_policy: lldppolicy
+ port_channel_policy: lacppolicy
state: present
delegate_to: localhost
@@ -181,9 +242,9 @@ EXAMPLES = r"""
password: SomeSecretPassword
lag_type: leaf
policy_group: policygroupname
- link_level_policy: whateverlinklevelpolicy
- cdp_policy: whatevercdppolicy
- lldp_policy: whateverlldppolicy
+ link_level_policy: linklevelpolicy
+ cdp_policy: cdppolicy
+ lldp_policy: lldppolicy
state: present
delegate_to: localhost
@@ -334,7 +395,7 @@ def main():
argument_spec.update(aci_owner_spec())
argument_spec.update(
# NOTE: Since this module needs to include both infra:AccBndlGrp (for PC and VPC) and infra:AccPortGrp (for leaf access port policy group):
- # NOTE: I'll allow the user to make the choice here (link(PC), node(VPC), leaf(leaf-access port policy group))
+ # NOTE: The user(s) can make the choice between (link(PC), node(VPC), leaf(leaf-access port policy group))
lag_type=dict(type="str", required=True, aliases=["lag_type_name"], choices=["leaf", "link", "node"]),
policy_group=dict(type="str", aliases=["name", "policy_group_name"]), # Not required for querying all objects
description=dict(type="str", aliases=["descr"]),
@@ -353,7 +414,22 @@ def main():
storm_control_interface_policy=dict(type="str", aliases=["storm_control_interface_policy_name"]),
l2_interface_policy=dict(type="str", aliases=["l2_interface_policy_name"]),
port_security_policy=dict(type="str", aliases=["port_security_policy_name"]),
+ link_flap_policy=dict(type="str", aliases=["link_flap_policy_name"]),
+ link_level_flow_control=dict(type="str", aliases=["link_level_flow_control_name"]),
+ mac_sec_interface_policy=dict(type="str", aliases=["mac_sec_interface_policy_name"]),
+ copp_policy=dict(type="str", aliases=["copp_policy_name"]),
aep=dict(type="str", aliases=["aep_name"]),
+ sync_e_interface_policy=dict(type="str", aliases=["sync_e_interface_policy_name"]),
+ transceiver_policy=dict(
+ type="dict",
+ options=dict(
+ name=dict(type="str", required=True, aliases=["transceiver_policy_name"]),
+ type=dict(type="str", required=True, choices=["zr", "zrp"], aliases=["transceiver_policy_type"]),
+ ),
+ ),
+ poe_interface_policy=dict(type="str", aliases=["poe_interface_policy_name"]),
+ port_authentication=dict(type="str", aliases=["port_authentication_name"]),
+ dwdm=dict(type="str", aliases=["dwdm_name"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
)
@@ -385,7 +461,17 @@ def main():
storm_control_interface_policy = module.params.get("storm_control_interface_policy")
l2_interface_policy = module.params.get("l2_interface_policy")
port_security_policy = module.params.get("port_security_policy")
+ link_flap_policy = module.params.get("link_flap_policy")
+ link_level_flow_control = module.params.get("link_level_flow_control")
+ mac_sec_interface_policy = module.params.get("mac_sec_interface_policy")
+ copp_policy = module.params.get("copp_policy")
+ poe_interface_policy = module.params.get("poe_interface_policy")
+ port_authentication = module.params.get("port_authentication")
+ dwdm = module.params.get("dwdm")
aep = module.params.get("aep")
+ transceiver_policy = module.params.get("transceiver_policy")
+ sync_e_interface_policy = module.params.get("sync_e_interface_policy")
+
state = module.params.get("state")
name_alias = module.params.get("name_alias")
@@ -396,6 +482,21 @@ def main():
(leaf access port policy group), if used\
assign null to it (port_channel_policy: null)."
)
+ invalid_parameters = {
+ "transceiver_policy": "transceiver_policy is not a valid parameter for link/node (Port Channel, Virtual Port Channel),\
+ if used assign null to it (transceiver_policy: null).",
+ "port_authentication": "port_authentication is not a valid parameter for link/node (Port Channel, Virtual Port Channel),\
+ if used assign null to it (port_authentication: null).",
+ "dwdm": "dwdm is not a valid parameter for link/node (Port Channel, Virtual Port Channel),\
+ if used assign null to it (dwdm: null).",
+ "poe_interface_policy": "poe_interface_policy is not a valid parameter for link/node (Port Channel, Virtual Port Channel),\
+ if used assign null to it (poe_interface_policy: null).",
+ }
+
+ if lag_type in ["link", "node"]:
+ for param, message in invalid_parameters.items():
+ if locals().get(param) is not None:
+ aci.fail_json(message)
if lag_type == "leaf":
aci_class_name = "infraAccPortGrp"
@@ -407,7 +508,7 @@ def main():
)
# Reset for target_filter
lag_type = None
- elif lag_type in ("link", "node"):
+ else:
aci_class_name = "infraAccBndlGrp"
dn_name = "accbundle"
class_config_dict = dict(
@@ -523,9 +624,60 @@ def main():
),
),
),
+ dict(
+ infraRsLinkFlapPol=dict(
+ attributes=dict(
+ tnFabricLinkFlapPolName=link_flap_policy,
+ ),
+ ),
+ ),
+ dict(
+ infraRsQosLlfcIfPol=dict(
+ attributes=dict(
+ tnQosLlfcIfPolName=link_level_flow_control,
+ ),
+ ),
+ ),
+ dict(
+ infraRsMacsecIfPol=dict(
+ attributes=dict(
+ tnMacsecIfPolName=mac_sec_interface_policy,
+ ),
+ ),
+ ),
+ dict(
+ infraRsCoppIfPol=dict(
+ attributes=dict(
+ tnCoppIfPolName=copp_policy,
+ ),
+ ),
+ ),
]
- # Add infraRsattEntP binding only when aep was defined
+ child_classes = [
+ "infraRsAttEntP",
+ "infraRsCdpIfPol",
+ "infraRsFcIfPol",
+ "infraRsHIfPol",
+ "infraRsL2IfPol",
+ "infraRsL2PortSecurityPol",
+ "infraRsLacpPol",
+ "infraRsLldpIfPol",
+ "infraRsMcpIfPol",
+ "infraRsMonIfInfraPol",
+ "infraRsQosEgressDppIfPol",
+ "infraRsQosIngressDppIfPol",
+ "infraRsQosPfcIfPol",
+ "infraRsQosSdIfPol",
+ "infraRsStormctrlIfPol",
+ "infraRsStpIfPol",
+ "infraRsLinkFlapPol",
+ "infraRsQosLlfcIfPol",
+ "infraRsMacsecIfPol",
+ "infraRsCoppIfPol",
+ ]
+
+ # Add infraRsattEntP binding only when aep is defined
if aep is not None:
child_configs.append(
dict(
@@ -537,6 +689,79 @@ def main():
)
)
+ # Add infraRsSynceEthIfPol/infraRsSynceEthIfPolBndlGrp binding only when sync_e_interface_policy is defined
+ if sync_e_interface_policy is not None:
+ if lag_type is None:
+ child_configs.append(
+ dict(
+ infraRsSynceEthIfPol=dict(
+ attributes=dict(
+ tnSynceEthIfPolName=sync_e_interface_policy,
+ ),
+ ),
+ )
+ )
+ child_classes.append("infraRsSynceEthIfPol")
+ elif lag_type == "node" or lag_type == "link":
+ child_configs.append(
+ dict(
+ infraRsSynceEthIfPolBndlGrp=dict(
+ attributes=dict(
+ tnSynceEthIfPolName=sync_e_interface_policy,
+ ),
+ ),
+ )
+ )
+ child_classes.append("infraRsSynceEthIfPolBndlGrp")
+
+ # Add the children only when lag_type == leaf (Leaf Interface specific policies).
+ if lag_type is None:
+ # Add infraRsOpticsIfPol binding only when transceiver_policy was defined
+ if transceiver_policy is not None:
+ child_configs.append(
+ dict(
+ infraRsOpticsIfPol=dict(
+ attributes=dict(
+ tDn="uni/infra/{0}-{1}".format(transceiver_policy.get("type"), transceiver_policy.get("name")),
+ ),
+ ),
+ )
+ )
+ child_classes.append("infraRsOpticsIfPol")
+ if dwdm is not None:
+ child_configs.append(
+ dict(
+ infraRsDwdmIfPol=dict(
+ attributes=dict(
+ tnDwdmIfPolName=dwdm,
+ ),
+ ),
+ )
+ )
+ child_classes.append("infraRsDwdmIfPol")
+ if port_authentication is not None:
+ child_configs.append(
+ dict(
+ infraRsL2PortAuthPol=dict(
+ attributes=dict(
+ tnL2PortAuthPolName=port_authentication,
+ ),
+ ),
+ )
+ )
+ child_classes.append("infraRsL2PortAuthPol")
+ if poe_interface_policy is not None:
+ child_configs.append(
+ dict(
+ infraRsPoeIfPol=dict(
+ attributes=dict(
+ tnPoeIfPolName=poe_interface_policy,
+ ),
+ ),
+ )
+ )
+ child_classes.append("infraRsPoeIfPol")
+
aci.construct_url(
root_class=dict(
aci_class=aci_class_name,
@@ -544,24 +769,7 @@ def main():
module_object=policy_group,
target_filter={"name": policy_group, "lagT": lag_type},
),
- child_classes=[
- "infraRsAttEntP",
- "infraRsCdpIfPol",
- "infraRsFcIfPol",
- "infraRsHIfPol",
- "infraRsL2IfPol",
- "infraRsL2PortSecurityPol",
- "infraRsLacpPol",
- "infraRsLldpIfPol",
- "infraRsMcpIfPol",
- "infraRsMonIfInfraPol",
- "infraRsQosEgressDppIfPol",
- "infraRsQosIngressDppIfPol",
- "infraRsQosPfcIfPol",
- "infraRsQosSdIfPol",
- "infraRsStormctrlIfPol",
- "infraRsStpIfPol",
- ],
+ child_classes=child_classes,
)
aci.get_existing()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py
index 8c1299374..12f18c9ee 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py
@@ -23,7 +23,7 @@ options:
aliases: [ name ]
description:
description:
- - The description for the LLDP interface policy name.
+ - The description of the LLDP interface policy name.
type: str
aliases: [ descr ]
receive_state:
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py
index 1a25eced5..41f802e24 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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
@@ -23,7 +24,7 @@ options:
aliases: [ mcp_interface, name ]
description:
description:
- - The description for the MCP interface.
+ - The description of the MCP interface.
type: str
aliases: [ descr ]
admin_state:
@@ -31,6 +32,38 @@ options:
- Enable or disable admin state.
- The APIC defaults to C(true) when unset during creation.
type: bool
+ mcp_mode:
+ description:
+ - Instance MCP mode
+ - The APIC defaults to C(non_strict) when unset during creation.
+ type: str
+ choices: [ non_strict, strict ]
+ grace_period:
+ description:
+ - For strict mode, grace period timeout in sec during which early loop detection takes place.
+ type: int
+ aliases: [ gracePeriod ]
+ grace_period_millisec:
+ description:
+ - For strict mode, grace period timeout in millisec during which early loop detection takes place
+ type: int
+ aliases: [ grace_period_msec, gracePeriodMsec ]
+ init_delay_time:
+ description:
+ - For strict mode, delay time in seconds for mcp to wait before sending BPDUs.
+ - This gives time for STP on the external network to converge.
+ type: int
+ aliases: [ strict_init_delay_time, strictInitDelayTime ]
+ tx_frequence:
+ description:
+ - For strict mode, transmission frequency of MCP packets until grace period on each L2 interface in seconds.
+ type: int
+ aliases: [ strict_tx_freq, strictTxFreq ]
+ tx_frequence_millisec:
+ description:
+ - For strict mode, transmission frequency of MCP packets until grace period on each L2 interface in milliseconds
+ type: int
+ aliases: [strict_tx_freq_msec, strictTxFreqMsec ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -53,6 +86,7 @@ seealso:
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Dag Wieers (@dagwieers)
+- Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -204,6 +238,9 @@ from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+MATCH_MCP_MODE_MAPPING = {"non_strict": "off", "strict": "on"}
+
+
def main():
argument_spec = aci_argument_spec()
argument_spec.update(aci_annotation_spec())
@@ -212,6 +249,12 @@ def main():
mcp=dict(type="str", aliases=["mcp_interface", "name"]), # Not required for querying all objects
description=dict(type="str", aliases=["descr"]),
admin_state=dict(type="bool"),
+ mcp_mode=dict(type="str", choices=list(MATCH_MCP_MODE_MAPPING.keys())),
+ grace_period=dict(type="int", aliases=["gracePeriod"]),
+ grace_period_millisec=dict(type="int", aliases=["grace_period_msec", "gracePeriodMsec"]),
+ init_delay_time=dict(type="int", aliases=["strict_init_delay_time", "strictInitDelayTime"]),
+ tx_frequence=dict(type="int", aliases=["strict_tx_freq", "strictTxFreq"]),
+ tx_frequence_millisec=dict(type="int", aliases=["strict_tx_freq_msec", "strictTxFreqMsec"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
)
@@ -230,6 +273,12 @@ def main():
mcp = module.params.get("mcp")
description = module.params.get("description")
admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled")
+ mcp_mode = MATCH_MCP_MODE_MAPPING.get(module.params.get("mcp_mode"))
+ grace_period = module.params.get("grace_period")
+ grace_period_millisec = module.params.get("grace_period_millisec")
+ init_delay_time = module.params.get("init_delay_time")
+ tx_frequence = module.params.get("tx_frequence")
+ tx_frequence_millisec = module.params.get("tx_frequence_millisec")
state = module.params.get("state")
name_alias = module.params.get("name_alias")
@@ -251,6 +300,12 @@ def main():
name=mcp,
descr=description,
adminSt=admin_state,
+ mcpMode=mcp_mode,
+ gracePeriod=grace_period,
+ gracePeriodMsec=grace_period_millisec,
+ strictInitDelayTime=init_delay_time,
+ strictTxFreq=tx_frequence,
+ strictTxFreqMsec=tx_frequence_millisec,
nameAlias=name_alias,
),
)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py
index 040e13963..5fce567ae 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py
@@ -31,7 +31,7 @@ options:
aliases: [ ospf_interface, name ]
description:
description:
- - The description for the OSPF interface.
+ - The description of the OSPF interface.
type: str
aliases: [ descr ]
network_type:
@@ -40,7 +40,7 @@ options:
- OSPF supports broadcast and point-to-point.
- The APIC defaults to C(unspecified) when unset during creation.
type: str
- choices: [ bcast, p2p ]
+ choices: [ bcast, p2p, unspecified ]
cost:
description:
- The OSPF cost of the interface.
@@ -71,7 +71,7 @@ options:
interface is announced as part of the routing network.
type: list
elements: str
- choices: [ advert-subnet, bfd, mtu-ignore, passive ]
+ choices: [ advert-subnet, bfd, mtu-ignore, passive, unspecified ]
dead_interval:
description:
- The interval between hello packets from a neighbor before the router
@@ -95,7 +95,8 @@ options:
description:
- Whether prefix suppressions is enabled or disabled.
- The APIC defaults to C(inherit) when unset during creation.
- type: bool
+ type: str
+ choices: [ inherit, enable, disable ]
priority:
description:
- The priority for the OSPF interface profile.
@@ -302,12 +303,12 @@ def main():
tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
ospf=dict(type="str", aliases=["ospf_interface", "name"]), # Not required for querying all objects
description=dict(type="str", aliases=["descr"]),
- network_type=dict(type="str", choices=["bcast", "p2p"]),
+ network_type=dict(type="str", choices=["bcast", "p2p", "unspecified"]),
cost=dict(type="int"),
- controls=dict(type="list", elements="str", choices=["advert-subnet", "bfd", "mtu-ignore", "passive"]),
+ controls=dict(type="list", elements="str", choices=["advert-subnet", "bfd", "mtu-ignore", "passive", "unspecified"]),
dead_interval=dict(type="int"),
hello_interval=dict(type="int"),
- prefix_suppression=dict(type="bool"),
+ prefix_suppression=dict(type="str", choices=["inherit", "enable", "disable"]),
priority=dict(type="int"),
retransmit_interval=dict(type="int"),
transmit_delay=dict(type="int"),
@@ -330,11 +331,9 @@ def main():
ospf = module.params.get("ospf")
description = module.params.get("description")
name_alias = module.params.get("name_alias")
-
- if module.params.get("controls") is None:
- controls = None
- else:
- controls = ",".join(module.params.get("controls"))
+ network_type = module.params.get("network_type")
+ prefix_suppression = module.params.get("prefix_suppression")
+ controls = ",".join(module.params.get("controls")) if module.params.get("controls") else None
cost = module.params.get("cost")
if cost is not None and cost not in range(1, 451):
@@ -348,8 +347,6 @@ def main():
if hello_interval is not None and hello_interval not in range(1, 65536):
module.fail_json(msg="Parameter 'hello_interval' is only valid in range between 1 and 65536.")
- network_type = module.params.get("network_type")
- prefix_suppression = aci.boolean(module.params.get("prefix_suppression"), "enabled", "disabled")
priority = module.params.get("priority")
if priority is not None and priority not in range(0, 256):
module.fail_json(msg="Parameter 'priority' is only valid in range between 1 and 255.")
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py
index 028bd849b..c6422df9c 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py
@@ -23,7 +23,7 @@ options:
aliases: [ name ]
description:
description:
- - The description for the port channel.
+ - The description of the port channel.
type: str
aliases: [ descr ]
max_links:
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spine_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spine_policy_group.py
new file mode 100644
index 000000000..ab413ce3d
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spine_policy_group.py
@@ -0,0 +1,355 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, 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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_interface_policy_spine_policy_group
+short_description: Manage spine access interface policy groups (infra:SpAccPortGrp)
+description:
+- Manage spine access interface policy groups on Cisco ACI fabrics.
+options:
+ policy_group:
+ description:
+ - The name of the spine access interface policy group.
+ type: str
+ aliases: [ name, policy_group_name, spine_policy_group_name ]
+ description:
+ description:
+ - Description of the spine access interface
+ type: str
+ aliases: [ descr ]
+ name_alias:
+ description:
+ - The alias of the current object. This relates to the nameAlias field in ACI.
+ type: str
+ link_level_policy:
+ description:
+ - The name of the link level policy used by the spine access interface
+ type: str
+ aliases: [ link_level_policy_name ]
+ link_flap_policy:
+ description:
+ - The name of the link flap policy used by the spine access interface
+ type: str
+ aliases: [ link_flap_policy_name ]
+ cdp_policy:
+ description:
+ - The name of the cdp policy used by the spine access interface
+ type: str
+ aliases: [ cdp_policy_name ]
+ mac_sec_policy:
+ description:
+ - The name of the mac sec policy used by the spine access interface
+ type: str
+ aliases: [ mac_sec_policy_name ]
+ attached_entity_profile:
+ description:
+ - The name of the attached entity profile used by the spine access interface
+ type: str
+ aliases: [ aep_name, aep ]
+ 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
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC classes B(infra:SpAccPortGrp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Anvitha Jain (@anvjain)
+"""
+
+EXAMPLES = r"""
+- name: Create a Spine Interface Policy Group
+ cisco.aci.aci_interface_policy_spine_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: spinepolicygroupname
+ description: policygroupname description
+ link_level_policy: somelinklevelpolicy
+ link_flap_policy: somelinkflappolicy
+ cdp_policy: somecdppolicy
+ mac_sec_policy: somemacsecpolicy
+ attached_entity_profile: someattachedentityprofile
+ state: present
+ delegate_to: localhost
+
+- name: Query all Spine Access Port Policy Groups of type link
+ cisco.aci.aci_interface_policy_spine_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific Lead Access Port Policy Group
+ cisco.aci.aci_interface_policy_spine_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: spinepolicygroupname
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Delete an Interface policy Spine Policy Group
+ cisco.aci.aci_interface_policy_spine_policy_group:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ name: spinepolicygroupname
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ policy_group=dict(type="str", aliases=["policy_group_name", "spine_policy_group_name", "name"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ link_level_policy=dict(type="str", aliases=["link_level_policy_name"]),
+ link_flap_policy=dict(type="str", aliases=["link_flap_policy_name"]),
+ cdp_policy=dict(type="str", aliases=["cdp_policy_name"]),
+ mac_sec_policy=dict(type="str", aliases=["mac_sec_policy_name"]),
+ attached_entity_profile=dict(type="str", aliases=["aep_name", "aep"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["policy_group"]],
+ ["state", "present", ["policy_group"]],
+ ],
+ )
+
+ policy_group = module.params.get("policy_group")
+ description = module.params.get("description")
+ name_alias = module.params.get("name_alias")
+ link_level_policy = module.params.get("link_level_policy")
+ link_flap_policy = module.params.get("link_flap_policy")
+ cdp_policy = module.params.get("cdp_policy")
+ mac_sec_policy = module.params.get("mac_sec_policy")
+ attached_entity_profile = module.params.get("attached_entity_profile")
+ state = module.params.get("state")
+
+ aci = ACIModule(module)
+
+ class_config = dict(
+ name=policy_group,
+ descr=description,
+ nameAlias=name_alias,
+ )
+
+ child_configs = [
+ dict(
+ infraRsHIfPol=dict(
+ attributes=dict(
+ tnFabricHIfPolName=link_level_policy,
+ ),
+ ),
+ ),
+ dict(
+ infraRsLinkFlapPol=dict(
+ attributes=dict(
+ tnFabricLinkFlapPolName=link_flap_policy,
+ ),
+ ),
+ ),
+ dict(
+ infraRsCdpIfPol=dict(
+ attributes=dict(
+ tnCdpIfPolName=cdp_policy,
+ ),
+ ),
+ ),
+ dict(
+ infraRsMacsecIfPol=dict(
+ attributes=dict(
+ tnMacsecIfPolName=mac_sec_policy,
+ ),
+ ),
+ ),
+ ]
+
+ if attached_entity_profile is not None:
+ child_configs.append(
+ dict(
+ infraRsAttEntP=dict(
+ attributes=dict(
+ tDn="uni/infra/attentp-{0}".format(attached_entity_profile),
+ ),
+ ),
+ )
+ )
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="infraSpAccPortGrp",
+ aci_rn="infra/funcprof/spaccportgrp-{0}".format(policy_group),
+ module_object=policy_group,
+ target_filter={"name": policy_group},
+ ),
+ child_classes=[
+ "infraRsHIfPol",
+ "infraRsLinkFlapPol",
+ "infraRsCdpIfPol",
+ "infraRsMacsecIfPol",
+ "infraRsAttEntP",
+ ],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="infraSpAccPortGrp",
+ class_config=class_config,
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="infraSpAccPortGrp")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py
index 70cd11f8c..56f158b3d 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py
@@ -1,13 +1,18 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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": "certified"}
+ANSIBLE_METADATA = {
+ "metadata_version": "1.1",
+ "status": ["preview"],
+ "supported_by": "certified",
+}
DOCUMENTATION = r"""
---
@@ -46,20 +51,84 @@ options:
route_control:
description:
- Route Control enforcement direction. The only allowed values are export or import,export.
+ - The APIC defaults to C(export) when unset during creation.
type: list
elements: str
choices: [ export, import ]
aliases: [ route_control_enforcement ]
+ mpls:
+ description:
+ - Indicate whether MPLS (Multi-Protocol Label Switching) is enabled or not.
+ - The APIC defaults to C(no) when unset during creation.
+ type: str
+ choices: [ "no", "yes" ]
l3protocol:
description:
- - Routing protocol for the L3Out
+ - Routing protocol for the L3Out.
+ - Protocols already associated with an l3out must be provided again when the l3out is modified if the associated protocols are to be kept.
+ - The Protocols are otherwise deleted if not provided each time an l3out is modified.
+ - First example, to add BGP protocol to an l3out with OSPF protocol, the user must enter C([ bgp, ospf ]) even though "ospf" was provided before.
+ - Second example, to change the protocol from OSPF to EIGRP, the user must simply enter C([ eigrp ]) and the previous OSPF protocol will be deleted.
+ - To remove all existing protocols, the user must enter C([ static ]).
type: list
elements: str
choices: [ bgp, eigrp, ospf, pim, static ]
+ ospf:
+ description:
+ - Parameters for the OSPF protocol.
+ type: dict
+ suboptions:
+ area_cost:
+ description:
+ - The OSPF area cost.
+ - The APIC defaults to C(1) when unset during creation.
+ type: int
+ area_ctrl:
+ description:
+ - The controls of redistribution and summary LSA generation into NSSA and Stub areas.
+ - The APIC defaults to C(redistribute,summary) when unset during creation.
+ type: list
+ elements: str
+ choices: [ redistribute, summary, suppress-fa, unspecified ]
+ area_id:
+ description:
+ - The OSPF Area ID.
+ - An area is a logical collection of OSPF networks, routers, and links that have the same area identification.
+ - A router within an area must maintain a topological database for the area to which it belongs.
+ - The router doesn't have detailed information about network topology outside of its area, thereby reducing the size of its database.
+ - Areas limit the scope of route information distribution. It is not possible to do route update filtering within an area.
+ - The link-state database (LSDB) of routers within the same area must be synchronized and be exactly the same.
+ - However, route summarization and filtering is possible between different areas.
+ - The main benefit of creating areas is a reduction in the number of routes to propagate-by the filtering and the summarization of routes.
+ - Areas are identified by an area ID.
+ - Cisco IOS software supports area IDs expressed in IP address format or decimal format, for example, area 0.0.0.0 is equal to area 0.
+ - The APIC defaults to C(1) when unset during creation.
+ type: str
+ area_type:
+ description:
+ - The OSPF area type.
+ - The APIC defaults to C(nssa) when unset during creation.
+ type: str
+ choices: [ nssa, regular, stub ]
+ description:
+ description:
+ - Specifies the description of a policy component.
+ type: str
+ aliases: [ descr ]
+ multipod_internal:
+ description:
+ - Start OSPF in WAN instance instead of default.
+ - The APIC defaults to C(no) when unset during creation.
+ type: str
+ choices: [ "no", "yes" ]
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
asn:
description:
- The AS number for the L3Out.
- - Only applicable when using 'eigrp' as the l3protocol
+ - Only applicable when using 'eigrp' as the l3protocol.
type: int
aliases: [ as_number ]
description:
@@ -85,7 +154,7 @@ extends_documentation_fragment:
notes:
- The C(tenant) and C(domain) and C(vrf) used must exist before using this module in your playbook.
- The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_domain) and M(cisco.aci.aci_vrf) modules can be used for this.
+- The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_domain) and M(cisco.aci.aci_vrf) modules can be used for this.
seealso:
- module: cisco.aci.aci_tenant
- module: cisco.aci.aci_domain
@@ -95,6 +164,7 @@ seealso:
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Rostyslav Davydenko (@rost-d)
+- Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -109,6 +179,12 @@ EXAMPLES = r"""
domain: l3dom_prod
vrf: prod
l3protocol: ospf
+ ospf:
+ area_cost: 1
+ area_ctrl: [ summary, redistribute ]
+ area_id: 0.0.0.1
+ area_type: regular
+ multipod_internal: no
state: present
delegate_to: localhost
@@ -132,6 +208,15 @@ EXAMPLES = r"""
state: query
delegate_to: localhost
register: query_result
+
+- name: Query all L3Outs
+ cisco.aci.aci_l3out:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_all_result
"""
RETURN = r"""
@@ -240,7 +325,13 @@ url:
"""
from ansible.module_utils.basic import AnsibleModule
-from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.aci import (
+ ACIModule,
+ aci_argument_spec,
+ aci_annotation_spec,
+ aci_owner_spec,
+)
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ospf_spec
def main():
@@ -253,7 +344,13 @@ def main():
domain=dict(type="str", aliases=["ext_routed_domain_name", "routed_domain"]),
vrf=dict(type="str", aliases=["vrf_name"]),
description=dict(type="str", aliases=["descr"]),
- route_control=dict(type="list", elements="str", choices=["export", "import"], aliases=["route_control_enforcement"]),
+ route_control=dict(
+ type="list",
+ elements="str",
+ choices=["export", "import"],
+ aliases=["route_control_enforcement"],
+ ),
+ mpls=dict(type="str", choices=["no", "yes"]),
dscp=dict(
type="str",
choices=[
@@ -283,7 +380,12 @@ def main():
],
aliases=["target"],
),
- l3protocol=dict(type="list", elements="str", choices=["bgp", "eigrp", "ospf", "pim", "static"]),
+ l3protocol=dict(
+ type="list",
+ elements="str",
+ choices=["bgp", "eigrp", "ospf", "pim", "static"],
+ ),
+ ospf=dict(type="dict", options=ospf_spec()),
asn=dict(type="int", aliases=["as_number"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
@@ -304,9 +406,11 @@ def main():
domain = module.params.get("domain")
dscp = module.params.get("dscp")
description = module.params.get("description")
- enforceRtctrl = module.params.get("route_control")
+ route_control = module.params.get("route_control")
+ mpls = module.params.get("mpls")
vrf = module.params.get("vrf")
l3protocol = module.params.get("l3protocol")
+ ospf = module.params.get("ospf")
asn = module.params.get("asn")
state = module.params.get("state")
tenant = module.params.get("tenant")
@@ -317,16 +421,62 @@ def main():
module.fail_json(msg="Parameter 'asn' is required when l3protocol is 'eigrp'")
if "eigrp" not in l3protocol and asn is not None:
module.warn("Parameter 'asn' is only applicable when l3protocol is 'eigrp'. The ASN will be ignored")
+ if "ospf" not in l3protocol and ospf is not None:
+ module.warn("Parameter 'ospf' is only applicable when l3protocol is 'ospf'. The OPSF specifications will be ignored")
- enforce_ctrl = ""
- if enforceRtctrl is not None:
- if len(enforceRtctrl) == 1 and enforceRtctrl[0] == "import":
+ enforce_ctrl = None
+ if route_control is not None:
+ if len(route_control) == 1 and route_control[0] == "import":
aci.fail_json("The route_control parameter is invalid: allowed options are export or import,export only")
- elif len(enforceRtctrl) == 1 and enforceRtctrl[0] == "export":
- enforce_ctrl = "export"
else:
- enforce_ctrl = "export,import"
- child_classes = ["l3extRsL3DomAtt", "l3extRsEctx", "bgpExtP", "ospfExtP", "eigrpExtP", "pimExtP"]
+ enforce_ctrl = ",".join(route_control)
+
+ child_classes = [
+ "l3extRsL3DomAtt",
+ "l3extRsEctx",
+ "bgpExtP",
+ "ospfExtP",
+ "eigrpExtP",
+ "pimExtP",
+ ]
+
+ child_configs = [
+ dict(l3extRsL3DomAtt=dict(attributes=dict(tDn="uni/l3dom-{0}".format(domain)))),
+ dict(l3extRsEctx=dict(attributes=dict(tnFvCtxName=vrf))),
+ ]
+ if l3protocol is not None:
+ l3protocol_child_configs = dict(
+ bgp=dict(bgpExtP=dict(attributes=dict(status="deleted"))),
+ eigrp=dict(eigrpExtP=dict(attributes=dict(status="deleted"))),
+ ospf=dict(ospfExtP=dict(attributes=dict(status="deleted"))),
+ pim=dict(pimExtP=dict(attributes=dict(status="deleted"))),
+ )
+ for protocol in l3protocol:
+ if protocol == "bgp":
+ l3protocol_child_configs["bgp"] = dict(bgpExtP=dict(attributes=dict(descr="")))
+ elif protocol == "eigrp":
+ l3protocol_child_configs["eigrp"] = dict(eigrpExtP=dict(attributes=dict(asn=asn)))
+ elif protocol == "ospf":
+ if isinstance(ospf, dict):
+ ospf["area_ctrl"] = ",".join(ospf.get("area_ctrl"))
+ l3protocol_child_configs["ospf"] = dict(
+ ospfExtP=dict(
+ attributes=dict(
+ areaCost=ospf.get("area_cost"),
+ areaCtrl=ospf.get("area_ctrl"),
+ areaId=ospf.get("area_id"),
+ areaType=ospf.get("area_type"),
+ descr=ospf.get("description"),
+ multipodInternal=ospf.get("multipod_internal"),
+ nameAlias=ospf.get("name_alias"),
+ )
+ )
+ )
+ else:
+ l3protocol_child_configs["ospf"] = dict(ospfExtP=dict(attributes=dict(descr="")))
+ elif protocol == "pim":
+ l3protocol_child_configs["pim"] = dict(pimExtP=dict(attributes=dict(descr="")))
+ child_configs.extend(list(l3protocol_child_configs.values()))
aci.construct_url(
root_class=dict(
@@ -346,29 +496,14 @@ def main():
aci.get_existing()
- child_configs = [
- dict(l3extRsL3DomAtt=dict(attributes=dict(tDn="uni/l3dom-{0}".format(domain)))),
- dict(l3extRsEctx=dict(attributes=dict(tnFvCtxName=vrf))),
- ]
- if l3protocol is not None:
- for protocol in l3protocol:
- if protocol == "bgp":
- child_configs.append(dict(bgpExtP=dict(attributes=dict(descr="", nameAlias=""))))
- elif protocol == "eigrp":
- child_configs.append(dict(eigrpExtP=dict(attributes=dict(descr="", nameAlias="", asn=asn))))
- elif protocol == "ospf":
- child_configs.append(dict(ospfExtP=dict(attributes=dict(descr="", nameAlias=""))))
- elif protocol == "pim":
- child_configs.append(dict(pimExtP=dict(attributes=dict(descr="", nameAlias=""))))
-
if state == "present":
aci.payload(
aci_class="l3extOut",
class_config=dict(
name=l3out,
descr=description,
- dn="uni/tn-{0}/out-{1}".format(tenant, l3out),
enforceRtctrl=enforce_ctrl,
+ mplsEnabled=mpls,
targetDscp=dscp,
nameAlias=name_alias,
),
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py
index 83bb9ce14..4719c42ca 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py
@@ -130,6 +130,19 @@ options:
- Name of the Route Control Profile direction.
type: str
required: true
+ local_as_number_config:
+ description:
+ - The local Autonomous System Number (ASN) configuration of the L3Out BGP Peer.
+ - The APIC defaults to C(none) when unset during creation.
+ type: str
+ choices: [ dual-as, no-prepend, none, replace-as ]
+ aliases: [ local_as_num_config ]
+ local_as_number:
+ description:
+ - The local Autonomous System Number (ASN) of the L3Out BGP Peer.
+ - The APIC defaults to 0 when unset during creation.
+ type: int
+ aliases: [ local_as_num ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -439,6 +452,8 @@ def main():
elements="dict",
options=route_control_profile_spec(),
),
+ local_as_number_config=dict(type="str", choices=["dual-as", "no-prepend", "none", "replace-as"], aliases=["local_as_num_config"]),
+ local_as_number=dict(type="int", aliases=["local_as_num"]),
)
module = AnsibleModule(
@@ -470,6 +485,8 @@ def main():
admin_state = module.params.get("admin_state")
allow_self_as_count = module.params.get("allow_self_as_count")
route_control_profiles = module.params.get("route_control_profiles")
+ local_as_number_config = module.params.get("local_as_number_config")
+ local_as_number = module.params.get("local_as_number")
aci = ACIModule(module)
if node_id:
@@ -492,6 +509,15 @@ def main():
)
)
+ if local_as_number_config or local_as_number:
+ child_configs.append(
+ dict(
+ bgpLocalAsnP=dict(
+ attributes=dict(asnPropagate=local_as_number_config, localAsn=local_as_number),
+ ),
+ )
+ )
+
if route_control_profiles:
child_classes.append("bgpRsPeerToProfile")
for profile in route_control_profiles:
@@ -514,22 +540,17 @@ def main():
)
)
- subclass_3 = None
- subclass_4 = None
- subclass_5 = None
-
- bgp_peer_profile_dict = None
-
- if peer_ip or state == "query":
- bgp_peer_profile_dict = dict(
- aci_class="bgpPeerP",
- aci_rn="peerP-[{0}]".format(peer_ip),
- module_object=peer_ip,
- target_filter={"addr": peer_ip},
- )
+ bgp_peer_profile_dict = dict(
+ aci_class="bgpPeerP",
+ aci_rn="peerP-[{0}]".format(peer_ip),
+ module_object=peer_ip,
+ target_filter={"addr": peer_ip},
+ )
if interface_profile is None:
subclass_3 = bgp_peer_profile_dict
+ subclass_4 = None
+ subclass_5 = None
else:
subclass_3 = dict(
aci_class="l3extLIfP",
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_protocol_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_protocol_profile.py
new file mode 100644
index 000000000..aacf22d3d
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_protocol_profile.py
@@ -0,0 +1,326 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_l3out_bgp_protocol_profile
+short_description: Manage BGP Protocol Profile (bgp:ProtP)
+description:
+- Manage BGP Protocol Profile for The Logical Node Profiles on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ l3out:
+ description:
+ - The name of an existing L3Out.
+ type: str
+ aliases: [ l3out_name ]
+ node_profile:
+ description:
+ - The name of an existing logical node profile.
+ type: str
+ aliases: [ node_profile_name, logical_node ]
+ bgp_timers_policy:
+ description:
+ - The name of an existing bgp timers policy.
+ type: str
+ aliases: [ bgp_timers_policy_name ]
+ bgp_best_path_policy:
+ description:
+ - The name of the bgp best path control policy.
+ type: str
+ aliases: [ bgp_best_path_policy_name ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant), C(l3out) and C(node_profile) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out) and M(cisco.aci.aci_l3out_logical_node_profile) modules can be used for this.
+- If C(bgp_timers_policy) and/or C(bgp_best_path_policy) are used, they must exist before using this module in your playbook.
+ The M(cisco.aci.aci_bgp_timers_policy) and M(cisco.aci.aci_bgp_best_path_policy) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_l3out
+- module: cisco.aci.aci_l3out_logical_node_profile
+- module: cisco.aci.aci_bgp_timers_policy
+- module: cisco.aci.aci_bgp_best_path_policy
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(bgp:ProtP).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+"""
+
+EXAMPLES = r"""
+- name: Create a l3out BGP protocol profile
+ cisco.aci.l3out_bgp_protocol_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ l3out: prod_l3out
+ node_profile: prod_node_profile
+ bgp_timers_policy: prod_bgp_timers_policy
+ bgp_best_path_policy: prod_bgp_best_path_policy
+ state: present
+ delegate_to: localhost
+
+- name: Delete a l3out BGP protocol profile
+ cisco.aci.l3out_bgp_protocol_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ l3out: prod_l3out
+ node_profile: prod_node_profile
+ state: absent
+ delegate_to: localhost
+
+- name: Query all l3out BGP protocol profiles
+ cisco.aci.l3out_bgp_protocol_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific l3out BGP protocol profile
+ cisco.aci.l3out_bgp_protocol_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ l3out: prod_l3out
+ node_profile: prod_node_profile
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects
+ node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), # Not required for querying all objects
+ bgp_timers_policy=dict(type="str", aliases=["bgp_timers_policy_name"]),
+ bgp_best_path_policy=dict(type="str", aliases=["bgp_best_path_policy_name"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["tenant", "l3out", "node_profile"]],
+ ["state", "present", ["tenant", "l3out", "node_profile"]],
+ ],
+ )
+
+ bgp_timers_policy = module.params.get("bgp_timers_policy")
+ bgp_best_path_policy = module.params.get("bgp_best_path_policy")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ l3out = module.params.get("l3out")
+ node_profile = module.params.get("node_profile")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ child_classes = ["bgpRsBgpNodeCtxPol", "bgpRsBestPathCtrlPol"]
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="l3extOut",
+ aci_rn="out-{0}".format(l3out),
+ module_object=l3out,
+ target_filter={"name": l3out},
+ ),
+ subclass_2=dict(
+ aci_class="l3extLNodeP",
+ aci_rn="lnodep-{0}".format(node_profile),
+ module_object=node_profile,
+ target_filter={"name": node_profile},
+ ),
+ subclass_3=dict(
+ aci_class="bgpProtP",
+ aci_rn="protp",
+ module_object="",
+ target_filter={"name": ""},
+ ),
+ child_classes=child_classes,
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ child_configs = []
+ if bgp_timers_policy is not None:
+ child_configs.append(dict(bgpRsBgpNodeCtxPol=dict(attributes=dict(tnBgpCtxPolName=bgp_timers_policy))))
+ if bgp_best_path_policy is not None:
+ child_configs.append(dict(bgpRsBestPathCtrlPol=dict(attributes=dict(tnBgpBestPathCtrlPolName=bgp_best_path_policy))))
+
+ aci.payload(
+ aci_class="bgpProtP",
+ class_config=dict(
+ nameAlias=name_alias,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="bgpProtP")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py
index 514708bd7..acaa7ea9e 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py
@@ -49,6 +49,38 @@ options:
type: str
choices: [ AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43, CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, unspecified ]
aliases: [ target ]
+ contract_exception_tag:
+ description:
+ - The tag for contract exception.
+ type: str
+ qos_class:
+ description:
+ - The priority class identifier.
+ type: str
+ choices: [ level1, level2, level3, level4, level5, level6, unspecified ]
+ default: level3
+ intra_ext_epg_isolation:
+ description:
+ - The preferred policy control.
+ type: str
+ choices: [ enforced, unenforced ]
+ route_control_profiles:
+ description:
+ - The route control profiles.
+ type: dict
+ suboptions:
+ import_profile:
+ description:
+ - The route control profile whose direction is import.
+ - To remove a route import policy pass an empty string (see Examples).
+ type: str
+ aliases: [ import ]
+ export_profile:
+ description:
+ - The route control profile whose direction is export.
+ - To remove a route export policy pass an empty string (see Examples).
+ type: str
+ aliases: [ export ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -88,6 +120,11 @@ EXAMPLES = r"""
l3out: prod_l3out
name: prod_extepg
description: ExtEpg for Production L3Out
+ contract_exception_tag: test
+ qos_class: level6
+ route_control_profiles:
+ import_profile: test1
+ export_profile: test2
state: present
delegate_to: localhost
@@ -102,6 +139,20 @@ EXAMPLES = r"""
state: absent
delegate_to: localhost
+- name: Delete the export route control profile in an ExtEpg
+ cisco.aci.aci_l3out_extepg:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: production
+ l3out: prod_l3out
+ name: prod_extepg
+ route_control_profiles:
+ import_profile: test1
+ export_profile: ""
+ state: present
+ delegate_to: localhost
+
- name: Query ExtEpg information
cisco.aci.aci_l3out_extepg:
host: apic
@@ -262,6 +313,13 @@ def main():
],
aliases=["target"],
),
+ contract_exception_tag=dict(type="str"),
+ qos_class=dict(type="str", default="level3", choices=["level1", "level2", "level3", "level4", "level5", "level6", "unspecified"]),
+ intra_ext_epg_isolation=dict(type="str", choices=["enforced", "unenforced"]),
+ route_control_profiles=dict(
+ type="dict",
+ options=dict(import_profile=dict(type="str", aliases=["import"]), export_profile=dict(type="str", aliases=["export"])),
+ ),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
)
@@ -284,6 +342,10 @@ def main():
preferred_group = aci.boolean(module.params.get("preferred_group"), "include", "exclude")
dscp = module.params.get("dscp")
state = module.params.get("state")
+ contract_exception_tag = module.params.get("contract_exception_tag")
+ qos_class = module.params.get("qos_class")
+ intra_ext_epg_isolation = module.params.get("intra_ext_epg_isolation")
+ route_control_profiles = module.params.get("route_control_profiles")
name_alias = module.params.get("name_alias")
aci.construct_url(
@@ -305,22 +367,49 @@ def main():
module_object=extepg,
target_filter={"name": extepg},
),
+ child_classes=["fvRsSecInherited", "l3extRsInstPToProfile"],
)
aci.get_existing()
if state == "present":
- aci.payload(
- aci_class="l3extInstP",
- class_config=dict(
- name=extepg,
- descr=description,
- prefGrMemb=preferred_group,
- targetDscp=dscp,
- nameAlias=name_alias,
- ),
+ child_configs = []
+ if route_control_profiles:
+ for key, name in route_control_profiles.items():
+ if "_profile" in key:
+ direction = key.rstrip("_profile")
+ if name == "" and isinstance(aci.existing, list) and len(aci.existing) > 0:
+ for child in aci.existing[0].get("l3extInstP", {}).get("children", {}):
+ if child.get("l3extRsInstPToProfile") and child.get("l3extRsInstPToProfile").get("attributes").get("direction") == direction:
+ child_configs.append(
+ dict(
+ l3extRsInstPToProfile=dict(
+ attributes=dict(
+ status="deleted",
+ direction=direction,
+ tnRtctrlProfileName=child.get("l3extRsInstPToProfile").get("attributes").get("tnRtctrlProfileName"),
+ )
+ )
+ )
+ )
+ elif name:
+ child_configs.append(dict(l3extRsInstPToProfile=dict(attributes=dict(direction=direction, tnRtctrlProfileName=name))))
+
+ class_config = dict(
+ name=extepg,
+ descr=description,
+ prefGrMemb=preferred_group,
+ targetDscp=dscp,
+ nameAlias=name_alias,
+ prio=qos_class,
+ exceptionTag=contract_exception_tag,
)
+ if intra_ext_epg_isolation:
+ class_config.update(pcEnfPref=intra_ext_epg_isolation)
+
+ aci.payload(aci_class="l3extInstP", class_config=class_config, child_configs=child_configs)
+
aci.get_diff(aci_class="l3extInstP")
aci.post_config()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py
index a87cb664c..db5890205 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py
@@ -40,14 +40,22 @@ options:
description:
- Name of the neighbor discovery interface policy.
type: str
+ default: ""
egress_dpp_policy:
description:
- Name of the egress data plane policing policy.
type: str
+ default: ""
ingress_dpp_policy:
description:
- Name of the ingress data plane policing policy.
type: str
+ default: ""
+ description:
+ description:
+ - The description for the logical interface profile.
+ type: str
+ aliases: [ descr ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -241,6 +249,7 @@ def main():
egress_dpp_policy=dict(type="str", default=""),
ingress_dpp_policy=dict(type="str", default=""),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ description=dict(type="str", aliases=["descr"]),
)
module = AnsibleModule(
@@ -259,6 +268,7 @@ def main():
nd_policy = module.params.get("nd_policy")
egress_dpp_policy = module.params.get("egress_dpp_policy")
ingress_dpp_policy = module.params.get("ingress_dpp_policy")
+ description = module.params.get("description")
state = module.params.get("state")
aci = ACIModule(module)
@@ -299,7 +309,7 @@ def main():
dict(l3extRsIngressQosDppPol=dict(attributes=dict(tnQosDppPolName=ingress_dpp_policy))),
dict(l3extRsEgressQosDppPol=dict(attributes=dict(tnQosDppPolName=egress_dpp_policy))),
]
- aci.payload(aci_class="l3extLIfP", class_config=dict(name=interface_profile), child_configs=child_configs)
+ aci.payload(aci_class="l3extLIfP", class_config=dict(name=interface_profile, descr=description), child_configs=child_configs)
aci.get_diff(aci_class="l3extLIfP")
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py
index 92361230f..798a82111 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py
@@ -52,7 +52,6 @@ options:
- OSPF authentication key.
- When using C(ospf_auth_key) this module will always show as C(changed) as the module cannot know what the currently configured key is.
type: str
- default: ""
state:
description:
- Use C(present) or C(absent) for adding or removing.
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py
index 2bfc876ad..33c8a22a5 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py
@@ -49,6 +49,12 @@ options:
type: str
choices: [ 'yes', 'no' ]
default: 'yes'
+ loopback_address:
+ description:
+ - The loopback IP address.
+ - A configured loopback address can be removed by passing an empty string (see Examples).
+ type: str
+ aliases: [ loopback ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -82,9 +88,23 @@ EXAMPLES = r"""
pod_id: 1
node_id: 111
router_id: 111.111.111.111
+ loopback_address: 111.111.111.112
state: present
delegate_to: localhost
+- name: Remove a loopback address from a node in node profile
+ cisco.aci.aci_l3out_logical_node:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ tenant: my_tenant
+ l3out: my_l3out
+ node_profile: my_node_profile
+ pod_id: 1
+ node_id: 111
+ loopback_address: ""
+ delegate_to: localhost
+
- name: Delete a node from a node profile
cisco.aci.aci_l3out_logical_node:
host: apic
@@ -243,6 +263,7 @@ def main():
node_id=dict(type="int"),
router_id=dict(type="str"),
router_id_as_loopback=dict(type="str", default="yes", choices=["yes", "no"]),
+ loopback_address=dict(type="str", aliases=["loopback"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
)
@@ -262,6 +283,7 @@ def main():
node_id = module.params.get("node_id")
router_id = module.params.get("router_id")
router_id_as_loopback = module.params.get("router_id_as_loopback")
+ loopback_address = module.params.get("loopback_address")
state = module.params.get("state")
tdn = None
@@ -270,6 +292,10 @@ def main():
aci = ACIModule(module)
+ child_classes = ["l3extLoopBackIfP"]
+
+ child_configs = []
+
aci.construct_url(
root_class=dict(
aci_class="fvTenant",
@@ -295,12 +321,25 @@ def main():
module_object=tdn,
target_filter={"name": tdn},
),
+ child_classes=child_classes,
)
aci.get_existing()
if state == "present":
- aci.payload(aci_class="l3extRsNodeL3OutAtt", class_config=dict(rtrId=router_id, rtrIdLoopBack=router_id_as_loopback, tDn=tdn))
+ if loopback_address is not None:
+ if loopback_address == "" and isinstance(aci.existing, list) and len(aci.existing) > 0:
+ for child in aci.existing[0].get("l3extRsNodeL3OutAtt", {}).get("children", []):
+ previous_loopback_address = child.get("l3extLoopBackIfP", {}).get("attributes", {}).get("addr")
+ child_configs.append(dict(l3extLoopBackIfP=dict(attributes=dict(addr=previous_loopback_address, status="deleted"))))
+ elif loopback_address:
+ child_configs.append(dict(l3extLoopBackIfP=dict(attributes=dict(addr=loopback_address))))
+
+ aci.payload(
+ aci_class="l3extRsNodeL3OutAtt",
+ class_config=dict(rtrId=router_id, rtrIdLoopBack=router_id_as_loopback, tDn=tdn),
+ child_configs=child_configs,
+ )
aci.get_diff(aci_class="l3extRsNodeL3OutAtt")
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py
index d3aa5e8f7..10eefd018 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py
@@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported
DOCUMENTATION = r"""
---
module: aci_l3out_logical_node_profile
-short_description: Manage Layer 3 Outside (L3Out) logical node profiles (l3extLNodeP:lnodep)
+short_description: Manage Layer 3 Outside (L3Out) logical node profiles (l3ext:LNodeP)
description:
- Manage Layer 3 Outside (L3Out) logical node profiles on Cisco ACI fabrics.
options:
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py
index 09a67b8ee..039593366 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py
@@ -61,9 +61,9 @@ options:
bfd:
description:
- Determines if bfd is required for route control.
- - The APIC defaults to C(None) when unset during creation.
+ - The APIC defaults to C(unspecified) when unset during creation.
type: str
- choices: [ bfd, None ]
+ choices: [ bfd, unspecified ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -264,7 +264,7 @@ def main():
prefix=dict(type="str", aliases=["route"]),
track_policy=dict(type="str"),
preference=dict(type="int"),
- bfd=dict(type="str", choices=["bfd", None]),
+ bfd=dict(type="str", choices=["bfd", "unspecified"]),
description=dict(type="str", aliases=["descr"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
@@ -350,7 +350,7 @@ def main():
tDn = "uni/tn-{0}/tracklist-{1}".format(tenant, track_policy)
child_configs.append({"ipRsRouteTrack": {"attributes": {"tDn": tDn}}})
- aci.payload(aci_class="ipRouteP", class_config=class_config, child_configs=child_configs),
+ aci.payload(aci_class="ipRouteP", class_config=class_config, child_configs=child_configs)
aci.get_diff(aci_class="ipRouteP")
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py
index 98b0df9f2..d4d249e25 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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
@@ -9,40 +10,65 @@ __metaclass__ = type
ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
-DOCUMENTATION = """
+DOCUMENTATION = r"""
module: aci_maintenance_group
-short_description: This creates an ACI maintenance group
-notes:
- - a maintenance policy (aci_maintenance_policy must be created prior to creating an aci maintenance group
+short_description: This creates an ACI maintenance group (maint:MaintGrp)
description:
- - This modules creates an ACI maintenance group
+ - This modules creates an ACI maintenance group.
options:
group:
description:
- - This is the name of the group
+ - The name of the maintenance group.
type: str
policy:
description:
- - This is the name of the policy that was created using aci_maintenance_policy
+ - The name of the maintenance policy.
+ type: str
+ aliases: [ maintenancepol ]
+ firmware_nodes_type:
+ description:
+ - The firmware type of nodes in the maintenance group.
+ - The APIC defaults to C(switch) when unset during creation.
+ type: str
+ choices: [ c_apic_patch, catalog, config, controller, controller_patch, plugin, plugin_package, switch, switch_patch, vpod ]
+ type_group:
+ description:
+ - The type of the maintenance group.
+ - The APIC defaults to C(range) when unset during creation.
+ type: str
+ choices: [ all, all_in_pod, range ]
+ description:
+ description:
+ - Description of the maintenance group.
type: str
+ aliases: [ descr ]
state:
description:
- - Use C(present) or C(absent) for adding or removing.
- - Use C(query) for listing an object or multiple objects.
+ - 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
name_alias:
description:
- - The alias for the current object. This relates to the nameAlias field in ACI.
+ - The alias for the current object. This relates to the nameAlias field in ACI.
type: str
extends_documentation_fragment:
- cisco.aci.aci
- cisco.aci.annotation
- cisco.aci.owner
+notes:
+- The C(policy) used must exist before using this module in your playbook.
+- The M(cisco.aci.aci_maintenance_policy) module can be used for this.
+seealso:
+- module: cisco.aci.aci_maintenance_policy
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(maint:MaintGrp).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Steven Gerhart (@sgerhart)
+ - Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -85,7 +111,7 @@ EXAMPLES = r"""
register: query_result
"""
-RETURN = """
+RETURN = r"""
current:
description: The existing configuration from the APIC after the module has finished
returned: success
@@ -191,6 +217,7 @@ url:
"""
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_TYPE_GROUP_MAPPING, MATCH_FIRMWARE_NODES_TYPE_MAPPING
from ansible.module_utils.basic import AnsibleModule
@@ -200,7 +227,10 @@ def main():
argument_spec.update(aci_owner_spec())
argument_spec.update(
group=dict(type="str"), # Not required for querying all objects
- policy=dict(type="str"), # Not required for querying all objects
+ policy=dict(type="str", aliases=["maintenancepol"]), # Not required for querying all objects
+ firmware_nodes_type=dict(type="str", choices=list(MATCH_FIRMWARE_NODES_TYPE_MAPPING.keys())),
+ type_group=dict(type="str", choices=list(MATCH_TYPE_GROUP_MAPPING.keys())),
+ description=dict(type="str", aliases=["descr"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
)
@@ -217,6 +247,9 @@ def main():
state = module.params.get("state")
group = module.params.get("group")
policy = module.params.get("policy")
+ firmware_nodes_type = MATCH_FIRMWARE_NODES_TYPE_MAPPING.get(module.params.get("firmware_nodes_type"))
+ type_group = MATCH_TYPE_GROUP_MAPPING.get(module.params.get("type_group"))
+ description = module.params.get("description")
name_alias = module.params.get("name_alias")
aci = ACIModule(module)
aci.construct_url(
@@ -236,6 +269,9 @@ def main():
aci_class="maintMaintGrp",
class_config=dict(
name=group,
+ fwtype=firmware_nodes_type,
+ type=type_group,
+ descr=description,
nameAlias=name_alias,
),
child_configs=[
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py
index 272143410..be97ede40 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py
@@ -1,5 +1,7 @@
#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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
@@ -11,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported
DOCUMENTATION = r"""
---
module: aci_maintenance_group_node
-short_description: Manage maintenance group nodes
+short_description: Manage maintenance group nodes (fabric:NodeBlk)
description:
- Manage maintenance group nodes
options:
@@ -22,7 +24,7 @@ options:
node:
description:
- The node to be added to the maintenance group.
- - The value equals the nodeid.
+ - The value equals the NodeID.
type: str
state:
description:
@@ -39,8 +41,14 @@ extends_documentation_fragment:
- cisco.aci.aci
- cisco.aci.annotation
+seealso:
+- module: cisco.aci.aci_maintenance_group
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(fabric:NodeBlk).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Steven Gerhart (@sgerhart)
+- Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -218,6 +226,9 @@ def main():
group = module.params.get("group")
node = module.params.get("node")
name_alias = module.params.get("name_alias")
+ block_name = None
+ if node is not None:
+ block_name = "blk{0}-{0}".format(node)
aci = ACIModule(module)
aci.construct_url(
@@ -230,7 +241,7 @@ def main():
subclass_1=dict(
aci_class="fabricNodeBlk",
aci_rn="nodeblk-blk{0}-{0}".format(node),
- target_filter={"name": "blk{0}-{0}".format(node)},
+ target_filter={"name": block_name},
module_object=node,
),
)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py
index ddc62df09..987dc2e35 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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
@@ -12,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported
DOCUMENTATION = r"""
---
module: aci_maintenance_policy
-short_description: Manage firmware maintenance policies
+short_description: Manage firmware maintenance policies (maint:MaintP)
description:
- Manage maintenance policies that defines behavior during an ACI upgrade.
options:
@@ -21,12 +22,13 @@ options:
- The name for the maintenance policy.
type: str
aliases: [ maintenance_policy ]
- runmode:
+ run_mode:
description:
- Whether the system pauses on error or just continues through it.
+ - The APIC defaults to C(pauseOnlyOnFailures) when unset during creation.
type: str
- choices: [ pauseOnlyOnFailures, pauseNever ]
- default: pauseOnlyOnFailures
+ choices: [ pause_always_between_sets, pause_only_on_failures, pause_never, pauseOnlyOnFailures, pauseNever ]
+ aliases: [ runmode ]
graceful:
description:
- Whether the system will bring down the nodes gracefully during an upgrade, which reduces traffic lost.
@@ -36,17 +38,71 @@ options:
description:
- The name of scheduler that is applied to the policy.
type: str
- adminst:
+ admin_state:
description:
- - Will trigger an immediate upgrade for nodes if adminst is set to triggered.
+ - The administrative state of the executable policies.
+ - Will trigger an immediate upgrade for nodes if C(admin_state) is set to triggered.
+ - The APIC defaults to C(untriggered) when unset during creation.
type: str
choices: [ triggered, untriggered ]
- default: untriggered
- ignoreCompat:
+ aliases: [ adminst ]
+ download_state:
+ description:
+ - The download state of the executable policies.
+ - The APIC defaults to C(untriggered) when unset during creation.
+ type: str
+ choices: [ triggered, untriggered ]
+ notify_condition:
+ description:
+ - Specifies under what pause condition will admin be notified via email/text as configured.
+ - This notification mechanism is independent of events/faults.
+ - The APIC defaults to C(notifyOnlyOnFailures) when unset during creation.
+ type: str
+ choices: [ notify_always_between_sets, notify_never, notify_only_on_failures ]
+ smu_operation:
+ description:
+ - Specifies SMU operation.
+ type: str
+ choices: [ smu_install, smu_uninstall ]
+ smu_operation_flags:
+ description:
+ - Specifies SMU operation flags
+ - Indicates if node should be reloaded immediately or skip auto reload on SMU Install/Uninstall.
+ type: str
+ choices: [ smu_reload_immediate, smu_reload_skip ]
+ sr_upgrade:
+ description:
+ - The SR firware upgrade.
+ type: bool
+ sr_version:
+ description:
+ - The SR version of the compatibility catalog.
+ type: str
+ version:
+ description:
+ - The version of the compatibility catalog.
+ type: str
+ version_check_override:
+ description:
+ - The version check override.
+ - This is a directive to ignore the version check for the next install.
+ - The version check, which occurs during a maintenance window, checks to see if the desired version matches the running version.
+ - If the versions do not match, the install is performed. If the versions do match, the install is not performed.
+ - The version check override is a one-time override that performs the install whether or not the versions match.
+ - The APIC defaults to C(untriggered) when unset during creation.
+ type: str
+ choices: [ trigger, trigger_immediate, triggered, untriggered ]
+ ignore_compat:
description:
- To check whether compatibility checks should be ignored
- The APIC defaults to C(false) when unset during creation.
type: bool
+ aliases: [ ignoreCompat ]
+ description:
+ description:
+ - Description for the maintenance policy.
+ type: str
+ aliases: [ descr ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -64,8 +120,14 @@ extends_documentation_fragment:
notes:
- A scheduler is required for this module, which could have been created using the M(cisco.aci.aci_fabric_scheduler) module or via the UI.
+seealso:
+- module: cisco.aci.aci_fabric_scheduler
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(maint:MaintP).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Steven Gerhart (@sgerhart)
+- Gaspard Micol (@gmicol)
"""
EXAMPLES = r"""
@@ -216,18 +278,36 @@ url:
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec
from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.constants import (
+ MATCH_RUN_MODE_MAPPING,
+ MATCH_NOTIFY_CONDITION_MAPPING,
+ MATCH_SMU_OPERATION_MAPPING,
+ MATCH_SMU_OPERATION_FLAGS_MAPPING,
+ MATCH_TRIGGER_MAPPING,
+)
def main():
+ list_run_mode_choices = list(MATCH_RUN_MODE_MAPPING.keys())
+ list_run_mode_choices.extend(["pauseOnlyOnFailures", "pauseNever"])
argument_spec = aci_argument_spec()
argument_spec.update(aci_annotation_spec())
argument_spec.update(
name=dict(type="str", aliases=["maintenance_policy"]), # Not required for querying all objects
- runmode=dict(type="str", default="pauseOnlyOnFailures", choices=["pauseOnlyOnFailures", "pauseNever"]),
+ run_mode=dict(type="str", choices=list_run_mode_choices, aliases=["runmode"]),
graceful=dict(type="bool"),
scheduler=dict(type="str"),
- ignoreCompat=dict(type="bool"),
- adminst=dict(type="str", default="untriggered", choices=["triggered", "untriggered"]),
+ ignore_compat=dict(type="bool", aliases=["ignoreCompat"]),
+ admin_state=dict(type="str", choices=list(MATCH_TRIGGER_MAPPING.keys())[2:], aliases=["adminst"]),
+ download_state=dict(type="str", choices=list(MATCH_TRIGGER_MAPPING.keys())[2:]),
+ notify_condition=dict(type="str", choices=list(MATCH_NOTIFY_CONDITION_MAPPING.keys())),
+ smu_operation=dict(type="str", choices=list(MATCH_SMU_OPERATION_MAPPING.keys())),
+ smu_operation_flags=dict(type="str", choices=list(MATCH_SMU_OPERATION_FLAGS_MAPPING.keys())),
+ sr_upgrade=dict(type="bool"),
+ sr_version=dict(type="str"),
+ version=dict(type="str"),
+ version_check_override=dict(type="str", choices=list(MATCH_TRIGGER_MAPPING.keys())),
+ description=dict(type="str", aliases=["descr"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
name_alias=dict(type="str"),
)
@@ -245,13 +325,25 @@ def main():
state = module.params.get("state")
name = module.params.get("name")
- runmode = module.params.get("runmode")
+ run_mode = module.params.get("run_mode")
+ graceful = aci.boolean(module.params.get("graceful"), "yes", "no")
scheduler = module.params.get("scheduler")
- adminst = module.params.get("adminst")
- graceful = aci.boolean(module.params.get("graceful"))
- ignoreCompat = aci.boolean(module.params.get("ignoreCompat"))
+ admin_state = module.params.get("admin_state")
+ download_state = module.params.get("download_state")
+ notify_condition = MATCH_NOTIFY_CONDITION_MAPPING.get(module.params.get("notify_condition"))
+ smu_operation = MATCH_SMU_OPERATION_MAPPING.get(module.params.get("smu_operation"))
+ smu_operation_flags = MATCH_SMU_OPERATION_FLAGS_MAPPING.get(module.params.get("smu_operation_flags"))
+ sr_version = module.params.get("sr_version")
+ sr_upgrade = module.params.get("sr_upgrade")
+ version = module.params.get("version")
+ version_check_override = MATCH_TRIGGER_MAPPING.get(module.params.get("version_check_override"))
+ ignore_compat = aci.boolean(module.params.get("ignore_compat"))
+ description = module.params.get("description")
name_alias = module.params.get("name_alias")
+ if run_mode not in ["pauseOnlyOnFailures", "pauseNever"]:
+ run_mode = MATCH_RUN_MODE_MAPPING.get(run_mode)
+
aci.construct_url(
root_class=dict(
aci_class="maintMaintP",
@@ -269,10 +361,19 @@ def main():
aci_class="maintMaintP",
class_config=dict(
name=name,
- runMode=runmode,
+ descr=description,
+ runMode=run_mode,
graceful=graceful,
- adminSt=adminst,
- ignoreCompat=ignoreCompat,
+ adminSt=admin_state,
+ downloadSt=download_state,
+ notifCond=notify_condition,
+ smuOperation=smu_operation,
+ smuOperationFlags=smu_operation_flags,
+ srUpgrade=sr_upgrade,
+ srVersion=sr_version,
+ version=version,
+ versionCheckOverride=version_check_override,
+ ignoreCompat=ignore_compat,
nameAlias=name_alias,
),
child_configs=[
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_as_path_regex_term.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_as_path_regex_term.py
new file mode 100644
index 000000000..bb87acaa5
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_as_path_regex_term.py
@@ -0,0 +1,306 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_match_as_path_regex_term
+short_description: Manage Match Regular Expression AS-Path Term (rtctrl:MatchAsPathRegexTerm)
+description:
+- Manage Match Term Based on Route Regular Expression AS-Path for Match Rule Profiles on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ match_rule:
+ description:
+ - The name of an exising match rule profile.
+ type: str
+ aliases: [ match_rule_name ]
+ match_as_path_regex_term:
+ description:
+ - The name of the match regex AS-Path term.
+ type: str
+ aliases: [ name, match_as_path_regex_term_name ]
+ regex:
+ description:
+ - The Regular Expression.
+ type: str
+ description:
+ description:
+ - The description for the match regex AS-Path term.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_match_rule
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(rtctrl:MatchAsPathRegexTerm).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+"""
+
+EXAMPLES = r"""
+- name: Create a match with AS-path regex term
+ cisco.aci.aci_match_as_path_regex_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ match_as_path_regex_term: prod_match_as_path_regex_term
+ regex: .*
+ tenant: production
+ state: present
+ delegate_to: localhost
+
+- name: Delete a match with AS-path regex term
+ cisco.aci.aci_match_as_path_regex_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ match_as_path_regex_term: prod_match_as_path_regex_term
+ state: absent
+ delegate_to: localhost
+
+- name: Query all match with AS-path regex terms
+ cisco.aci.aci_match_as_path_regex_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific match with AS-path regex term
+ cisco.aci.aci_match_as_path_regex_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ match_as_path_regex_term: prod_match_as_path_regex_term
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects
+ match_as_path_regex_term=dict(type="str", aliases=["name", "match_as_path_regex_term_name"]),
+ regex=dict(type="str"),
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["match_as_path_regex_term", "tenant"]],
+ ["state", "present", ["match_as_path_regex_term", "tenant"]],
+ ],
+ )
+
+ match_as_path_regex_term = module.params.get("match_as_path_regex_term")
+ description = module.params.get("description")
+ regex = module.params.get("regex")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ match_rule = module.params.get("match_rule")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="rtctrlSubjP",
+ aci_rn="subj-{0}".format(match_rule),
+ module_object=match_rule,
+ target_filter={"name": match_rule},
+ ),
+ subclass_2=dict(
+ aci_class="rtctrlMatchAsPathRegexTerm",
+ aci_rn="aspathrxtrm-{0}".format(match_as_path_regex_term),
+ module_object=match_as_path_regex_term,
+ target_filter={"name": match_as_path_regex_term},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="rtctrlMatchAsPathRegexTerm",
+ class_config=dict(
+ name=match_as_path_regex_term,
+ regex=regex,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="rtctrlMatchAsPathRegexTerm")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_community_factor.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_factor.py
new file mode 100644
index 000000000..8db4e6e71
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_factor.py
@@ -0,0 +1,325 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_match_community_factor
+short_description: Manage Match Community Factor (rtctrl:MatchCommFactor)
+description:
+- Manage Match Community Factors for Match Terms Based on Community on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ match_rule:
+ description:
+ - The name of an exising match rule profile.
+ type: str
+ aliases: [ match_rule_name ]
+ match_community_term:
+ description:
+ - The name of an existing match community term.
+ type: str
+ aliases: [ match_community_term_name ]
+ community:
+ description:
+ - The match community value.
+ type: str
+ scope:
+ description:
+ - The item scope.
+ - If the scope is transitive, this community may be passed between ASs.
+ - If the scope is non-transitive, this community should be carried only within the local AS.
+ type: str
+ choices: [ transitive, non-transitive ]
+ description:
+ description:
+ - The description for the match community factor.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant), the C(match_rule) and the C(match_community_term) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant), the M(cisco.aci.aci_match_rule) and M(cisco.aci.aci_match_community_term) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_match_rule
+- module: cisco.aci.aci_match_community_term
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(rtctrl:MatchCommFactor).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+"""
+
+EXAMPLES = r"""
+- name: Create a match match AS-path regex term
+ cisco.aci.aci_match_community_factor:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ match_community_term: prod_match_community_term
+ community: regular:as2-nn2:4:15
+ scope: transitive
+ tenant: production
+ state: present
+ delegate_to: localhost
+
+- name: Delete a match match AS-path regex term
+ cisco.aci.aci_match_community_factor:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ match_community_term: prod_match_community_term
+ community: regular:as2-nn2:4:15
+ state: absent
+ delegate_to: localhost
+
+- name: Query all match AS-path regex terms
+ cisco.aci.aci_match_community_factor:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific match match AS-path regex term
+ cisco.aci.aci_match_community_factor:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ match_community_term: prod_match_community_term
+ community: regular:as2-nn2:4:15
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects
+ match_community_term=dict(type="str", aliases=["match_community_term_name"]), # Not required for querying all objects
+ community=dict(type="str"),
+ scope=dict(type="str", choices=["transitive", "non-transitive"]),
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["community", "tenant", "match_rule", "match_community_term"]],
+ ["state", "present", ["community", "tenant", "match_rule", "match_community_term"]],
+ ],
+ )
+
+ community = module.params.get("community")
+ scope = module.params.get("scope")
+ description = module.params.get("description")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ match_rule = module.params.get("match_rule")
+ match_community_term = module.params.get("match_community_term")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="rtctrlSubjP",
+ aci_rn="subj-{0}".format(match_rule),
+ module_object=match_rule,
+ target_filter={"name": match_rule},
+ ),
+ subclass_2=dict(
+ aci_class="rtctrlMatchCommTerm",
+ aci_rn="commtrm-{0}".format(match_community_term),
+ module_object=match_community_term,
+ target_filter={"name": match_community_term},
+ ),
+ subclass_3=dict(
+ aci_class="rtctrlMatchCommFactor",
+ aci_rn="commfct-{0}".format(community),
+ module_object=community,
+ target_filter={"community": community},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="rtctrlMatchCommFactor",
+ class_config=dict(
+ community=community,
+ scope=scope,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="rtctrlMatchCommFactor")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_community_regex_term.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_regex_term.py
new file mode 100644
index 000000000..1b2d0007a
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_regex_term.py
@@ -0,0 +1,317 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_match_community_regex_term
+short_description: Manage Match Regular Expression Community Term (rtctrl:MatchCommRegexTerm)
+description:
+- Manage Match Terms Based on Route Regular Expression Community for Match Rule Profiles on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ match_rule:
+ description:
+ - The name of an exising math rule profile.
+ type: str
+ aliases: [ match_rule_name ]
+ match_community_regex_term:
+ description:
+ - The name of the match regex community term.
+ type: str
+ aliases: [ name, match_community_regex_term_name ]
+ community_type:
+ description:
+ - The community type.
+ type: str
+ choices: [ extended, regular ]
+ default: regular
+ regex:
+ description:
+ - The regular expression.
+ type: str
+ description:
+ description:
+ - The description for the match regex community term.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this.
+- Only two match community regex terms can exist at the same time, one of each C(community_type).
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_match_rule
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(rtctrl:MatchCommRegexTerm).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+"""
+
+EXAMPLES = r"""
+- name: Create a match with comunity regex term
+ cisco.aci.aci_match_community_regex_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ match_community_regex_term: prod_match_community_regex_term
+ community_type: regular
+ regex: .*
+ tenant: production
+ state: present
+ delegate_to: localhost
+
+- name: Delete a match with comunity regex term
+ cisco.aci.aci_match_community_regex_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ community_type: regular
+ state: absent
+ delegate_to: localhost
+
+- name: Query all match with commmuntiy regex terms
+ cisco.aci.aci_match_community_regex_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific match with comunity regex term
+ cisco.aci.aci_match_community_regex_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ community_type: regular
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects
+ match_community_regex_term=dict(type="str", aliases=["name", "match_community_regex_term_name"]),
+ community_type=dict(type="str", default="regular", choices=["extended", "regular"]),
+ regex=dict(type="str"),
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["community_type", "tenant", "match_rule"]],
+ ["state", "present", ["community_type", "tenant", "match_rule"]],
+ ],
+ )
+
+ match_community_regex_term = module.params.get("match_community_regex_term")
+ description = module.params.get("description")
+ community_type = module.params.get("community_type")
+ regex = module.params.get("regex")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ match_rule = module.params.get("match_rule")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="rtctrlSubjP",
+ aci_rn="subj-{0}".format(match_rule),
+ module_object=match_rule,
+ target_filter={"name": match_rule},
+ ),
+ subclass_2=dict(
+ aci_class="rtctrlMatchCommRegexTerm",
+ aci_rn="commrxtrm-{0}".format(community_type),
+ module_object=community_type,
+ target_filter={"commType": community_type},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="rtctrlMatchCommRegexTerm",
+ class_config=dict(
+ name=match_community_regex_term,
+ commType=community_type,
+ regex=regex,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="rtctrlMatchCommRegexTerm")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_community_term.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_term.py
new file mode 100644
index 000000000..21a3f35db
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_community_term.py
@@ -0,0 +1,298 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_match_community_term
+short_description: Manage Match Community Term (rtctrl:MatchCommTerm)
+description:
+- Manage Match Term Based on Community for Match Rule Profiles on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ match_rule:
+ description:
+ - the name of an exising match rule profile.
+ type: str
+ aliases: [ match_rule_name ]
+ match_community_term:
+ description:
+ - the name of the Match Community Term.
+ type: str
+ aliases: [ name, match_community_term_name ]
+ description:
+ description:
+ - The description for the Match Community Term.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_match_rule
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(rtctrl:MatchCommTerm).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+"""
+
+EXAMPLES = r"""
+- name: Create a match with community term
+ cisco.aci.aci_match_community_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ match_community_term: prod_match_community_term
+ tenant: production
+ state: present
+ delegate_to: localhost
+
+- name: Delete a match with community term
+ cisco.aci.aci_match_community_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ match_community_term: prod_match_community_term
+ state: absent
+ delegate_to: localhost
+
+- name: Query all with community terms
+ cisco.aci.aci_match_community_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific match with community term
+ cisco.aci.aci_match_community_term:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ match_community_term: prod_match_community_term
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects
+ match_community_term=dict(type="str", aliases=["name", "match_community_term_name"]),
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["match_community_term", "tenant", "match_rule"]],
+ ["state", "present", ["match_community_term", "tenant", "match_rule"]],
+ ],
+ )
+
+ match_community_term = module.params.get("match_community_term")
+ description = module.params.get("description")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ match_rule = module.params.get("match_rule")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="rtctrlSubjP",
+ aci_rn="subj-{0}".format(match_rule),
+ module_object=match_rule,
+ target_filter={"name": match_rule},
+ ),
+ subclass_2=dict(
+ aci_class="rtctrlMatchCommTerm",
+ aci_rn="commtrm-{0}".format(match_community_term),
+ module_object=match_community_term,
+ target_filter={"name": match_community_term},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="rtctrlMatchCommTerm",
+ class_config=dict(
+ name=match_community_term,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="rtctrlMatchCommTerm")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py
new file mode 100644
index 000000000..973d70561
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py
@@ -0,0 +1,325 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com>
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_match_route_destination
+short_description: Manage Match action rule term based on the Route Destination. (rtctrl:MatchRtDest)
+description:
+- Match action rule terms based on the Route Destination for Subject Profiles on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ match_rule:
+ description:
+ - The name of an exising match rule profile.
+ type: str
+ aliases: [ match_rule_name ]
+ ip:
+ description:
+ - The IP address of the route destination.
+ type: str
+ aggregate:
+ description:
+ - Option to enable/disable aggregated route.
+ type: str
+ choices: [ "no", "yes" ]
+ from_prefix_length:
+ description:
+ - The start of the prefix length.
+ - It corresponds to the lesser Mask if the route is aggregated.
+ type: int
+ to_prefix_length:
+ description:
+ - The end of the prefix length.
+ - It corresponds to greater Mask if the route is aggregated.
+ type: int
+ description:
+ description:
+ - The description for the match action rule term.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_match_rule
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(rtctrl:MatchRtDest).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+- Tim Cragg (@timcragg)
+"""
+
+EXAMPLES = r"""
+- name: Create a match rule destination
+ cisco.aci.aci_match_rule_destination:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ ip: 11.11.11.11/24
+ aggregate: "yes"
+ from_prefix_length: 0
+ to_prefix_length: 32
+ state: present
+ delegate_to: localhost
+
+- name: Delete a match rule destination
+ cisco.aci.aci_match_rule_destination:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ state: absent
+ delegate_to: localhost
+
+- name: Query all match rules destination
+ cisco.aci.aci_match_rule_destination:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific match rule destination
+ cisco.aci.aci_match_rule_destination:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ ip: 11.11.11.11/24
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects
+ ip=dict(type="str"),
+ aggregate=dict(type="str", choices=["no", "yes"]),
+ from_prefix_length=dict(type="int"),
+ to_prefix_length=dict(type="int"),
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["ip", "tenant", "match_rule"]],
+ ["state", "present", ["ip", "tenant", "match_rule"]],
+ ],
+ )
+
+ ip = module.params.get("ip")
+ description = module.params.get("description")
+ aggregate = module.params.get("aggregate")
+ from_prefix_length = module.params.get("from_prefix_length")
+ to_prefix_length = module.params.get("to_prefix_length")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ match_rule = module.params.get("match_rule")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="rtctrlSubjP",
+ aci_rn="subj-{0}".format(match_rule),
+ module_object=match_rule,
+ target_filter={"name": match_rule},
+ ),
+ subclass_2=dict(
+ aci_class="rtctrlMatchRtDest",
+ aci_rn="dest-[{0}]".format(ip),
+ module_object=ip,
+ target_filter={"ip": ip},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="rtctrlMatchRtDest",
+ class_config=dict(
+ ip=ip,
+ aggregate=aggregate,
+ fromPfxLen=from_prefix_length,
+ toPfxLen=to_prefix_length,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="rtctrlMatchRtDest")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_rule.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_rule.py
new file mode 100644
index 000000000..c412d2458
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_rule.py
@@ -0,0 +1,283 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com>
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_match_rule
+short_description: Manage Match Rule (rtcrtl:SubjP)
+description:
+- Manage Match Rule Profiles for the Tenants on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ match_rule:
+ description:
+ - The name of the match rule profile being created.
+ type: str
+ aliases: [ name, match_rule_name ]
+ description:
+ description:
+ - The description for the match rule profile.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) module can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(rtctrl:SubjP).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+- Tim Cragg (@timcragg)
+"""
+
+EXAMPLES = r"""
+- name: Create a match rule
+ cisco.aci.aci_match_rule:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ state: present
+ delegate_to: localhost
+
+- name: Delete a match rule
+ cisco.aci.aci_match_rule:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ state: absent
+ delegate_to: localhost
+
+- name: Query all match rules
+ cisco.aci.aci_match_rule:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific match rule
+ cisco.aci.aci_match_rule:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ match_rule: prod_match_rule
+ tenant: production
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ match_rule=dict(type="str", aliases=["name", "match_rule_name"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["match_rule", "tenant"]],
+ ["state", "present", ["match_rule", "tenant"]],
+ ],
+ )
+
+ match_rule = module.params.get("match_rule")
+ description = module.params.get("description")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="rtctrlSubjP",
+ aci_rn="subj-{0}".format(match_rule),
+ module_object=match_rule,
+ target_filter={"name": match_rule},
+ ),
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="rtctrlSubjP",
+ class_config=dict(
+ name=match_rule,
+ descr=description,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="rtctrlSubjP")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_rest.py b/ansible_collections/cisco/aci/plugins/modules/aci_rest.py
index 67428e4f9..ad73cb5bd 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_rest.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_rest.py
@@ -55,6 +55,11 @@ options:
together with the C(template) lookup plugin, or use C(template).
type: path
aliases: [ config_file ]
+ rsp_subtree_preserve:
+ description:
+ - Preserve the response for the provided path.
+ type: bool
+ default: false
extends_documentation_fragment:
- cisco.aci.aci
@@ -245,6 +250,7 @@ url:
import json
import os
+import re
try:
from ansible.module_utils.six.moves.urllib.parse import parse_qsl, urlencode, urlparse, urlunparse
@@ -279,7 +285,6 @@ except Exception:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec
-from ansible.module_utils.urls import fetch_url
from ansible.module_utils._text import to_text
@@ -335,16 +340,19 @@ def main():
method=dict(type="str", default="get", choices=["delete", "get", "post"], aliases=["action"]),
src=dict(type="path", aliases=["config_file"]),
content=dict(type="raw"),
+ rsp_subtree_preserve=dict(type="bool", default=False),
)
module = AnsibleModule(
argument_spec=argument_spec,
+ supports_check_mode=True,
mutually_exclusive=[["content", "src"]],
)
content = module.params.get("content")
path = module.params.get("path")
src = module.params.get("src")
+ rsp_subtree_preserve = module.params.get("rsp_subtree_preserve")
# Report missing file
file_exists = False
@@ -352,7 +360,7 @@ def main():
if os.path.isfile(src):
file_exists = True
else:
- module.fail_json(msg="Cannot find/access src '%s'" % src)
+ module.fail_json(msg="Cannot find/access src '{0}'".format(src))
# Find request type
if path.find(".xml") != -1:
@@ -386,7 +394,7 @@ def main():
# Validate YAML/JSON string
payload = json.dumps(yaml.safe_load(payload))
except Exception as e:
- module.fail_json(msg="Failed to parse provided JSON/YAML payload: %s" % to_text(e), exception=to_text(e), payload=payload)
+ module.fail_json(msg="Failed to parse provided JSON/YAML payload: {0}".format(to_text(e)), exception=to_text(e), payload=payload)
elif rest_type == "xml" and HAS_LXML_ETREE:
if content and isinstance(content, dict) and HAS_XMLJSON_COBRA:
# Validate inline YAML/JSON
@@ -396,52 +404,62 @@ def main():
# Validate XML string
payload = etree.tostring(etree.fromstring(payload), encoding="unicode")
except Exception as e:
- module.fail_json(msg="Failed to parse provided XML payload: %s" % to_text(e), payload=payload)
+ module.fail_json(msg="Failed to parse provided XML payload: {0}".format(to_text(e)), payload=payload)
# Perform actual request using auth cookie (Same as aci.request(), but also supports XML)
- if "port" in aci.params and aci.params.get("port") is not None:
- aci.url = "%(protocol)s://%(host)s:%(port)s/" % aci.params + path.lstrip("/")
- else:
- aci.url = "%(protocol)s://%(host)s/" % aci.params + path.lstrip("/")
- if aci.params.get("method") != "get":
- path += "?rsp-subtree=modified"
+ # NOTE By setting aci.path we ensure that Ansible displays accurate URL info when the plugin and the aci_rest module are used.
+ aci.path = path.lstrip("/")
+ aci.url = "{0}/{1}".format(aci.base_url, aci.path)
+ if aci.params.get("method") != "get" and not rsp_subtree_preserve:
+ aci.path = "{0}?rsp-subtree=modified".format(aci.path)
aci.url = update_qsl(aci.url, {"rsp-subtree": "modified"})
- # Sign and encode request as to APIC's wishes
- if aci.params.get("private_key") is not None:
- aci.cert_auth(path=path, payload=payload)
-
- aci.method = aci.params.get("method").upper()
-
+ method = aci.params.get("method").upper()
# Perform request
- resp, info = fetch_url(
- module, aci.url, data=payload, headers=aci.headers, method=aci.method, timeout=aci.params.get("timeout"), use_proxy=aci.params.get("use_proxy")
- )
-
- aci.response = info.get("msg")
- aci.status = info.get("status")
+ if not aci.module.check_mode:
+ resp, info = aci.api_call(method, aci.url, data=payload, return_response=True)
+ # Report failure
+ if info.get("status") != 200:
+ try:
+ # APIC error
+ aci.response_type(info["body"], rest_type)
+ aci.fail_json(msg="APIC Error {code}: {text}".format_map(aci.error))
+ except KeyError:
+ # Connection error
+ aci.fail_json(msg="Connection failed for {url}. {msg}".format_map(info))
- # Report failure
- if info.get("status") != 200:
try:
- # APIC error
+ aci.response_type(resp.read(), rest_type)
+ except AttributeError:
aci.response_type(info.get("body"), rest_type)
- aci.fail_json(msg="APIC Error %(code)s: %(text)s" % aci.error)
- except KeyError:
- # Connection error
- aci.fail_json(msg="Connection failed for %(url)s. %(msg)s" % info)
- aci.response_type(resp.read(), rest_type)
+ aci.result["status"] = aci.status
+ aci.result["imdata"] = aci.imdata
+ aci.result["totalCount"] = aci.totalCount
+
+ else:
+ # NOTE A case when aci_rest is used with check mode and the apic host is used directly from the inventory
+ if aci.connection is not None and aci.params.get("host") is None:
+ aci.url = urlunparse(urlparse(aci.url)._replace(netloc=re.sub(r"[[\]]", "", aci.connection.get_option("host")).split(",")[0]))
+ aci.method = method
+ # Set changed to true so check_mode changed result is behaving similar to non aci_rest modules
+ aci.result["changed"] = True
+
+ # Only set proposed if we have a payload and thus also only allow output_path if we have a payload
+ # DELETE and GET do not have a payload
+ if payload and method == "POST":
+ if rest_type == "json":
+ payload = json.loads(payload)
- aci.result["status"] = aci.status
- aci.result["imdata"] = aci.imdata
- aci.result["totalCount"] = aci.totalCount
+ aci.result["proposed"] = payload
- if aci.params.get("method") != "get":
output_path = aci.params.get("output_path")
if output_path is not None:
with open(output_path, "a") as output_file:
- output_file.write(str(payload))
+ if rest_type == "json":
+ json.dump([payload], output_file)
+ else:
+ output_file.write(str(payload))
# Report success
aci.exit_json(**aci.result)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_route_control_context.py b/ansible_collections/cisco/aci/plugins/modules/aci_route_control_context.py
new file mode 100644
index 000000000..19993767a
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_route_control_context.py
@@ -0,0 +1,393 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com>
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_route_control_context
+short_description: Manage Route Control Context (rtcrtl:CtxP)
+description:
+- Manage Route Control Context Policies for the Route Control Profiles on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ l3out:
+ description:
+ - The name of an existing L3Out.
+ - To use only if the route control profile is linked to an existing L3Out.
+ type: str
+ aliases: [ l3out_name ]
+ route_control_profile:
+ description:
+ - The name of an existing route control profile.
+ type: str
+ aliases: [ route_control_profile_name ]
+ route_control_context:
+ description:
+ - The name of the route control context being created.
+ type: str
+ aliases: [ name, route_control_context_name, context ]
+ action:
+ description:
+ - The action required when the condition is met.
+ type: str
+ choices: [ deny, permit ]
+ action_rule:
+ description:
+ - The name of the action rule profile to be associated with this route control context.
+ - Set the rules for a Route Map.
+ type: str
+ aliases: [ action_rule_name ]
+ match_rule:
+ description:
+ - The name of the match rule profile to be associated with this route control context.
+ - Set the associated Matched rules.
+ type: str
+ aliases: [ match_rule_name ]
+ order:
+ description:
+ - The order of the route control context.
+ - The value range from 0 to 9.
+ type: int
+ description:
+ description:
+ - The description for the route control context.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) and the C(route_control_profile) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_route_control_profile) modules can be used for this.
+- If C(l3out) is used, the C(l3out) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_l3out) module can be used for this.
+- if C(action_rule) is used, the C(action_rule) used must exist before using this module in your plabook.
+ The module M(cisco.aci.aci_tenant_action_rule_profile) can be used for this.
+- if C(match_rule) is used, the C(match_rule) used must exist before using this module in your plabook.
+ The module M(cisco.aci.aci_match_rule) can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_route_control_profile
+- module: cisco.aci.aci_l3out
+- module: cisco.aci.aci_tenant_action_rule_profile
+- module: cisco.aci.aci_match_rule
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(rtctrl:CtxP).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+- Tim Cragg (@timcragg)
+"""
+
+EXAMPLES = r"""
+- name: Create a route context policy
+ cisco.aci.aci_route_control_context:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ route_control_context: prod_route_control_context
+ route_control_profile: prod_route_control_profile
+ tenant: production
+ l3out: prod_l3out
+ action: permit
+ order: 0
+ action_rule: prod_action_rule_profile
+ match_rule: prod_match_rule
+ state: present
+ delegate_to: localhost
+
+- name: Delete a route context policy
+ cisco.aci.aci_route_control_context:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ route_control_context: prod_route_control_context
+ route_control_profile: prod_route_control_profile
+ tenant: production
+ l3out: prod_l3out
+ state: absent
+ delegate_to: localhost
+
+- name: Query all route context policy
+ cisco.aci.aci_route_control_context:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific route context policy
+ cisco.aci.aci_route_control_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ route_control_context: prod_route_control_context
+ route_control_profile: prod_route_control_profile
+ tenant: production
+ l3out: prod_l3out
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects
+ route_control_profile=dict(type="str", aliases=["route_control_profile_name"]), # Not required for querying all objects
+ route_control_context=dict(type="str", aliases=["name", "route_control_context_name", "context"]), # Not required for querying all objects
+ match_rule=dict(type="str", aliases=["match_rule_name"]),
+ action_rule=dict(type="str", aliases=["action_rule_name"]),
+ action=dict(type="str", choices=["deny", "permit"]),
+ order=dict(type="int"),
+ description=dict(type="str", aliases=["descr"]),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["route_control_context", "tenant"]],
+ ["state", "present", ["route_control_context", "tenant"]],
+ ],
+ )
+
+ route_control_context = module.params.get("route_control_context")
+ description = module.params.get("description")
+ action = module.params.get("action")
+ order = module.params.get("order")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ l3out = module.params.get("l3out")
+ route_control_profile = module.params.get("route_control_profile")
+ match_rule = module.params.get("match_rule")
+ action_rule = module.params.get("action_rule")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ child_classes = ["rtctrlRsCtxPToSubjP", "rtctrlScope"]
+
+ tenant_url_config = dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ )
+
+ route_control_profile_url_config = dict(
+ aci_class="rtctrlProfile",
+ aci_rn="prof-{0}".format(route_control_profile),
+ module_object=route_control_profile,
+ target_filter={"name": route_control_profile},
+ )
+
+ route_control_context_url_config = dict(
+ aci_class="rtctrlCtxP",
+ aci_rn="ctx-{0}".format(route_control_context),
+ module_object=route_control_context,
+ target_filter={"name": route_control_context},
+ )
+
+ if l3out is not None:
+ aci.construct_url(
+ root_class=tenant_url_config,
+ subclass_1=dict(
+ aci_class="l3extOut",
+ aci_rn="out-{0}".format(l3out),
+ module_object=l3out,
+ target_filter={"name": l3out},
+ ),
+ subclass_2=route_control_profile_url_config,
+ subclass_3=route_control_context_url_config,
+ child_classes=child_classes,
+ )
+ else:
+ aci.construct_url(
+ root_class=tenant_url_config,
+ subclass_1=route_control_profile_url_config,
+ subclass_2=route_control_context_url_config,
+ child_classes=child_classes,
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ child_configs = []
+ if match_rule is not None:
+ child_configs.append({"rtctrlRsCtxPToSubjP": {"attributes": {"tnRtctrlSubjPName": match_rule}}})
+ if action_rule is not None:
+ child_configs.append(
+ {
+ "rtctrlScope": {
+ "attributes": {"descr": ""},
+ "children": [{"rtctrlRsScopeToAttrP": {"attributes": {"tnRtctrlAttrPName": action_rule}}}],
+ }
+ }
+ )
+
+ aci.payload(
+ aci_class="rtctrlCtxP",
+ class_config=dict(
+ name=route_control_context,
+ descr=description,
+ action=action,
+ order=order,
+ nameAlias=name_alias,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="rtctrlCtxP")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_route_control_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_route_control_profile.py
new file mode 100644
index 000000000..496cce9b3
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_route_control_profile.py
@@ -0,0 +1,333 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com>
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_route_control_profile
+short_description: Manage Route Control Profile (rtcrtl:Profile)
+description:
+- Manage Route Control Profiles on Cisco ACI fabrics.
+options:
+ tenant:
+ description:
+ - The name of an existing tenant.
+ type: str
+ aliases: [ tenant_name ]
+ l3out:
+ description:
+ - The name of an existing L3Out.
+ - This will link the created route control profile to the existing L3Out.
+ type: str
+ aliases: [ l3out_name ]
+ route_control_profile:
+ description:
+ - The name of the route control profile being created.
+ type: str
+ aliases: [ name, route_control_profile_name ]
+ auto_continue:
+ description:
+ - The option to enable/disable auto-continue.
+ type: str
+ choices: [ "no", "yes" ]
+ default: "no"
+ policy_type:
+ description:
+ - Set the policy type to combinable or global.
+ type: str
+ choices: [ combinable, global ]
+ default: combinable
+ description:
+ description:
+ - The description for the route control profile.
+ type: str
+ aliases: [ descr ]
+ 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
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) module can be used for this.
+- If C(l3out) is used, the C(l3out) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_l3out) module can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_l3out
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(rtctrl:Profile).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Gaspard Micol (@gmicol)
+- Tim Cragg (@timcragg)
+"""
+
+EXAMPLES = r"""
+- name: Create a route control profile
+ cisco.aci.aci_route_control_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ route_control_profile: prod_route_control_profile
+ tenant: production
+ l3out: prod_l3out
+ auto_continue: "no"
+ policy_type: combinable
+ state: present
+ delegate_to: localhost
+
+- name: Delete a route control profile
+ cisco.aci.aci_route_control_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ route_control_profile: prod_route_control_profile
+ tenant: production
+ l3out: prod_l3out
+ state: absent
+ delegate_to: localhost
+
+- name: Query all route control profiles
+ cisco.aci.aci_route_control_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Query a specific route control profile
+ cisco.aci.aci_route_control_profile:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ route_control_profile: prod_route_control_profile
+ tenant: production
+ l3out: prod_l3out
+ state: query
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerauto_continue": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects
+ route_control_profile=dict(type="str", aliases=["name", "route_control_profile_name"]), # Not required for querying all objects
+ description=dict(type="str", aliases=["descr"]),
+ auto_continue=dict(type="str", default="no", choices=["no", "yes"]),
+ policy_type=dict(type="str", default="combinable", choices=["combinable", "global"]),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "absent", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["route_control_profile", "tenant"]],
+ ["state", "present", ["route_control_profile", "tenant"]],
+ ],
+ )
+
+ route_control_profile = module.params.get("route_control_profile")
+ description = module.params.get("description")
+ auto_continue = module.params.get("auto_continue")
+ policy_type = module.params.get("policy_type")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ l3out = module.params.get("l3out")
+ name_alias = module.params.get("name_alias")
+
+ aci = ACIModule(module)
+
+ tenant_url_config = dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ )
+
+ route_control_profile_url_config = dict(
+ aci_class="rtctrlProfile",
+ aci_rn="prof-{0}".format(route_control_profile),
+ module_object=route_control_profile,
+ target_filter={"name": route_control_profile},
+ )
+
+ if l3out is not None:
+ aci.construct_url(
+ root_class=tenant_url_config,
+ subclass_1=dict(
+ aci_class="l3extOut",
+ aci_rn="out-{0}".format(l3out),
+ module_object=l3out,
+ target_filter={"name": l3out},
+ ),
+ subclass_2=route_control_profile_url_config,
+ )
+ else:
+ aci.construct_url(
+ root_class=tenant_url_config,
+ subclass_1=route_control_profile_url_config,
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ aci.payload(
+ aci_class="rtctrlProfile",
+ class_config=dict(
+ name=route_control_profile,
+ descr=description,
+ autoContinue=auto_continue,
+ type=policy_type,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="rtctrlProfile")
+
+ aci.post_config()
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py
index 49a9f5ae5..9e9b6d852 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py
@@ -1,6 +1,8 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
+# Copyright: (c) 2021, Tim Cragg (@timcragg)
+# Copyright: (c) 2023, Akini Ross (@akinross)
# 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
@@ -21,7 +23,7 @@ options:
description:
- SNMP authentication method
type: str
- choices: [ hmac-md5-96, hmac-sha1-96]
+ choices: [ hmac-md5-96, hmac-sha1-96, hmac-sha2-224, hmac-sha2-256, hmac-sha2-384, hmac-sha2-512 ]
auth_key:
description:
- SNMP authentication key
@@ -31,6 +33,11 @@ options:
- Name of the SNMP user policy
type: str
aliases: [ snmp_user_policy ]
+ description:
+ description:
+ - Description of the SNMP user policy
+ type: str
+ aliases: [ descr ]
policy:
description:
- Name of an existing SNMP policy
@@ -56,12 +63,17 @@ extends_documentation_fragment:
- cisco.aci.aci
- cisco.aci.annotation
+notes:
+- The C(policy) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_snmp_policy) module can be used for this.
seealso:
+- module: cisco.aci.aci_snmp_policy
- name: APIC Management Information Model reference
description: More information about the internal APIC class B(snmp:UserP).
link: https://developer.cisco.com/docs/apic-mim-ref/
author:
- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
"""
EXAMPLES = r"""
@@ -72,7 +84,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
policy: my_snmp_policy
name: my_snmp_user
- auth_type: hmac-sha1-96
+ auth_type: hmac-sha2-256
auth_key: "{{ hmac_key }}"
state: present
delegate_to: localhost
@@ -238,7 +250,8 @@ def main():
argument_spec.update(
policy=dict(type="str", aliases=["snmp_policy", "snmp_policy_name"]),
name=dict(type="str", aliases=["snmp_user_policy"]),
- auth_type=dict(type="str", choices=["hmac-md5-96", "hmac-sha1-96"]),
+ description=dict(type="str", aliases=["descr"]),
+ auth_type=dict(type="str", choices=["hmac-md5-96", "hmac-sha1-96", "hmac-sha2-224", "hmac-sha2-256", "hmac-sha2-384", "hmac-sha2-512"]),
auth_key=dict(type="str", no_log=True),
privacy_type=dict(type="str", choices=["aes-128", "des", "none"]),
privacy_key=dict(type="str", no_log=True),
@@ -258,6 +271,7 @@ def main():
policy = module.params.get("policy")
name = module.params.get("name")
+ description = module.params.get("description")
auth_type = module.params.get("auth_type")
auth_key = module.params.get("auth_key")
privacy_type = module.params.get("privacy_type")
@@ -284,7 +298,7 @@ def main():
if state == "present":
aci.payload(
aci_class="snmpUserP",
- class_config=dict(privType=privacy_type, privKey=privacy_key, authType=auth_type, authKey=auth_key, name=name),
+ class_config=dict(privType=privacy_type, privKey=privacy_key, authType=auth_type, authKey=auth_key, name=name, descr=description),
)
aci.get_diff(aci_class="snmpUserP")
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py
index b5428449f..051d5ee00 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py
@@ -48,7 +48,7 @@ options:
description:
- Determines the primary encapsulation ID associating the C(epg)
with the interface path when using micro-segmentation.
- - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096) and C(unknown.
+ - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096) and C(unknown).
- C(unknown) is the default value and using C(unknown) disables the Micro-Segmentation.
type: str
aliases: [ primary_vlan, primary_vlan_id ]
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py
index 95dcd7f39..b0bb61bb5 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py
@@ -25,6 +25,7 @@ options:
include:
description:
- List of message types to include
+ - The APIC defaults to C(faults) when unset during creation.
type: list
elements: str
choices: [ audit, events, faults, session ]
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_system_banner.py b/ansible_collections/cisco/aci/plugins/modules/aci_system_banner.py
new file mode 100644
index 000000000..bbba1d12e
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_system_banner.py
@@ -0,0 +1,296 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_system_banner
+short_description: Manages Alias and Banners (aaa:PreLoginBanner)
+description:
+- Manages Alias and Banners on Cisco ACI fabrics.
+options:
+ description:
+ description:
+ - The description of the AAA login banner.
+ type: str
+ gui_alias:
+ description:
+ - The system GUI alias.
+ type: str
+ controller_banner:
+ description:
+ - The contents of the CLI informational banner to be displayed before user login authentication.
+ - The CLI banner is a text based string printed as-is to the console.
+ type: str
+ switch_banner:
+ description:
+ - The switch banner message.
+ type: str
+ application_banner:
+ description:
+ - The application banner message.
+ type: str
+ severity:
+ description:
+ - The application banner severity.
+ type: str
+ choices: [ critical, info, major, minor, warning ]
+ gui_banner:
+ description:
+ - The contents of the GUI informational banner to be displayed before user login authentication.
+ - When I(gui_banner) starts with I(http://) or I(https://) the banner will be of URL type.
+ - Note that the URL site owner must allow the site to be placed in an iFrame to display the informational banner.
+ type: str
+ state:
+ description:
+ - Use C(present) for updating.
+ - Use C(query) for listing an object.
+ type: str
+ choices: [ present, query ]
+ default: present
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(aaa:PreLoginBanner).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+
+author:
+- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Configure banner
+ cisco.aci.aci_system_banner:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ gui_alias: "Test GUI Alias"
+ controller_banner: "Test Controller Banner"
+ application_banner: "Test Application Banner"
+ severity: critical
+ switch_banner: "Test Switch Banner"
+ gui_banner: "Test GUI Banner"
+ state: present
+ delegate_to: localhost
+
+- name: Configure banner with a url
+ cisco.aci.aci_system_banner:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ gui_alias: "Test GUI Alias"
+ controller_banner: "Test Controller Banner Message"
+ application_banner: "Test Application Banner"
+ severity: critical
+ switch_banner: "Test Switch Banner Message"
+ gui_banner: https://www.cisco.com
+ state: present
+ delegate_to: localhost
+
+- name: Query banner
+ cisco.aci.aci_system_banner:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+
+import re
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ description=dict(type="str"),
+ gui_alias=dict(type="str"),
+ controller_banner=dict(type="str"),
+ switch_banner=dict(type="str"),
+ application_banner=dict(type="str"),
+ severity=dict(type="str", choices=["critical", "info", "major", "minor", "warning"]),
+ gui_banner=dict(type="str"),
+ name_alias=dict(type="str"),
+ state=dict(type="str", default="present", choices=["present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_by={"severity": "application_banner"},
+ )
+ aci = ACIModule(module)
+
+ description = module.params.get("description")
+ gui_alias = module.params.get("gui_alias")
+ controller_banner = module.params.get("controller_banner")
+ switch_banner = module.params.get("switch_banner")
+ application_banner = module.params.get("application_banner")
+ severity = module.params.get("severity")
+ gui_banner = module.params.get("gui_banner")
+ state = module.params.get("state")
+ name_alias = module.params.get("name_alias")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="aaaPreLoginBanner",
+ aci_rn="userext/preloginbanner",
+ ),
+ )
+ aci.get_existing()
+
+ if state == "present":
+ regex_url = "^https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)$"
+
+ aci.payload(
+ aci_class="aaaPreLoginBanner",
+ class_config=dict(
+ descr=description,
+ bannerMessage=application_banner,
+ bannerMessageSeverity=severity,
+ guiMessage=gui_banner,
+ guiTextMessage=gui_alias,
+ isGuiMessageText="no" if re.fullmatch(regex_url, gui_banner) else "yes",
+ message=controller_banner,
+ showBannerMessage="yes" if application_banner else "no",
+ switchMessage=switch_banner,
+ nameAlias=name_alias,
+ ),
+ )
+
+ aci.get_diff(aci_class="aaaPreLoginBanner")
+
+ aci.post_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_system_endpoint_controls.py b/ansible_collections/cisco/aci/plugins/modules/aci_system_endpoint_controls.py
new file mode 100644
index 000000000..7da8afb3f
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_system_endpoint_controls.py
@@ -0,0 +1,338 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <timcragg@cisco.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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_system_endpoint_controls
+short_description: Manage System Endpoint Controls (ep:IpAgingP, ep:ControlP, and ep:LoopProtectP)
+description:
+- Manage System Endpoint Controls on Cisco ACI fabrics.
+options:
+ ip_aging:
+ description: Configuration container for IP Aging.
+ type: dict
+ suboptions:
+ admin_state:
+ description:
+ - Whether to enable IP Aging Controls on the fabric.
+ type: bool
+ roque_ep_control:
+ description: Configuration container for Rogue EP Control.
+ type: dict
+ suboptions:
+ admin_state:
+ description:
+ - Whether to enable Rogue EP Control on the fabric.
+ type: bool
+ interval:
+ description:
+ - The rogue endpoint detection interval in seconds.
+ type: int
+ multiplication_factor:
+ description:
+ - The rogue endpoint detection multiplication factor.
+ type: int
+ hold_interval:
+ description:
+ - The rogue endpoint hold interval in seconds.
+ type: int
+ ep_loop_protection:
+ description: Configuration container for EP Loop Protection.
+ type: dict
+ suboptions:
+ admin_state:
+ description:
+ - Whether to enable EP Loop Protection on the fabric.
+ type: bool
+ interval:
+ description:
+ - The loop protection detection interval in seconds.
+ type: int
+ multiplication_factor:
+ description:
+ - The loop protection detection multiplication factor.
+ type: int
+ action:
+ description:
+ - The action(s) to take when a loop is detected.
+ type: list
+ elements: str
+ choices: [ bd, port ]
+ state:
+ description:
+ - Use C(present) for updating configuration.
+ - Use C(query) for showing current configuration.
+ type: str
+ choices: [ present, query ]
+ default: present
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(ep:IpAgingP), B(ep:ControlP), and B(ep:LoopProtectP).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Set system endpoint controls settings
+ cisco.aci.aci_system_endpoint_controls:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ admin_state: true
+ ip_aging:
+ admin_state: true
+ roque_ep_control:
+ admin_state: true
+ interval: 50
+ multiplication_factor: 10
+ hold_interval: 2000
+ ep_loop_protection:
+ admin_state: true
+ interval: 70
+ multiplication_factor: 15
+ action: [ bd, port ]
+ delegate_to: localhost
+
+- name: Query system endpoint controls settings
+ cisco.aci.aci_system_endpoint_controls:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+from ansible_collections.cisco.aci.plugins.module_utils.constants import EP_LOOP_PROTECTION_ACTION_MAPPING
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ ip_aging=dict(type="dict", options=dict(admin_state=dict(type="bool"))),
+ roque_ep_control=dict(
+ type="dict",
+ options=dict(
+ admin_state=dict(type="bool"),
+ interval=dict(type="int"),
+ multiplication_factor=dict(type="int"),
+ hold_interval=dict(type="int"),
+ ),
+ ),
+ ep_loop_protection=dict(
+ type="dict",
+ options=dict(
+ admin_state=dict(type="bool"),
+ interval=dict(type="int"),
+ multiplication_factor=dict(type="int"),
+ action=dict(type="list", elements="str", choices=["bd", "port"]),
+ ),
+ ),
+ state=dict(type="str", default="present", choices=["present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[["state", "present", ["ip_aging", "roque_ep_control", "ep_loop_protection"], True]],
+ )
+
+ aci = ACIModule(module)
+ state = module.params.get("state")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="infraInfra",
+ aci_rn="infra",
+ ),
+ child_classes=["epIpAgingP", "epControlP", "epLoopProtectP"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ child_configs = []
+ ip_aging = module.params.get("ip_aging")
+ roque_ep_control = module.params.get("roque_ep_control")
+ ep_loop_protection = module.params.get("ep_loop_protection")
+
+ if ip_aging:
+ child_configs.append(
+ {"epIpAgingP": {"attributes": {"name": "default", "adminSt": aci.boolean(ip_aging.get("admin_state"), "enabled", "disabled")}}}
+ )
+
+ if roque_ep_control:
+ child_configs.append(
+ {
+ "epControlP": {
+ "attributes": {
+ "name": "default",
+ "adminSt": aci.boolean(roque_ep_control.get("admin_state"), "enabled", "disabled"),
+ "rogueEpDetectIntvl": roque_ep_control.get("interval"),
+ "rogueEpDetectMult": roque_ep_control.get("multiplication_factor"),
+ "holdIntvl": roque_ep_control.get("hold_interval"),
+ }
+ }
+ }
+ )
+
+ if ep_loop_protection:
+ actions = None
+ if ep_loop_protection.get("action"):
+ actions = ",".join(sorted([EP_LOOP_PROTECTION_ACTION_MAPPING.get(action) for action in ep_loop_protection.get("action")]))
+ child_configs.append(
+ {
+ "epLoopProtectP": {
+ "attributes": {
+ "name": "default",
+ "adminSt": aci.boolean(ep_loop_protection.get("admin_state"), "enabled", "disabled"),
+ "loopDetectIntvl": ep_loop_protection.get("interval"),
+ "loopDetectMult": ep_loop_protection.get("multiplication_factor"),
+ "action": actions,
+ }
+ }
+ }
+ )
+
+ aci.payload(
+ aci_class="infraInfra",
+ class_config=dict(),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="infraInfra")
+
+ aci.post_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_system_global_aes_passphrase_encryption.py b/ansible_collections/cisco/aci/plugins/modules/aci_system_global_aes_passphrase_encryption.py
new file mode 100644
index 000000000..f316a4b22
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_system_global_aes_passphrase_encryption.py
@@ -0,0 +1,236 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.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
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_system_global_aes_passphrase_encryption
+short_description: Manage Global AES Passphrase Encryption Settings (pki:ExportEncryptionKey)
+description:
+- Manage Global AES Passphrase Encryption Settings on Cisco ACI fabrics.
+options:
+ passphrase:
+ description:
+ - The AES passphrase to use for configuration export encryption.
+ - This cannot be modified once in place on the APIC. To modify an existing passphrase, you must delete it by sending a request with state C(absent).
+ - The value of the passphrase will not be shown in the results of a C(query).
+ type: str
+ enable:
+ description:
+ - Whether to enable strong encryption.
+ - The APIC defaults to C(false) when unset during creation.
+ - Note that this will be set back to False when deleting an existing passphrase.
+ type: bool
+ state:
+ description:
+ - Use C(present) to create a passphrase or to change the enable setting.
+ - Use C(absent) to delete the existing passphrase.
+ - Use C(query) for showing current configuration.
+ type: str
+ choices: [ absent, present, query ]
+ default: present
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+seealso:
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(pki:ExportEncryptionKey).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Tim Cragg (@timcragg)
+- Akini Ross (@akinross)
+"""
+
+EXAMPLES = r"""
+- name: Enable encryption with a passphrase
+ cisco.aci.aci_system_global_aes_passphrase_encryption:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ passphrase: ansible_passphrase
+ enable: yes
+ state: present
+ delegate_to: localhost
+
+- name: Query passphrase settings
+ cisco.aci.aci_system_global_aes_passphrase_encryption:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ delegate_to: localhost
+ register: query_result
+
+- name: Clear encryption key
+ cisco.aci.aci_system_global_aes_passphrase_encryption:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: absent
+ delegate_to: localhost
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ passphrase=dict(type="str", no_log=True),
+ enable=dict(type="bool"),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ )
+
+ aci = ACIModule(module)
+
+ passphrase = module.params.get("passphrase")
+ enable = aci.boolean(module.params.get("enable"))
+ state = module.params.get("state")
+
+ aci.construct_url(
+ root_class=dict(
+ aci_class="pkiExportEncryptionKey",
+ aci_rn="exportcryptkey",
+ ),
+ )
+
+ aci.get_existing()
+
+ if state in ["present", "absent"]:
+ class_config = dict(passphrase=passphrase, strongEncryptionEnabled=enable) if state == "present" else dict(clearEncryptionKey="yes")
+
+ aci.payload(aci_class="pkiExportEncryptionKey", class_config=class_config)
+
+ aci.get_diff(aci_class="pkiExportEncryptionKey")
+
+ aci.post_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tag.py b/ansible_collections/cisco/aci/plugins/modules/aci_tag.py
index 9508f1d6b..9e56ae068 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_tag.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_tag.py
@@ -28,6 +28,7 @@ options:
description:
- Value of the property.
type: str
+ default: ""
tag_type:
description:
- Type of tag object.
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py
index c1896bb3a..c499adaaa 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py
@@ -253,7 +253,7 @@ from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, ac
BOUNCE_TRIG_MAPPING = dict(
coop="protocol",
- rarp="rarp-flood",
+ flood="rarp-flood",
)
@@ -308,7 +308,7 @@ def main():
if move_frequency == 0:
move_frequency = "none"
remote_ep_interval = module.params.get("remote_ep_interval")
- if remote_ep_interval is not None and remote_ep_interval not in range(120, 65536):
+ if remote_ep_interval is not None and remote_ep_interval != 0 and remote_ep_interval not in range(120, 65536):
module.fail_json(msg="The remote_ep_interval must be a value of 0 or between 120 and 65535")
if remote_ep_interval == 0:
remote_ep_interval = "infinite"
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py
index 952e8fe28..a9907e983 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py
@@ -155,6 +155,7 @@ EXAMPLES = r"""
destination_group: group1
state: query
delegate_to: localhost
+ register: query_result
- name: Query all SPAN destination groups
cisco.aci.aci_tenant_span_dst_group:
@@ -163,6 +164,7 @@ EXAMPLES = r"""
password: SomeSecretPassword
state: query
delegate_to: localhost
+ register: query_result
"""
RETURN = r"""
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py
index ab3e8b341..260b9b392 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py
@@ -18,7 +18,7 @@ description:
options:
admin_state:
description:
- - Enable or disable the span sources.
+ - Enable C(true) or disable C(false) the span sources.
- The APIC defaults to C(true) when unset during creation.
type: bool
description:
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py
index 02d118416..0ac97b03c 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py
@@ -227,8 +227,7 @@ url:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
-
-DIRECTION_MAP = {"incoming": "in", "outgoing": "out", "both": "both"}
+from ansible_collections.cisco.aci.plugins.module_utils.constants import SPAN_DIRECTION_MAP
def main():
@@ -237,7 +236,7 @@ def main():
argument_spec.update(aci_owner_spec())
argument_spec.update(
description=dict(type="str", aliases=["descr"]),
- direction=dict(type="str", choices=["incoming", "outgoing", "both"]),
+ direction=dict(type="str", choices=list(SPAN_DIRECTION_MAP.keys())),
name=dict(type="str"), # Not required for querying all objects
src_ap=dict(type="str", aliases=["ap"]),
src_epg=dict(type="str", aliases=["epg"]),
@@ -298,7 +297,7 @@ def main():
aci.payload(
aci_class="spanSrc",
- class_config=dict(descr=description, name=name, dir=DIRECTION_MAP.get(direction)),
+ class_config=dict(descr=description, name=name, dir=SPAN_DIRECTION_MAP.get(direction)),
child_configs=[{"spanRsSrcToEpg": {"attributes": {"tDn": tdn}}}],
)
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py b/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py
index 18a38566a..adec9bb9c 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py
@@ -273,6 +273,9 @@ def main():
["state", "absent", ["pool", "pool_allocation_mode", "block_end", "block_start"]],
["state", "present", ["pool", "pool_allocation_mode", "block_end", "block_start"]],
],
+ required_together=[
+ ["pool", "pool_allocation_mode"],
+ ],
)
allocation_mode = module.params.get("allocation_mode")
@@ -316,11 +319,8 @@ def main():
aci_block_mo = None
# ACI Pool URL requires the allocation mode (ex: uni/infra/vlanns-[poolname]-static)
- if pool is not None:
- if pool_allocation_mode is not None:
- pool_name = "[{0}]-{1}".format(pool, pool_allocation_mode)
- else:
- module.fail_json(msg="ACI requires the 'pool_allocation_mode' when 'pool' is provided")
+ if pool:
+ pool_name = "[{0}]-{1}".format(pool, pool_allocation_mode)
aci = ACIModule(module)
aci.construct_url(
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py
index b066d8b91..f8a51529c 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py
@@ -33,7 +33,7 @@ options:
- Version of the VMware DVS.
type: str
aliases: []
- choices: [ 'unmanaged', '5.1', '5.5', '6.0', '6.5', '6.6', '7.0' ]
+ choices: [ 'unmanaged', '5.1', '5.5', '6.0', '6.5', '6.6', '7.0', '8.0' ]
stats_collection:
description:
- Whether stats collection is enabled.
@@ -274,7 +274,7 @@ def main():
argument_spec.update(
name=dict(type="str"),
controller_hostname=dict(type="str"),
- dvs_version=dict(type="str", choices=["unmanaged", "5.1", "5.5", "6.0", "6.5", "6.6", "7.0"]),
+ dvs_version=dict(type="str", choices=["unmanaged", "5.1", "5.5", "6.0", "6.5", "6.6", "7.0", "8.0"]),
stats_collection=dict(type="str", default="disabled", choices=["enabled", "disabled"]),
domain=dict(type="str", aliases=["domain_name", "domain_profile"]),
state=dict(type="str", default="present", choices=["absent", "present", "query"]),
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py b/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py
index 49c0c9fc1..1aa87f2a3 100644
--- a/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py
@@ -43,6 +43,13 @@ options:
- The description for the VRF.
type: str
aliases: [ descr ]
+ ip_data_plane_learning:
+ description:
+ - Whether IP data plane learning is enabled or disabled.
+ - The APIC defaults to C(enabled) when unset during creation.
+ type: str
+ choices: [ enabled, disabled ]
+ aliases: [ ip_dataplane_learning ]
state:
description:
- Use C(present) or C(absent) for adding or removing.
@@ -256,6 +263,7 @@ def main():
preferred_group=dict(type="str", choices=["enabled", "disabled"]),
match_type=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]),
name_alias=dict(type="str"),
+ ip_data_plane_learning=dict(type="str", choices=["enabled", "disabled"], aliases=["ip_dataplane_learning"]),
)
module = AnsibleModule(
@@ -276,6 +284,7 @@ def main():
name_alias = module.params.get("name_alias")
preferred_group = module.params.get("preferred_group")
match_type = module.params.get("match_type")
+ ip_data_plane_learning = module.params.get("ip_data_plane_learning")
if match_type is not None:
match_type = MATCH_TYPE_MAPPING[match_type]
@@ -308,6 +317,7 @@ def main():
pcEnfPref=policy_control_preference,
name=vrf,
nameAlias=name_alias,
+ ipDataPlaneLearning=ip_data_plane_learning,
),
child_configs=[
dict(vzAny=dict(attributes=dict(prefGrMemb=preferred_group, matchT=match_type))),
diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vrf_leak_internal_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_vrf_leak_internal_subnet.py
new file mode 100644
index 000000000..67432de61
--- /dev/null
+++ b/ansible_collections/cisco/aci/plugins/modules/aci_vrf_leak_internal_subnet.py
@@ -0,0 +1,397 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2023, Abraham Mughal (@abmughal) abmughal@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": "certified"}
+
+DOCUMENTATION = r"""
+---
+module: aci_vrf_leak_internal_subnet
+short_description: Manage VRF leaking of subnets (fv:leakInternalSubnet)
+description:
+- Manage the leaking of internal subnets under the VRF.
+options:
+ tenant:
+ description:
+ - The name of the Tenant the VRF belongs to.
+ type: str
+ aliases: [ tenant_name ]
+ vrf:
+ description:
+ - The name of the VRF.
+ type: str
+ aliases: [ context, name, vrf_name ]
+ description:
+ description:
+ - The description for the VRF Leak Internal Subnet.
+ type: str
+ aliases: [ descr ]
+ name_alias:
+ description:
+ - The alias for the current object. This relates to the nameAlias field in ACI.
+ type: str
+ scope:
+ description:
+ - Scope of the object.
+ type: str
+ choices: [ public, private, shared ]
+ default: private
+ leak_to:
+ description:
+ - The VRFs to leak the subnet routes into.
+ type: list
+ elements: dict
+ suboptions:
+ tenant:
+ description:
+ - Name of the tenant.
+ type: str
+ aliases: [ tenant_name ]
+ vrf:
+ description:
+ - Name of the VRF.
+ type: str
+ aliases: [ vrf_name ]
+ ip:
+ description:
+ - The IP address / subnet used to match routes to be leaked.
+ type: 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
+extends_documentation_fragment:
+- cisco.aci.aci
+- cisco.aci.annotation
+- cisco.aci.owner
+
+notes:
+- The C(tenant) and C(vrf) used must exist before using this module in your playbook.
+ The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_vrf) modules can be used for this.
+seealso:
+- module: cisco.aci.aci_tenant
+- module: cisco.aci.aci_vrf
+- name: APIC Management Information Model reference
+ description: More information about the internal APIC class B(leak:InternalSubnet).
+ link: https://developer.cisco.com/docs/apic-mim-ref/
+author:
+- Abraham Mughal (@abmughal)
+"""
+
+EXAMPLES = r"""
+- name: Create leak internal subnet
+ cisco.aci.aci_vrf_leak_internal_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ vrf: vrf_lab
+ tenant: lab_tenant
+ descr: Lab VRF
+ state: present
+ leak_to:
+ - vrf: "test"
+ tenant: "lab_tenant"
+ - vrf: "test2"
+ tenant: "lab_tenant"
+ description: Ansible Test
+ ip: 1.1.1.2
+ delegate_to: localhost
+
+- name: Remove a subnet from leaking
+ cisco.aci.aci_vrf_leak_internal_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ vrf: vrf_lab
+ tenant: lab_tenant
+ state: absent
+ leak_to:
+ - vrf: "test2"
+ tenant: "lab_tenant"
+ description: Ansible Test
+ ip: 1.1.1.2
+ delegate_to: localhost
+
+- name: Delete leak internal subnet
+ cisco.aci.aci_vrf_leak_internal_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ vrf: vrf_lab
+ tenant: lab_tenant
+ state: absent
+ description: Ansible Test
+ ip: 1.1.1.2
+ delegate_to: localhost
+
+- name: Query all leak internal subnet
+ cisco.aci.aci_vrf_leak_internal_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ state: query
+ ip: 1.1.1.2
+ delegate_to: localhost
+ register: query_result
+
+- name: Query leak internal subnet
+ cisco.aci.aci_vrf_leak_internal_subnet:
+ host: apic
+ username: admin
+ password: SomeSecretPassword
+ vrf: vrf_lab
+ tenant: lab_tenant
+ state: query
+ ip: 1.1.1.2
+ delegate_to: localhost
+ register: query_result
+"""
+
+RETURN = r"""
+current:
+ description: The existing configuration from the APIC after the module has finished
+ returned: success
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+error:
+ description: The error information as returned from the APIC
+ returned: failure
+ type: dict
+ sample:
+ {
+ "code": "122",
+ "text": "unknown managed object class foo"
+ }
+raw:
+ description: The raw output returned by the APIC REST API (xml or json)
+ returned: parse error
+ type: str
+ sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>'
+sent:
+ description: The actual/minimal configuration pushed to the APIC
+ returned: info
+ type: list
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment"
+ }
+ }
+ }
+previous:
+ description: The original configuration from the APIC before the module has started
+ returned: info
+ type: list
+ sample:
+ [
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production",
+ "dn": "uni/tn-production",
+ "name": "production",
+ "nameAlias": "",
+ "ownerKey": "",
+ "ownerTag": ""
+ }
+ }
+ }
+ ]
+proposed:
+ description: The assembled configuration from the user-provided parameters
+ returned: info
+ type: dict
+ sample:
+ {
+ "fvTenant": {
+ "attributes": {
+ "descr": "Production environment",
+ "name": "production"
+ }
+ }
+ }
+filter_string:
+ description: The filter string used for the request
+ returned: failure or debug
+ type: str
+ sample: ?rsp-prop-include=config-only
+method:
+ description: The HTTP method used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: POST
+response:
+ description: The HTTP response from the APIC
+ returned: failure or debug
+ type: str
+ sample: OK (30 bytes)
+status:
+ description: The HTTP status from the APIC
+ returned: failure or debug
+ type: int
+ sample: 200
+url:
+ description: The HTTP url used for the request to the APIC
+ returned: failure or debug
+ type: str
+ sample: https://10.11.12.13/api/mo/uni/tn-production.json
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec
+
+
+def main():
+ argument_spec = aci_argument_spec()
+ argument_spec.update(aci_annotation_spec())
+ argument_spec.update(aci_owner_spec())
+ argument_spec.update(
+ tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects
+ vrf=dict(type="str", aliases=["context", "name", "vrf_name"]), # Not required for querying all objects
+ leak_to=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ vrf=dict(type="str", aliases=["vrf_name"]),
+ tenant=dict(type="str", aliases=["tenant_name"]),
+ ),
+ ),
+ description=dict(type="str", aliases=["descr"]),
+ state=dict(type="str", default="present", choices=["absent", "present", "query"]),
+ name_alias=dict(type="str"),
+ scope=dict(type="str", default="private", choices=["public", "private", "shared"]),
+ ip=dict(type="str"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_if=[
+ ["state", "absent", ["tenant", "vrf"]],
+ ["state", "present", ["tenant", "vrf", "leak_to"]],
+ ],
+ )
+
+ description = module.params.get("description")
+ state = module.params.get("state")
+ tenant = module.params.get("tenant")
+ vrf = module.params.get("vrf")
+ leak_to = module.params.get("leak_to")
+ name_alias = module.params.get("name_alias")
+ scope = module.params.get("scope")
+ ip = module.params.get("ip")
+
+ aci = ACIModule(module)
+ aci.construct_url(
+ root_class=dict(
+ aci_class="fvTenant",
+ aci_rn="tn-{0}".format(tenant),
+ module_object=tenant,
+ target_filter={"name": tenant},
+ ),
+ subclass_1=dict(
+ aci_class="fvCtx",
+ aci_rn="ctx-{0}".format(vrf),
+ module_object=vrf,
+ target_filter={"name": vrf},
+ ),
+ subclass_2=dict(
+ aci_class="leakRoutes",
+ aci_rn="leakroutes",
+ module_object=True,
+ ),
+ subclass_3=dict(
+ aci_class="leakInternalSubnet",
+ aci_rn="leakintsubnet-[{0}]".format(ip),
+ module_object=ip,
+ target_filter={"ip": ip},
+ ),
+ child_classes=["leakTo"],
+ )
+
+ aci.get_existing()
+
+ if state == "present":
+ child_configs = []
+
+ subnet_rn_list = []
+ for subnet in leak_to:
+ subnet_rn_list.append("to-[{0}]-[{1}]".format(subnet.get("tenant"), subnet.get("vrf")))
+ child_configs.append(
+ dict(
+ leakTo=dict(
+ attributes=dict(
+ ctxName=subnet.get("vrf"),
+ tenantName=subnet.get("tenant"),
+ scope=scope,
+ )
+ )
+ )
+ )
+
+ if isinstance(aci.existing, list) and len(aci.existing) > 0:
+ for child in aci.existing[0].get("leakInternalSubnet", {}).get("children", {}):
+ child_attributes = child.get("leakTo", {}).get("attributes", {})
+ if child_attributes and "to-[{0}]-[{1}]".format(child_attributes.get("tenantName"), child_attributes.get("ctxName")) not in subnet_rn_list:
+ child_configs.append(
+ dict(
+ leakTo=dict(
+ attributes=dict(
+ ctxName=child_attributes.get("ctxName"),
+ tenantName=child_attributes.get("tenantName"),
+ status="deleted",
+ )
+ )
+ )
+ )
+
+ aci.payload(
+ aci_class="leakInternalSubnet",
+ class_config=dict(
+ descr=description,
+ ip=ip,
+ scope=scope,
+ nameAlias=name_alias,
+ ),
+ child_configs=child_configs,
+ )
+
+ aci.get_diff(aci_class="leakInternalSubnet")
+
+ if aci.existing:
+ aci.post_config()
+ else:
+ aci.post_config(parent_class="leakRoutes")
+
+ elif state == "absent":
+ aci.delete_config()
+
+ aci.exit_json()
+
+
+if __name__ == "__main__":
+ main()