diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/cisco/aci/plugins | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/aci/plugins')
172 files changed, 54947 insertions, 0 deletions
diff --git a/ansible_collections/cisco/aci/plugins/doc_fragments/__init__.py b/ansible_collections/cisco/aci/plugins/doc_fragments/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/doc_fragments/__init__.py diff --git a/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py b/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py new file mode 100644 index 000000000..2bed3dc59 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2017, Swetha Chunduri (@schunduri) +# 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 + + +class ModuleDocFragment(object): + # Standard files documentation fragment + DOCUMENTATION = r""" +options: + host: + description: + - 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: + - Port number to be used for REST connection. + - The default value depends on parameter C(use_ssl). + - If the value is not specified in the task, the value of environment variable C(ACI_PORT) will be used instead. + type: int + username: + 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. + type: str + default: admin + aliases: [ user ] + password: + description: + - The password to use for authentication. + - This option is mutual exclusive with C(private_key). If C(private_key) is provided too, it will be used instead. + - If the value is not specified in the task, the value of environment variables C(ACI_PASSWORD) or C(ANSIBLE_NET_PASSWORD) will be used instead. + type: str + private_key: + description: + - Either a PEM-formatted private key file or the private key content used for signature-based authentication. + - This value also influences the default C(certificate_name) that is used. + - This option is mutual exclusive with C(password). If C(password) is provided too, it will be ignored. + - If the value is not specified in the task, the value of environment variable C(ACI_PRIVATE_KEY) or C(ANSIBLE_NET_SSH_KEYFILE) will be used instead. + type: str + aliases: [ cert_key ] + certificate_name: + description: + - The X.509 certificate name attached to the APIC AAA user used for signature-based authentication. + - If a C(private_key) filename was provided, this defaults to the C(private_key) basename, without extension. + - If PEM-formatted content was provided for C(private_key), this defaults to the C(username) value. + - If the value is not specified in the task, the value of environment variable C(ACI_CERTIFICATE_NAME) will be used instead. + type: str + aliases: [ cert_name ] + output_level: + description: + - Influence the output of this ACI module. + - C(normal) means the standard output, incl. C(current) dict + - C(info) adds informational output, incl. C(previous), C(proposed) and C(sent) dicts + - C(debug) adds debugging output, incl. C(filter_string), C(method), C(response), C(status) and C(url) information + - If the value is not specified in the task, the value of environment variable C(ACI_OUTPUT_LEVEL) will be used instead. + type: str + choices: [ debug, info, normal ] + default: normal + timeout: + 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. + 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. + 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. + 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. + 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. + - If the value is not specified in the task, the value of environment variable C(ACI_OUTPUT_PATH) will be used instead. + type: str +seealso: +- ref: aci_guide + description: Detailed information on how to manage your ACI infrastructure using Ansible. +- ref: aci_dev_guide + description: Detailed guide on how to write your own Cisco ACI modules to contribute. +""" diff --git a/ansible_collections/cisco/aci/plugins/doc_fragments/annotation.py b/ansible_collections/cisco/aci/plugins/doc_fragments/annotation.py new file mode 100644 index 000000000..c037b4bee --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/doc_fragments/annotation.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# 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 + + +class ModuleDocFragment(object): + # Standard files documentation fragment + DOCUMENTATION = r""" +options: + annotation: + description: + - User-defined string for annotating an object. + - If the value is not specified in the task, the value of environment variable C(ACI_ANNOTATION) will be used instead. + - If the value is not specified in the task and environment variable C(ACI_ANNOTATION) then the default value will be used. + type: str + default: orchestrator:ansible +""" diff --git a/ansible_collections/cisco/aci/plugins/doc_fragments/owner.py b/ansible_collections/cisco/aci/plugins/doc_fragments/owner.py new file mode 100644 index 000000000..31d3eedfb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/doc_fragments/owner.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +# 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 + + +class ModuleDocFragment(object): + # Standard files documentation fragment + DOCUMENTATION = r""" +options: + owner_key: + description: + - User-defined string for the ownerKey attribute of an ACI object. + - This attribute represents a key for enabling clients to own their data for entity correlation. + - If the value is not specified in the task, the value of environment variable C(ACI_OWNER_KEY) will be used instead. + type: str + owner_tag: + description: + - User-defined string for the ownerTag attribute of an ACI object. + - This attribute represents a tag for enabling clients to add their own data. + - For example, to indicate who created this object. + - If the value is not specified in the task, the value of environment variable C(ACI_OWNER_TAG) will be used instead. + type: str +""" diff --git a/ansible_collections/cisco/aci/plugins/lookup/__init__.py b/ansible_collections/cisco/aci/plugins/lookup/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/lookup/__init__.py diff --git a/ansible_collections/cisco/aci/plugins/lookup/interface_range.py b/ansible_collections/cisco/aci/plugins/lookup/interface_range.py new file mode 100644 index 000000000..c2370197e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/lookup/interface_range.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, 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 = """ + name: interface_range + short_description: query interfaces from a range or comma separated list of ranges + description: + - this lookup returns interfaces from a range or comma separated list of ranges given to it + notes: + - duplicate interfaces from overlapping ranges will only be returned once + options: + _terms: + description: comma separated strings of interface ranges + required: True +""" + +EXAMPLES = """ +- name: "loop through range of interfaces" + ansible.builtin.debug: + msg: "{{ item }}" + with_items: "{{ query('cisco.aci.interface_range', '1/1-4,1/20-25', '1/5', '1/2/3/8-10', '5/0-2') }}" +""" + +RETURN = """ + _list: + description: list of interfaces + type: list + elements: str +""" + +import re + +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase + + +class LookupModule(LookupBase): + def run(self, terms, **kwargs): + interfaces = [] + errors = [] + + for interface_range in ",".join(terms).replace(" ", "").split(","): + if re.fullmatch(r"((\d+/)+\d+-\d+$)", interface_range): + slots = interface_range.rsplit("/", 1)[0] + range_start, range_stop = interface_range.rsplit("/", 1)[1].split("-") + if int(range_stop) > int(range_start): + for x in range(int(range_start), int(range_stop) + 1): + interfaces.append("{0}/{1}".format(slots, x)) + else: + errors.append(interface_range) + elif re.fullmatch(r"((\d+/)+\d+$)", interface_range): + interfaces.append(interface_range) + else: + errors.append(interface_range) + if errors: + raise AnsibleError("Invalid range inputs, {0}".format(errors)) + + # Sorted functionality for visual aid only, will result in 1/25, 1/3, 1/31 + # If full sort is needed leverage natsort package (https://github.com/SethMMorton/natsort) + return sorted(set(interfaces)) diff --git a/ansible_collections/cisco/aci/plugins/module_utils/__init__.py b/ansible_collections/cisco/aci/plugins/module_utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/module_utils/__init__.py diff --git a/ansible_collections/cisco/aci/plugins/module_utils/aci.py b/ansible_collections/cisco/aci/plugins/module_utils/aci.py new file mode 100644 index 000000000..5d95e5c1e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/module_utils/aci.py @@ -0,0 +1,1624 @@ +# -*- coding: utf-8 -*- + +# This code is part of Ansible, but is an independent component + +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. + +# Copyright: (c) 2017, Dag Wieers <dag@wieers.com> +# Copyright: (c) 2017, Jacob McGill (@jmcgill298) +# Copyright: (c) 2017, Swetha Chunduri (@schunduri) +# 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> +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import base64 +import json +import os +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 + +# 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 + +# Optional, only used for XML payload +try: + import lxml.etree + + HAS_LXML_ETREE = True +except ImportError: + HAS_LXML_ETREE = False + +# Optional, only used for XML payload +try: + from xmljson import cobra + + HAS_XMLJSON_COBRA = True +except ImportError: + HAS_XMLJSON_COBRA = 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"]), + ), + password=dict( + type="str", + no_log=True, + fallback=(env_fallback, ["ACI_PASSWORD", "ANSIBLE_NET_PASSWORD"]), + ), + # Beware, this is not the same as client_key ! + private_key=dict( + type="str", + aliases=["cert_key"], + no_log=True, + fallback=(env_fallback, ["ACI_PRIVATE_KEY", "ANSIBLE_NET_SSH_KEYFILE"]), + ), + # Beware, this is not the same as client_cert ! + certificate_name=dict( + type="str", + aliases=["cert_name"], + fallback=(env_fallback, ["ACI_CERTIFICATE_NAME"]), + ), + output_level=dict( + type="str", + default="normal", + 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"])), + output_path=dict(type="str", fallback=(env_fallback, ["ACI_OUTPUT_PATH"])), + ) + + +def aci_annotation_spec(): + return dict( + annotation=dict( + type="str", + default="orchestrator:ansible", + fallback=(env_fallback, ["ACI_ANNOTATION"]), + ), + ) + + +def aci_owner_spec(): + return dict( + owner_key=dict(type="str", no_log=False, fallback=(env_fallback, ["ACI_OWNER_KEY"])), + owner_tag=dict(type="str", fallback=(env_fallback, ["ACI_OWNER_TAG"])), + ) + + +def enhanced_lag_spec(): + return dict( + name=dict(type="str", required=True), + lacp_mode=dict(type="str", choices=["active", "passive"]), + load_balancing_mode=dict( + type="str", + choices=[ + "dst-ip", + "dst-ip-l4port", + "dst-ip-vlan", + "dst-ip-l4port-vlan", + "dst-mac", + "dst-l4port", + "src-ip", + "src-ip-l4port", + "src-ip-vlan", + "src-ip-l4port-vlan", + "src-mac", + "src-l4port", + "src-dst-ip", + "src-dst-ip-l4port", + "src-dst-ip-vlan", + "src-dst-ip-l4port-vlan", + "src-dst-mac", + "src-dst-l4port", + "src-port-id", + "vlan", + ], + ), + number_uplinks=dict(type="int"), + ) + + +def netflow_spec(): + return dict( + name=dict(type="str", required=True), + active_flow_timeout=dict(type="int"), + idle_flow_timeout=dict(type="int"), + sampling_rate=dict(type="int"), + ) + + +def expression_spec(): + return dict( + key=dict(type="str", required=True, no_log=False), + operator=dict( + type="str", + choices=[ + "not_in", + "in", + "equals", + "not_equals", + "has_key", + "does_not_have_key", + ], + required=True, + ), + value=dict(type="str"), + ) + + +def aci_contract_qos_spec(): + return dict(type="str", choices=["level1", "level2", "level3", "unspecified"]) + + +def aci_contract_dscp_spec(direction=None): + return dict( + type="str", + aliases=["target" if not direction else "target_{0}".format(direction)], + choices=[ + "AF11", + "AF12", + "AF13", + "AF21", + "AF22", + "AF23", + "AF31", + "AF32", + "AF33", + "AF41", + "AF42", + "AF43", + "CS0", + "CS1", + "CS2", + "CS3", + "CS4", + "CS5", + "CS6", + "CS7", + "EF", + "VA", + "unspecified", + ], + ) + + +def route_control_profile_spec(): + return dict( + profile=dict(type="str", required=True), + l3out=dict(type="str"), + direction=dict(type="str", required=True), + tenant=dict(type="str", required=True), + ) + + +class ACIModule(object): + def __init__(self, module): + self.module = module + self.params = module.params + self.result = dict(changed=False) + self.headers = dict() + self.child_classes = set() + + # error output + self.error = dict(code=None, text=None) + + # normal output + self.existing = None + + # info output + self.config = dict() + self.original = None + self.proposed = dict() + self.stdout = None + + # debug output + self.filter_string = "" + self.obj_filter = None + self.method = None + self.path = None + self.response = None + self.status = None + self.url = None + + # aci_rest output + self.imdata = None + self.totalCount = None + + # Ensure protocol is set + self.define_protocol() + + if self.module._debug: + self.module.warn("Enable debug output because ANSIBLE_DEBUG was set.") + self.params["output_level"] = "debug" + + 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") + + def boolean(self, value, true="yes", false="no"): + """Return an acceptable value back""" + + # When we expect value is of type=bool + if value is None: + return None + elif value is True: + return true + elif value is False: + return false + + # If all else fails, escalate back to user + self.module.fail_json(msg="Boolean value '%s' is an invalid ACI boolean value.") + + def iso8601_format(self, dt): + """Return an ACI-compatible ISO8601 formatted time: 2123-12-12T00:00:00.000+00:00""" + try: + return dt.isoformat(timespec="milliseconds") + except Exception: + tz = dt.strftime("%z") + return "%s.%03d%s:%s" % ( + dt.strftime("%Y-%m-%dT%H:%M:%S"), + dt.microsecond / 1000, + tz[:3], + tz[3:], + ) + + def define_protocol(self): + """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" + + 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 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 + payload = { + "aaaUser": { + "attributes": { + "name": 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"), + ) + + # Handle APIC response + if auth.get("status") != 200: + self.response = auth.get("msg") + self.status = auth.get("status") + try: + # APIC error + self.response_json(auth["body"]) + self.fail_json(msg="Authentication failed: %(code)s %(text)s" % self.error) + except KeyError: + # Connection error + self.fail_json(msg="Connection failed for %(url)s. %(msg)s" % auth) + + # Retain cookie for later use + self.headers["Cookie"] = resp.headers.get("Set-Cookie") + + def cert_auth(self, path=None, payload="", method=None): + """Perform APIC signature-based authentication, not the expected SSL client certificate authentication.""" + + if method is None: + method = self.params.get("method").upper() + + # NOTE: ACI documentation incorrectly uses complete URL + if path is None: + path = self.path + path = "/" + path.lstrip("/") + + if payload is None: + payload = "" + + # Check if we got a private key. This allows the use of vaulting the private key. + try: + if HAS_CRYPTOGRAPHY: + key = self.params.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.params.get("private_key")) + except Exception: + if os.path.exists(self.params.get("private_key")): + try: + permission = "r" + if HAS_CRYPTOGRAPHY: + permission = "rb" + 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) + 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: + self.module.fail_json(msg="Cannot load private key file '%(private_key)s'." % 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) + + if self.params.get("certificate_name") is None: + self.params["certificate_name"] = 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 + self.headers["Cookie"] = ( + "APIC-Certificate-Algorithm=v1.0; " + + "APIC-Certificate-DN=%s; " % sig_dn + + "APIC-Certificate-Fingerprint=fingerprint; " + + "APIC-Request-Signature=%s" % to_native(base64.b64encode(sig_signature)) + ) + + def response_json(self, rawoutput): + """Handle APIC JSON response output""" + try: + 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.result["raw"] = rawoutput + return + + # Extract JSON API output + self.imdata = jsondata.get("imdata") + if self.imdata is None: + self.imdata = dict() + self.totalCount = int(jsondata.get("totalCount")) + + # Handle possible APIC error information + self.response_error() + + def response_xml(self, rawoutput): + """Handle APIC XML response output""" + + # NOTE: The XML-to-JSON conversion is using the "Cobra" convention + try: + xml = lxml.etree.fromstring(to_bytes(rawoutput)) + 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.result["raw"] = rawoutput + return + + # Reformat as ACI does for JSON API output + 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")) + + # Handle possible APIC error information + self.response_error() + + def response_error(self): + """Set error information when found""" + + # Handle possible APIC error information + if self.totalCount != "0": + try: + self.error = self.imdata[0].get("error").get("attributes") + 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""" + accepted_params = dict((k, v) for (k, v) in params.items() if v is not None) + if accepted_params: + if self.filter_string: + self.filter_string += "&" + else: + self.filter_string = "?" + self.filter_string += "&".join(["%s=%s" % (k, v) for (k, v) in accepted_params.items()]) + + # TODO: This could be designed to accept multiple obj_classes and keys + def build_filter(self, obj_class, params): + """Build an APIC filter based on obj_class and key-value pairs""" + accepted_params = dict((k, v) for (k, v) in params.items() if v is not None) + if len(accepted_params) == 1: + return ",".join('eq({0}.{1},"{2}")'.format(obj_class, k, v) for (k, v) in accepted_params.items()) + elif len(accepted_params) > 1: + return "and(" + ",".join(['eq({0}.{1},"{2}")'.format(obj_class, k, v) for (k, v) in accepted_params.items()]) + ")" + + def _deep_url_path_builder(self, obj): + target_class = obj.get("target_class") + target_filter = obj.get("target_filter") + subtree_class = obj.get("subtree_class") + subtree_filter = obj.get("subtree_filter") + object_rn = obj.get("object_rn") + mo = obj.get("module_object") + add_subtree_filter = obj.get("add_subtree_filter") + add_target_filter = obj.get("add_target_filter") + + if self.module.params.get("state") in ("absent", "present") and mo is not None: + self.path = "api/mo/uni/{0}.json".format(object_rn) + self.update_qs({"rsp-prop-include": "config-only"}) + + else: + # State is 'query' + if object_rn is not None: + # Query for a specific object in the module's class + self.path = "api/mo/uni/{0}.json".format(object_rn) + else: + self.path = "api/class/{0}.json".format(target_class) + + if add_target_filter: + self.update_qs({"query-target-filter": self.build_filter(target_class, target_filter)}) + + if add_subtree_filter: + self.update_qs({"rsp-subtree-filter": self.build_filter(subtree_class, subtree_filter)}) + + if self.params.get("port") is not None: + self.url = "{protocol}://{host}:{port}/{path}".format(path=self.path, **self.module.params) + + else: + self.url = "{protocol}://{host}/{path}".format(path=self.path, **self.module.params) + + if self.child_classes: + self.update_qs( + { + "rsp-subtree": "full", + "rsp-subtree-class": ",".join(sorted(self.child_classes)), + } + ) + + def _deep_url_parent_object(self, parent_objects, parent_class): + for parent_object in parent_objects: + if parent_object.get("aci_class") is parent_class: + return parent_object + + return None + + def construct_deep_url(self, target_object, parent_objects=None, child_classes=None): + """ + This method is used to retrieve the appropriate URL path and filter_string to make the request to the APIC. + + :param target_object: The target class dictionary containing parent_class, aci_class, aci_rn, target_filter, and module_object keys. + :param parent_objects: The parent class list of dictionaries containing parent_class, aci_class, aci_rn, target_filter, and module_object keys. + :param child_classes: The list of child classes that the module supports along with the object. + :type target_object: dict + :type parent_objects: list[dict] + :type child_classes: list[string] + :return: The path and filter_string needed to build the full URL. + """ + + self.filter_string = "" + rn_builder = None + subtree_classes = None + add_subtree_filter = False + add_target_filter = False + has_target_query = False + has_target_query_compare = False + has_target_query_difference = False + has_target_query_called = False + + if child_classes is None: + self.child_classes = set() + else: + self.child_classes = set(child_classes) + + target_parent_class = target_object.get("parent_class") + target_class = target_object.get("aci_class") + target_rn = target_object.get("aci_rn") + target_filter = target_object.get("target_filter") + target_module_object = target_object.get("module_object") + + url_path_object = dict( + target_class=target_class, + target_filter=target_filter, + subtree_class=target_class, + subtree_filter=target_filter, + module_object=target_module_object, + ) + + if target_module_object is not None: + rn_builder = target_rn + else: + has_target_query = True + has_target_query_compare = True + + if parent_objects is not None: + current_parent_class = target_parent_class + has_parent_query_compare = False + has_parent_query_difference = False + is_first_parent = True + is_single_parent = None + search_classes = set() + + while current_parent_class != "uni": + parent_object = self._deep_url_parent_object(parent_objects=parent_objects, parent_class=current_parent_class) + + if parent_object is not None: + parent_parent_class = parent_object.get("parent_class") + parent_class = parent_object.get("aci_class") + parent_rn = parent_object.get("aci_rn") + parent_filter = parent_object.get("target_filter") + parent_module_object = parent_object.get("module_object") + + if is_first_parent: + is_single_parent = True + else: + is_single_parent = False + is_first_parent = False + + if parent_parent_class != "uni": + search_classes.add(parent_class) + + if parent_module_object is not None: + if rn_builder is not None: + rn_builder = "{0}/{1}".format(parent_rn, rn_builder) + else: + rn_builder = parent_rn + + url_path_object["target_class"] = parent_class + url_path_object["target_filter"] = parent_filter + + has_target_query = False + else: + rn_builder = None + subtree_classes = search_classes + + has_target_query = True + if is_single_parent: + has_parent_query_compare = True + + current_parent_class = parent_parent_class + else: + raise ValueError("Reference error for parent_class '{0}'. Each parent_class must reference a valid object".format(current_parent_class)) + + if not has_target_query_difference and not has_target_query_called: + if has_target_query is not has_target_query_compare: + has_target_query_difference = True + else: + if not has_parent_query_difference and has_target_query is not has_parent_query_compare: + has_parent_query_difference = True + has_target_query_called = True + + if not has_parent_query_difference and has_parent_query_compare and target_module_object is not None: + add_target_filter = True + + elif has_parent_query_difference and target_module_object is not None: + add_subtree_filter = True + self.child_classes.add(target_class) + + if has_target_query: + add_target_filter = True + + elif has_parent_query_difference and not has_target_query and target_module_object is None: + self.child_classes.add(target_class) + self.child_classes.update(subtree_classes) + + elif not has_parent_query_difference and not has_target_query and target_module_object is None: + self.child_classes.add(target_class) + + elif not has_target_query and is_single_parent and target_module_object is None: + self.child_classes.add(target_class) + + url_path_object["object_rn"] = rn_builder + url_path_object["add_subtree_filter"] = add_subtree_filter + url_path_object["add_target_filter"] = add_target_filter + + self._deep_url_path_builder(url_path_object) + + def construct_url( + self, + root_class, + subclass_1=None, + subclass_2=None, + subclass_3=None, + subclass_4=None, + subclass_5=None, + child_classes=None, + config_only=True, + ): + """ + This method is used to retrieve the appropriate URL path and filter_string to make the request to the APIC. + + :param root_class: The top-level class dictionary containing aci_class, aci_rn, target_filter, and module_object keys. + :param sublass_1: The second-level class dictionary containing aci_class, aci_rn, target_filter, and module_object keys. + :param sublass_2: The third-level class dictionary containing aci_class, aci_rn, target_filter, and module_object keys. + :param sublass_3: The fourth-level class dictionary containing aci_class, aci_rn, target_filter, and module_object keys. + :param child_classes: The list of child classes that the module supports along with the object. + :type root_class: dict + :type subclass_1: dict + :type subclass_2: dict + :type subclass_3: dict + :type subclass_4: dict + :type subclass_5: dict + :type child_classes: list + :return: The path and filter_string needed to build the full URL. + """ + self.filter_string = "" + + if child_classes is None: + self.child_classes = set() + else: + 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, + ) + 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: + self._construct_url_4(root_class, subclass_1, subclass_2, subclass_3, config_only) + elif subclass_2 is not None: + self._construct_url_3(root_class, subclass_1, subclass_2, config_only) + elif subclass_1 is not None: + self._construct_url_2(root_class, subclass_1, config_only) + else: + self._construct_url_1(root_class, config_only) + + if self.params.get("port") is not None: + self.url = "{protocol}://{host}:{port}/{path}".format(path=self.path, **self.module.params) + else: + self.url = "{protocol}://{host}/{path}".format(path=self.path, **self.module.params) + + if self.child_classes: + # Append child_classes to filter_string if filter string is empty + self.update_qs( + { + "rsp-subtree": "full", + "rsp-subtree-class": ",".join(sorted(self.child_classes)), + } + ) + + def _construct_url_1(self, obj, config_only=True): + """ + This method is used by construct_url when the object is the top-level class. + """ + obj_class = obj.get("aci_class") + obj_rn = obj.get("aci_rn") + obj_filter = obj.get("target_filter") + mo = obj.get("module_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) + if config_only: + self.update_qs({"rsp-prop-include": "config-only"}) + self.obj_filter = obj_filter + elif mo is None: + # Query for all objects of the module's class (filter by properties) + self.path = "api/class/{0}.json".format(obj_class) + if obj_filter is not None: + self.update_qs({"query-target-filter": self.build_filter(obj_class, obj_filter)}) + else: + # Query for a specific object in the module's class + self.path = "api/mo/uni/{0}.json".format(obj_rn) + + def _construct_url_2(self, parent, obj, config_only=True): + """ + This method is used by construct_url when the object is the second-level class. + """ + parent_rn = parent.get("aci_rn") + parent_obj = parent.get("module_object") + obj_class = obj.get("aci_class") + obj_rn = obj.get("aci_rn") + obj_filter = obj.get("target_filter") + mo = obj.get("module_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) + if config_only: + self.update_qs({"rsp-prop-include": "config-only"}) + self.obj_filter = obj_filter + elif parent_obj is None and mo is None: + # Query for all objects of the module's class + self.path = "api/class/{0}.json".format(obj_class) + self.update_qs({"query-target-filter": self.build_filter(obj_class, obj_filter)}) + elif parent_obj is None: # mo is known + # Query for all objects of the module's class that match the provided ID value + self.path = "api/class/{0}.json".format(obj_class) + self.update_qs({"query-target-filter": self.build_filter(obj_class, obj_filter)}) + elif mo is None: # parent_obj is known + # Query for all object's of the module's class that belong to a specific parent object + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}.json".format(parent_rn) + else: + # Query for specific object in the module's class + self.path = "api/mo/uni/{0}/{1}.json".format(parent_rn, obj_rn) + + def _construct_url_3(self, root, parent, obj, config_only=True): + """ + This method is used by construct_url when the object is the third-level class. + """ + root_rn = root.get("aci_rn") + root_obj = root.get("module_object") + parent_class = parent.get("aci_class") + parent_rn = parent.get("aci_rn") + parent_filter = parent.get("target_filter") + parent_obj = parent.get("module_object") + obj_class = obj.get("aci_class") + obj_rn = obj.get("aci_rn") + obj_filter = obj.get("target_filter") + mo = obj.get("module_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) + if config_only: + self.update_qs({"rsp-prop-include": "config-only"}) + self.obj_filter = obj_filter + elif root_obj is None and parent_obj is None and mo is None: + # Query for all objects of the module's class + self.path = "api/class/{0}.json".format(obj_class) + self.update_qs({"query-target-filter": self.build_filter(obj_class, obj_filter)}) + elif root_obj is None and parent_obj is None: # mo is known + # Query for all objects of the module's class matching the provided ID value of the object + self.path = "api/class/{0}.json".format(obj_class) + self.update_qs({"query-target-filter": self.build_filter(obj_class, obj_filter)}) + elif root_obj is None and mo is None: # parent_obj is known + # Query for all objects of the module's class that belong to any parent class + # matching the provided ID value for the parent object + self.child_classes.add(obj_class) + self.path = "api/class/{0}.json".format(parent_class) + self.update_qs({"query-target-filter": self.build_filter(parent_class, parent_filter)}) + elif parent_obj is None and mo is None: # root_obj is known + # Query for all objects of the module's class that belong to a specific root object + self.child_classes.update([parent_class, obj_class]) + self.path = "api/mo/uni/{0}.json".format(root_rn) + # NOTE: No need to select by root_filter + # self.update_qs({'query-target-filter': self.build_filter(root_class, root_filter)}) + elif root_obj is None: # mo and parent_obj are known + # Query for all objects of the module's class that belong to any parent class + # matching the provided ID values for both object and parent object + self.child_classes.add(obj_class) + self.path = "api/class/{0}.json".format(parent_class) + self.update_qs({"query-target-filter": self.build_filter(parent_class, parent_filter)}) + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif parent_obj is None: # mo and root_obj are known + # Query for all objects of the module's class that match the provided ID value and belong to a specific root object + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}.json".format(root_rn) + # NOTE: No need to select by root_filter + # self.update_qs({'query-target-filter': self.build_filter(root_class, root_filter)}) + # TODO: Filter by parent_filter and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif mo is None: # root_obj and parent_obj are known + # Query for all objects of the module's class that belong to a specific parent object + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}.json".format(root_rn, parent_rn) + # NOTE: No need to select by parent_filter + # self.update_qs({'query-target-filter': self.build_filter(parent_class, parent_filter)}) + else: + # Query for a specific object of the module's class + self.path = "api/mo/uni/{0}/{1}/{2}.json".format(root_rn, parent_rn, obj_rn) + + def _construct_url_4(self, root, sec, parent, obj, config_only=True): + """ + This method is used by construct_url when the object is the fourth-level class. + """ + root_rn = root.get("aci_rn") + root_obj = root.get("module_object") + sec_rn = sec.get("aci_rn") + sec_obj = sec.get("module_object") + parent_rn = parent.get("aci_rn") + parent_obj = parent.get("module_object") + obj_class = obj.get("aci_class") + obj_rn = obj.get("aci_rn") + obj_filter = obj.get("target_filter") + mo = obj.get("module_object") + + if self.child_classes is None: + self.child_classes = [obj_class] + + 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) + if config_only: + self.update_qs({"rsp-prop-include": "config-only"}) + self.obj_filter = obj_filter + # TODO: Add all missing cases + elif root_obj is None: + self.child_classes.add(obj_class) + self.path = "api/class/{0}.json".format(obj_class) + self.update_qs({"query-target-filter": self.build_filter(obj_class, obj_filter)}) + elif sec_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}.json".format(root_rn) + # NOTE: No need to select by root_filter + # self.update_qs({'query-target-filter': self.build_filter(root_class, root_filter)}) + # TODO: Filter by sec_filter, parent and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif parent_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}.json".format(root_rn, sec_rn) + # NOTE: No need to select by sec_filter + # self.update_qs({'query-target-filter': self.build_filter(sec_class, sec_filter)}) + # TODO: Filter by parent_filter and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif mo is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}/{2}.json".format(root_rn, sec_rn, parent_rn) + # NOTE: No need to select by parent_filter + # self.update_qs({'query-target-filter': self.build_filter(parent_class, parent_filter)}) + else: + # Query for a specific object of the module's class + self.path = "api/mo/uni/{0}/{1}/{2}/{3}.json".format(root_rn, sec_rn, parent_rn, obj_rn) + + def _construct_url_5(self, root, ter, sec, parent, obj, config_only=True): + """ + This method is used by construct_url when the object is the fourth-level class. + """ + + root_rn = root.get("aci_rn") + root_obj = root.get("module_object") + ter_rn = ter.get("aci_rn") + ter_obj = ter.get("module_object") + sec_rn = sec.get("aci_rn") + sec_obj = sec.get("module_object") + parent_rn = parent.get("aci_rn") + parent_obj = parent.get("module_object") + obj_class = obj.get("aci_class") + obj_rn = obj.get("aci_rn") + obj_filter = obj.get("target_filter") + mo = obj.get("module_object") + + if self.child_classes is None: + self.child_classes = [obj_class] + + 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) + if config_only: + self.update_qs({"rsp-prop-include": "config-only"}) + self.obj_filter = obj_filter + # TODO: Add all missing cases + elif root_obj is None: + self.child_classes.add(obj_class) + self.path = "api/class/{0}.json".format(obj_class) + self.update_qs({"query-target-filter": self.build_filter(obj_class, obj_filter)}) + elif ter_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}.json".format(root_rn) + # NOTE: No need to select by root_filter + # self.update_qs({'query-target-filter': self.build_filter(root_class, root_filter)}) + # TODO: Filter by ter_filter, parent and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif sec_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}.json".format(root_rn, ter_rn) + # NOTE: No need to select by ter_filter + # self.update_qs({'query-target-filter': self.build_filter(ter_class, ter_filter)}) + # TODO: Filter by sec_filter, parent and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif parent_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}/{2}.json".format(root_rn, ter_rn, sec_rn) + # NOTE: No need to select by sec_filter + # self.update_qs({'query-target-filter': self.build_filter(sec_class, sec_filter)}) + # TODO: Filter by parent_filter and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif mo is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}/{2}/{3}.json".format(root_rn, ter_rn, sec_rn, parent_rn) + # NOTE: No need to select by parent_filter + # self.update_qs({'query-target-filter': self.build_filter(parent_class, parent_filter)}) + else: + # Query for a specific object of the module's class + self.path = "api/mo/uni/{0}/{1}/{2}/{3}/{4}.json".format(root_rn, ter_rn, sec_rn, parent_rn, obj_rn) + + def _construct_url_6(self, root, quad, ter, sec, parent, obj, config_only=True): + """ + This method is used by construct_url when the object is the fourth-level class. + """ + root_rn = root.get("aci_rn") + root_obj = root.get("module_object") + quad_rn = quad.get("aci_rn") + quad_obj = quad.get("module_object") + ter_rn = ter.get("aci_rn") + ter_obj = ter.get("module_object") + sec_rn = sec.get("aci_rn") + sec_obj = sec.get("module_object") + parent_rn = parent.get("aci_rn") + parent_obj = parent.get("module_object") + obj_class = obj.get("aci_class") + obj_rn = obj.get("aci_rn") + obj_filter = obj.get("target_filter") + mo = obj.get("module_object") + + if self.child_classes is None: + self.child_classes = [obj_class] + + if self.module.params.get("state") in ("absent", "present"): + # State is absent or present + self.path = "api/mo/uni/{0}/{1}/{2}/{3}/{4}/{5}.json".format(root_rn, quad_rn, ter_rn, sec_rn, parent_rn, obj_rn) + if config_only: + self.update_qs({"rsp-prop-include": "config-only"}) + self.obj_filter = obj_filter + # TODO: Add all missing cases + elif root_obj is None: + self.child_classes.add(obj_class) + self.path = "api/class/{0}.json".format(obj_class) + self.update_qs({"query-target-filter": self.build_filter(obj_class, obj_filter)}) + elif quad_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}.json".format(root_rn) + # NOTE: No need to select by root_filter + # self.update_qs({'query-target-filter': self.build_filter(root_class, root_filter)}) + # TODO: Filter by quad_filter, parent and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif ter_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}.json".format(root_rn, quad_rn) + # NOTE: No need to select by quad_filter + # self.update_qs({'query-target-filter': self.build_filter(quad_class, quad_filter)}) + # TODO: Filter by ter_filter, parent and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif sec_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}/{2}.json".format(root_rn, quad_rn, ter_rn) + # NOTE: No need to select by ter_filter + # self.update_qs({'query-target-filter': self.build_filter(ter_class, ter_filter)}) + # TODO: Filter by sec_filter, parent and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif parent_obj is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}/{2}/{3}.json".format(root_rn, quad_rn, ter_rn, sec_rn) + # NOTE: No need to select by sec_filter + # self.update_qs({'query-target-filter': self.build_filter(sec_class, sec_filter)}) + # TODO: Filter by parent_filter and obj_filter + self.update_qs({"rsp-subtree-filter": self.build_filter(obj_class, obj_filter)}) + elif mo is None: + self.child_classes.add(obj_class) + self.path = "api/mo/uni/{0}/{1}/{2}/{3}/{4}.json".format(root_rn, quad_rn, ter_rn, sec_rn, parent_rn) + # NOTE: No need to select by parent_filter + # self.update_qs({'query-target-filter': self.build_filter(parent_class, parent_filter)}) + else: + # Query for a specific object of the module's class + self.path = "api/mo/uni/{0}/{1}/{2}/{3}/{4}/{5}.json".format(root_rn, quad_rn, ter_rn, sec_rn, parent_rn, obj_rn) + + def delete_config(self): + """ + This method is used to handle the logic when the modules state is equal to absent. The method only pushes a change if + the object exists, and if check_mode is False. A successful change will mark the module as changed. + """ + self.proposed = dict() + + 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) + else: + self.result["changed"] = True + self.method = "DELETE" + + def get_diff(self, aci_class): + """ + 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. + """ + proposed_config = self.proposed[aci_class]["attributes"] + if self.existing: + existing_config = self.existing[0][aci_class]["attributes"] + config = {} + + # values are strings, so any diff between proposed and existing can be a straight replace + for key, value in proposed_config.items(): + existing_field = existing_config.get(key) + if value != existing_field: + config[key] = value + + # add name back to config only if the configs do not match + if config: + # TODO: If URLs are built with the object's name, then we should be able to leave off adding the name back + config = {aci_class: {"attributes": config}} + + # check for updates to child configs and update new config dictionary + children = self.get_diff_children(aci_class) + + if children and config: + config[aci_class].update({"children": children}) + elif children: + config = {aci_class: {"attributes": {}, "children": children}} + + else: + config = self.proposed + self.config = config + + @staticmethod + def get_diff_child(child_class, proposed_child, existing_child): + """ + 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. + The config portion of the proposed child dictionary. + :param existing_child: Type dict. + The config portion of the existing child dictionary. + :return: The child config with only values that are updated. If the proposed dictionary has no updates to make + to what exists on the APIC, then None is returned. + """ + update_config = {child_class: {"attributes": {}}} + for key, value in proposed_child.items(): + existing_field = existing_child.get(key) + if value != existing_field: + update_config[child_class]["attributes"][key] = value + + if not update_config[child_class]["attributes"]: + return None + + return update_config + + def get_diff_children(self, aci_class, proposed_obj=None, existing_obj=None): + """ + 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 + configurations. + """ + if proposed_obj is None: + proposed_children = self.proposed[aci_class].get("children") + else: + proposed_children = proposed_obj + + if proposed_children: + child_updates = [] + if existing_obj is None: + existing_children = self.existing[0][aci_class].get("children", []) + else: + existing_children = existing_obj + + # Loop through proposed child configs and compare against existing child configuration + for child in proposed_children: + child_class, proposed_child, existing_child = self.get_nested_config(child, existing_children) + ( + proposed_child_children, + existing_child_children, + ) = self.get_nested_children(child, existing_children) + + if existing_child is None: + child_update = child + else: + child_update = self.get_diff_child(child_class, proposed_child, existing_child) + if proposed_child_children: + child_update_children = self.get_diff_children(aci_class, proposed_child_children, existing_child_children) + + if child_update_children: + child_update = child + + # Update list of updated child configs only if the child config is different than what exists + if child_update: + child_updates.append(child_update) + else: + return None + + return child_updates + + def get_existing(self): + """ + This method is used to get the existing object(s) based on the path specified in the module. Each module should + build the URL so that if the object's name is supplied, then it will retrieve the configuration for that particular + object, but if no name is supplied, then it will retrieve all MOs for the class. Following this method will ensure + that this method can be used to supply the existing configuration when using the get_diff method. The response, status, + and existing configuration will be added to the self.result dictionary. + """ + 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) + + @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. + The list of existing child config dictionaries. + :return: The child's class as str (root config dict key), the child's proposed config dict, and the child's + existing configuration dict. + """ + for key in proposed_child.keys(): + child_class = key + proposed_config = proposed_child[key]["attributes"] + existing_config = None + + # FIXME: Design causes issues for repeated child_classes + # get existing dictionary from the list of existing to use for comparison + for child in existing_children: + if child.get(child_class): + existing_config = child[key]["attributes"] + # NOTE: This is an ugly fix + # Return the one that is a subset match + if set(proposed_config.items()).issubset(set(existing_config.items())): + break + existing_config = None + + return child_class, proposed_config, existing_config + + @staticmethod + 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. + The list of existing child config dictionaries. + :return: The child's class as str (root config dict key), the child's proposed children as a list and the child's + existing children as a list. + """ + for key in proposed_child.keys(): + child_class = key + proposed_config = proposed_child[key]["attributes"] + existing_config = None + proposed_children = proposed_child[key].get("children") + existing_child_children = None + + # FIXME: Design causes issues for repeated child_classes + # get existing dictionary from the list of existing to use for comparison + for child in existing_children: + if child.get(child_class): + existing_config = child[key]["attributes"] + existing_child_children = child[key].get("children") + # NOTE: This is an ugly fix + # Return the one that is a subset match + if set(proposed_config.items()).issubset(set(existing_config.items())): + break + existing_child_children = None + existing_config = None + + return proposed_children, existing_child_children + + def payload(self, aci_class, class_config, child_configs=None): + """ + 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 + This is the configuration of the MO using the dictionary keys expected by the API + :param child_configs: Type list + This is a list of child dictionaries associated with the MOs config. The list should only + include child objects that are used to associate two MOs together. Children that represent + MOs should have their own module. + """ + proposed = dict((k, str(v)) for k, v in class_config.items() if v is not None) + if self.params.get("annotation") is not None: + proposed["annotation"] = self.params.get("annotation") + if self.params.get("owner_key") is not None: + proposed["ownerKey"] = self.params.get("owner_key") + if self.params.get("owner_tag") is not None: + proposed["ownerTag"] = self.params.get("owner_tag") + self.proposed = {aci_class: {"attributes": proposed}} + + # add child objects to proposed + if child_configs: + children = [] + for child in child_configs: + child_copy = deepcopy(child) + has_value = False + for root_key in child_copy.keys(): + for final_keys, values in child_copy[root_key]["attributes"].items(): + if values is None: + child[root_key]["attributes"].pop(final_keys) + else: + child[root_key]["attributes"][final_keys] = str(values) + has_value = True + if has_value: + children.append(child) + + if children: + self.proposed[aci_class].update(dict(children=children)) + + def post_config(self): + """ + 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 + module as changed. + """ + if not self.config: + 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) + else: + self.result["changed"] = True + self.method = "POST" + + def exit_json(self, filter_existing=None, **kwargs): + """ + :param filter_existing: tuple consisting of the function at (index 0) and the args at (index 1) + CAUTION: the function should always take in self.existing in its first parameter + :param kwargs: kwargs to be passed to ansible module exit_json() + filter_existing is not passed via kwargs since it cant handle function type and should not be exposed to user + """ + + 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 not filter_existing else filter_existing[0](self.existing, filter_existing[1]) + + # Return the gory details when we need it + if self.params.get("output_level") == "debug": + if "state" in self.params: + self.result["filter_string"] = self.filter_string + self.result["method"] = self.method + # self.result['path'] = self.path # Adding 'path' in result causes state: absent in output + self.result["response"] = self.response + self.result["status"] = self.status + self.result["url"] = self.url + if self.stdout: + self.result["stdout"] = self.stdout + + if "state" in self.params: + self.original = self.existing + if self.params.get("state") in ("absent", "present"): + self.get_existing() + + # if self.module._diff and self.original != self.existing: + # self.result['diff'] = dict( + # before=json.dumps(self.original, sort_keys=True, indent=4), + # after=json.dumps(self.existing, sort_keys=True, indent=4), + # ) + self.result["current"] = self.existing if not filter_existing else filter_existing[0](self.existing, filter_existing[1]) + + if self.params.get("output_level") in ("debug", "info"): + self.result["sent"] = self.config + self.result["proposed"] = self.proposed + + self.dump_json() + self.result.update(**kwargs) + self.module.exit_json(**self.result) + + def fail_json(self, msg, **kwargs): + # Return error information, if we have it + if self.error.get("code") is not None and self.error.get("text") is not None: + self.result["error"] = self.error + + 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": + if self.imdata is not None: + self.result["imdata"] = self.imdata + self.result["totalCount"] = self.totalCount + + if self.params.get("output_level") == "debug": + if self.url is not None: + if "state" in self.params: + self.result["filter_string"] = self.filter_string + self.result["method"] = self.method + # self.result['path'] = self.path # Adding 'path' in result causes state: absent in output + self.result["response"] = self.response + self.result["status"] = self.status + self.result["url"] = self.url + + if "state" in self.params: + if self.params.get("output_level") in ("debug", "info"): + self.result["sent"] = self.config + self.result["proposed"] = self.proposed + + self.result.update(**kwargs) + self.module.fail_json(msg=msg, **self.result) + + def dump_json(self): + if self.params.get("state") in ("absent", "present"): + dn_path = (self.url).split("/mo/")[-1] + if dn_path[-5:] == ".json": + dn_path = dn_path[:-5] + mo = {} + if self.proposed: + mo = self.proposed + for aci_class in mo: + mo[aci_class]["attributes"]["dn"] = dn_path + if self.obj_filter is not None: + if "tDn" in self.obj_filter: + mo[aci_class]["attributes"]["tDn"] = self.obj_filter["tDn"] + + elif self.params.get("state") == "absent" and self.existing: + for aci_class in self.existing[0]: + mo[aci_class] = dict(attributes=dict(dn=dn_path, status="deleted")) + + self.result["mo"] = mo + output_path = self.params.get("output_path") + if output_path is not None: + with open(output_path, "a") as output_file: + if self.result.get("changed") is True: + json.dump([mo], output_file) diff --git a/ansible_collections/cisco/aci/plugins/module_utils/constants.py b/ansible_collections/cisco/aci/plugins/module_utils/constants.py new file mode 100644 index 000000000..72d7585a5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/module_utils/constants.py @@ -0,0 +1,3 @@ +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"} diff --git a/ansible_collections/cisco/aci/plugins/modules/__init__.py b/ansible_collections/cisco/aci/plugins/modules/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/__init__.py diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_aaa_custom_privilege.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_custom_privilege.py new file mode 100644 index 000000000..a7735e24a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_custom_privilege.py @@ -0,0 +1,366 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_custom_privilege +short_description: Manage AAA RBAC Custom Privileges (aaa:RbacClassPriv) +description: +- Manage AAA Custom Privileges with RBAC Rules on Cisco ACI fabrics. +options: + name: + description: + - Name of the object class for which you are configuring access. + type: str + aliases: [ custom_privilege_name ] + description: + description: + - Description of the AAA custom privilege. + type: str + aliases: [ descr ] + write_privilege: + description: + - Name of the custom privilege that will include write access to objects of the class. + type: str + aliases: [ write_priv, w_priv ] + choices: [ + custom-privilege-1, + custom-privilege-2, + custom-privilege-3, + custom-privilege-4, + custom-privilege-5, + custom-privilege-6, + custom-privilege-7, + custom-privilege-8, + custom-privilege-9, + custom-privilege-10, + custom-privilege-11, + custom-privilege-12, + custom-privilege-13, + custom-privilege-14, + custom-privilege-15, + custom-privilege-16, + custom-privilege-17, + custom-privilege-18, + custom-privilege-19, + custom-privilege-20, + custom-privilege-21, + custom-privilege-22 + ] + read_privilege: + description: + - Name of the custom privilege that will include read access to objects of the class. + type: str + aliases: [ read_priv, r_priv ] + choices: [ + custom-privilege-1, + custom-privilege-2, + custom-privilege-3, + custom-privilege-4, + custom-privilege-5, + custom-privilege-6, + custom-privilege-7, + custom-privilege-8, + custom-privilege-9, + custom-privilege-10, + custom-privilege-11, + custom-privilege-12, + custom-privilege-13, + custom-privilege-14, + custom-privilege-15, + custom-privilege-16, + custom-privilege-17, + custom-privilege-18, + custom-privilege-19, + custom-privilege-20, + custom-privilege-21, + custom-privilege-22 + ] + 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 + +seealso: +- module: cisco.aci.aci_aaa_domain +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(aaa:Domain). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Add a custom privilege + cisco.aci.aci_aaa_custom_privilege: + host: apic + username: admin + password: SomeSecretPassword + name: fabricPod + write_privilege: custom-privilege-1 + read_privilege: custom-privilege-1 + state: present + delegate_to: localhost + +- name: Add list of custom privileges + cisco.aci.aci_aaa_custom_privilege: + host: apic + username: admin + password: SomeSecretPassword + name: "{{ item.name }}" + write_privilege: "{{ item.write_privilege }}" + read_privilege: "{{ item.read_privilege | default('') }}" + state: present + with_items: + - name: fvTenant + write_privilege: custom-privilege-2 + read_privilege: custom-privilege-2 + - name: aaaUser + write_privilege: custom-privilege-3 + delegate_to: localhost + +- name: Query a custom privilege with name + cisco.aci.aci_aaa_custom_privilege: + host: apic + username: admin + password: SomeSecretPassword + name: fabricPod + state: query + delegate_to: localhost + +- name: Query all custom privileges + cisco.aci.aci_aaa_custom_privilege: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove a custom privilege + cisco.aci.aci_aaa_custom_privilege: + host: apic + username: admin + password: SomeSecretPassword + name: fabricPod + 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 + +CUSTOM_PRIVILEGES = [ + "custom-privilege-1", + "custom-privilege-2", + "custom-privilege-3", + "custom-privilege-4", + "custom-privilege-5", + "custom-privilege-6", + "custom-privilege-7", + "custom-privilege-8", + "custom-privilege-9", + "custom-privilege-10", + "custom-privilege-11", + "custom-privilege-12", + "custom-privilege-13", + "custom-privilege-14", + "custom-privilege-15", + "custom-privilege-16", + "custom-privilege-17", + "custom-privilege-18", + "custom-privilege-19", + "custom-privilege-20", + "custom-privilege-21", + "custom-privilege-22", +] + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update( + name=dict(type="str", aliases=["custom_privilege_name"]), + description=dict(type="str", aliases=["descr"]), + write_privilege=dict( + type="str", + aliases=["write_priv", "w_priv"], + choices=CUSTOM_PRIVILEGES, + ), + read_privilege=dict( + type="str", + aliases=["read_priv", "r_priv"], + choices=CUSTOM_PRIVILEGES, + ), + 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", ["name"]], + ["state", "present", ["name"]], + ], + ) + + aci = ACIModule(module) + + name = module.params.get("name") + description = module.params.get("description") + w_priv = module.params.get("write_privilege") + r_priv = module.params.get("read_privilege") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict(aci_class="aaaRbacClassPriv", aci_rn="rbacdb/rbacclpriv-{0}".format(name), module_object=name, target_filter=dict(name=name)), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="aaaRbacClassPriv", + class_config=dict( + name=name, + descr=description, + wPriv=w_priv, + rPriv=r_priv, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="aaaRbacClassPriv") + + 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_aaa_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_domain.py new file mode 100644 index 000000000..99104b01c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_domain.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_domain +short_description: Manage AAA domains (aaa:Domain) +description: +- Manage AAA domains on Cisco ACI fabrics. +options: + name: + description: + - The name of the aaa domain. + type: str + aliases: [ aaa_domain ] + description: + description: + - Description of the aaa domain. + type: str + aliases: [ descr ] + restricted_rbac_domain: + description: + - C(True) to enable Restricted RBAC Domain on the aaa security domain. + type: bool + choices: [ false, true ] + 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 + +seealso: +- module: cisco.aci.aci_aaa_role +- name: Manage AAA roles (aaa:Role) + description: More information about the AAA roles class B(aaa:Role). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Add an aaa security domain + cisco.aci.aci_aaa_domain: + host: apic + username: admin + password: SomeSecretPassword + name: anstest_security_domain + description: "Anstest Sec Domain Descr" + state: present + delegate_to: localhost + +- name: Add list of aaa security domain + cisco.aci.aci_aaa_domain: + host: apic + username: admin + password: SomeSecretPassword + name: "{{ item.name }}" + description: "{{ item.description }}" + state: present + with_items: + - name: anstest1 + description: "Anstest Sec Domain Descr 1" + - name: anstest2 + description: "Anstest Sec Domain Descr 2" + delegate_to: localhost + +- name: Query an aaa security domain with name + cisco.aci.aci_aaa_domain: + host: apic + username: admin + password: SomeSecretPassword + name: anstest_security_domain + state: query + delegate_to: localhost + +- name: Query all aaa security domains + cisco.aci.aci_aaa_domain: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove an aaa security domain + cisco.aci.aci_aaa_domain: + host: apic + username: admin + password: SomeSecretPassword + name: anstest_security_domain + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update( + name=dict(type="str", aliases=["aaa_domain"]), + description=dict(type="str", aliases=["descr"]), + restricted_rbac_domain=dict(type="bool", choices=[False, True]), + 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", ["name"]], + ["state", "present", ["name"]], + ], + ) + + name = module.params.get("name") + description = module.params.get("description") + restricted_rbac_domain = module.params.get("restricted_rbac_domain") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="aaaDomain", + aci_rn="userext/domain-{0}".format(name), + module_object=name, + target_filter=dict(name=name), + ), + ) + aci.get_existing() + + if state == "present": + restricted_rbac_domain_mapping = {False: "no", True: "yes"} + restricted_rbac_domain_state = restricted_rbac_domain_mapping.get(restricted_rbac_domain) + aci.payload( + aci_class="aaaDomain", + class_config=dict( + name=name, + descr=description, + restrictedRbacDomain=restricted_rbac_domain_state, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="aaaDomain") + + 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_aaa_role.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_role.py new file mode 100644 index 000000000..147c6512c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_role.py @@ -0,0 +1,380 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_role +short_description: Manage AAA roles (aaa:Role) +description: +- Manage AAA roles on Cisco ACI fabrics. +options: + name: + description: + - The name of the aaa role. + type: str + aliases: [ aaa_role ] + privileges: + description: + - The privilege(s) assigned to a role. + type: list + aliases: [ priv ] + elements: str + choices: [ + admin, + aaa, + tenant-connectivity, + tenant-protocol, + vmm-policy, + tenant-ext-connectivity, + tenant-ext-protocol, + tenant-qos, + tenant-security, + tenant-network-profile, + tenant-epg, + fabric-connectivity, + fabric-protocol, + fabric-equipment, + access-connectivity, + access-protocol, + access-equipment, + access-qos, + nw-svc-params, + ops, + nw-svc-policy, + site-admin, + site-policy, + config-manager, + custom-privilege-1, + custom-privilege-2, + custom-privilege-3, + custom-privilege-4, + custom-privilege-5, + custom-privilege-6, + custom-privilege-7, + custom-privilege-8, + custom-privilege-9, + custom-privilege-10, + custom-privilege-11, + custom-privilege-12, + custom-privilege-13, + custom-privilege-14, + custom-privilege-15, + custom-privilege-16, + custom-privilege-17, + custom-privilege-18, + custom-privilege-19, + custom-privilege-20, + custom-privilege-21, + custom-privilege-22, + custom-port-privilege + ] + description: + description: + - Description of the aaa role. + 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 + +seealso: +- module: cisco.aci.aci_aaa_domain +- name: Manage AAA domains (aaa:Domain) + description: More information about the AAA domains class B(aaa:Domain). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + + +EXAMPLES = r""" +- name: Add a aaa role + cisco.aci.aci_aaa_role: + host: apic + username: admin + password: SomeSecretPassword + name: anstest + privileges: aaa + state: present + delegate_to: localhost + +- name: Add list of aaa roles + cisco.aci.aci_aaa_role: + host: apic + username: admin + password: SomeSecretPassword + name: "{{ item.name }}" + privileges: "{{ item.privilege }}" + state: present + delegate_to: localhost + with_items: + - name: anstest1 + privilege: site-admin + - name: anstest2 + privilege: site-policy + +- name: Query a aaa role with name + cisco.aci.aci_aaa_role: + host: apic + username: admin + password: SomeSecretPassword + name: anstest + state: query + delegate_to: localhost + +- name: Query all aaa roles + cisco.aci.aci_aaa_role: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove a aaa role with name + cisco.aci.aci_aaa_role: + host: apic + username: admin + password: SomeSecretPassword + name: anstest + 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 + +PRIVILEGES = [ + "admin", + "aaa", + "tenant-connectivity", + "tenant-protocol", + "vmm-policy", + "tenant-ext-connectivity", + "tenant-ext-protocol", + "tenant-qos", + "tenant-security", + "tenant-network-profile", + "tenant-epg", + "fabric-connectivity", + "fabric-protocol", + "fabric-equipment", + "access-connectivity", + "access-protocol", + "access-equipment", + "access-qos", + "nw-svc-params", + "ops", + "nw-svc-policy", + "site-admin", + "site-policy", + "config-manager", + "custom-privilege-1", + "custom-privilege-2", + "custom-privilege-3", + "custom-privilege-4", + "custom-privilege-5", + "custom-privilege-6", + "custom-privilege-7", + "custom-privilege-8", + "custom-privilege-9", + "custom-privilege-10", + "custom-privilege-11", + "custom-privilege-12", + "custom-privilege-13", + "custom-privilege-14", + "custom-privilege-15", + "custom-privilege-16", + "custom-privilege-17", + "custom-privilege-18", + "custom-privilege-19", + "custom-privilege-20", + "custom-privilege-21", + "custom-privilege-22", + "custom-port-privilege", +] + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update( + name=dict(type="str", aliases=["aaa_role"]), + privileges=dict(type="list", aliases=["priv"], elements="str", choices=PRIVILEGES), + 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", ["name"]], + ["state", "present", ["name", "privileges"]], + ], + ) + + name = module.params.get("name") + description = module.params.get("description") + privileges = module.params.get("privileges") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="aaaRole", + aci_rn="userext/role-{0}".format(name), + module_object=name, + target_filter=dict(name=name), + ), + ) + aci.get_existing() + + if state == "present": + formatted_privileges = ",".join(privileges) + aci.payload( + aci_class="aaaRole", + class_config=dict( + name=name, + priv=formatted_privileges, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="aaaRole") + + 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_aaa_ssh_auth.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_ssh_auth.py new file mode 100644 index 000000000..df4732f28 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_ssh_auth.py @@ -0,0 +1,269 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_ssh_auth +short_description: Manage AAA SSH auth (aaaSshAuth) objects. +description: +- Manage AAA SSH Auth key configuration on Cisco ACI fabrics. +options: + aaa_user: + description: + - Name of an existing AAA user + type: str + required: true + auth_name: + description: + - Name of the AAA SSH Auth key + type: str + data: + description: + - SSH key data + 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(aaa_user) must exist before using this module in your playbook. + The M(cisco.aci.aci_aaa_user) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(aaaSshAuth). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new SSH key + cisco.aci.aci_aaa_ssh_auth: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + auth_name: my_key + data: "{{ ssh_key_data_var }}" + state: present + delegate_to: localhost + +- name: Remove an SSH key + cisco.aci.aci_aaa_ssh_auth: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + auth_name: my_key + state: absent + delegate_to: localhost + +- name: Query an SSH key + cisco.aci.aci_aaa_ssh_auth: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + auth_name: my_key + state: query + delegate_to: localhost + register: query_result + +- name: Query all SSH auth keys under a user + cisco.aci.aci_aaa_ssh_auth: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + 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 "/></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( + aaa_user=dict(type="str", required=True), + auth_name=dict(type="str"), + data=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", ["auth_name"]], + ["state", "present", ["auth_name", "data"]], + ], + ) + + aaa_user = module.params.get("aaa_user") + auth_name = module.params.get("auth_name") + data = module.params.get("data") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="aaaUser", + aci_rn="userext/user-{0}".format(aaa_user), + module_object=aaa_user, + target_filter={"name": aaa_user}, + ), + subclass_1=dict( + aci_class="aaaSshAuth", + aci_rn="sshauth-{0}".format(auth_name), + module_object=auth_name, + target_filter={"name": auth_name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="aaaSshAuth", + class_config=dict(name=auth_name, data=data), + ) + + aci.get_diff(aci_class="aaaSshAuth") + + 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_aaa_user.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_user.py new file mode 100644 index 000000000..bccb9c483 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_user.py @@ -0,0 +1,371 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (dagwieers) <dag@wieers.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_aaa_user +short_description: Manage AAA users (aaa:User) +description: +- Manage AAA users on Cisco ACI fabrics. +requirements: +- python-dateutil +options: + aaa_password: + description: + - The password of the locally-authenticated user. + type: str + aaa_password_lifetime: + description: + - The lifetime of the locally-authenticated user password. + type: int + aaa_password_update_required: + description: + - Whether this account needs password update. + type: bool + aaa_user: + description: + - The name of the locally-authenticated user user to add. + type: str + aliases: [ name, user ] + clear_password_history: + description: + - Whether to clear the password history of a locally-authenticated user. + type: bool + description: + description: + - Description for the AAA user. + type: str + aliases: [ descr ] + email: + description: + - The email address of the locally-authenticated user. + type: str + enabled: + description: + - The status of the locally-authenticated user account. + type: bool + expiration: + description: + - The expiration date of the locally-authenticated user account. + type: str + expires: + description: + - Whether to enable an expiration date for the locally-authenticated user account. + type: bool + first_name: + description: + - The first name of the locally-authenticated user. + type: str + last_name: + description: + - The last name of the locally-authenticated user. + type: str + phone: + description: + - The phone number of the locally-authenticated user. + 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: +- This module is not idempotent when C(aaa_password) is being used + (even if that password was already set identically). This + appears to be an inconsistency wrt. the idempotent nature + of the APIC REST API. The vendor has been informed. + More information in :ref:`the ACI documentation <aci_guide_known_issues>`. +seealso: +- module: cisco.aci.aci_aaa_user_certificate +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(aaa:User). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add a user + cisco.aci.aci_aaa_user: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: dag + aaa_password: AnotherSecretPassword + expiration: never + expires: false + email: dag@wieers.com + phone: 1-234-555-678 + first_name: Dag + last_name: Wieers + state: present + delegate_to: localhost + +- name: Remove a user + cisco.aci.aci_aaa_user: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: dag + state: absent + delegate_to: localhost + +- name: Query a user + cisco.aci.aci_aaa_user: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: dag + state: query + delegate_to: localhost + register: query_result + +- name: Query all users + cisco.aci.aci_aaa_user: + 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 +""" + +try: + from dateutil.tz import tzutc + import dateutil.parser + + HAS_DATEUTIL = True +except ImportError: + HAS_DATEUTIL = False + +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( + aaa_password=dict(type="str", no_log=True), + aaa_password_lifetime=dict(type="int", no_log=False), + aaa_password_update_required=dict(type="bool", no_log=False), + aaa_user=dict(type="str", aliases=["name"]), # Not required for querying all objects + clear_password_history=dict(type="bool", no_log=False), + description=dict(type="str", aliases=["descr"]), + email=dict(type="str"), + enabled=dict(type="bool"), + expiration=dict(type="str"), + expires=dict(type="bool"), + first_name=dict(type="str"), + last_name=dict(type="str"), + phone=dict(type="str"), + 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", ["aaa_user"]], + ["state", "present", ["aaa_user"]], + ["expires", True, ["expiration"]], + ], + ) + + aci = ACIModule(module) + + if not HAS_DATEUTIL: + module.fail_json(msg="dateutil required for this module") + + aaa_password = module.params.get("aaa_password") + aaa_password_lifetime = module.params.get("aaa_password_lifetime") + aaa_password_update_required = aci.boolean(module.params.get("aaa_password_update_required")) + aaa_user = module.params.get("aaa_user") + clear_password_history = aci.boolean(module.params.get("clear_password_history")) + description = module.params.get("description") + email = module.params.get("email") + enabled = aci.boolean(module.params.get("enabled"), "active", "inactive") + expires = aci.boolean(module.params.get("expires")) + first_name = module.params.get("first_name") + last_name = module.params.get("last_name") + phone = module.params.get("phone") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + expiration = module.params.get("expiration") + if expiration is not None and expiration != "never": + try: + expiration = aci.iso8601_format(dateutil.parser.parse(expiration).replace(tzinfo=tzutc())) + except Exception as e: + module.fail_json(msg="Failed to parse date format '%s', %s" % (module.params.get("expiration"), e)) + + aci.construct_url( + root_class=dict( + aci_class="aaaUser", + aci_rn="userext/user-{0}".format(aaa_user), + module_object=aaa_user, + target_filter={"name": aaa_user}, + ), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="aaaUser", + class_config=dict( + accountStatus=enabled, + clearPwdHistory=clear_password_history, + descr=description, + email=email, + expiration=expiration, + expires=expires, + firstName=first_name, + lastName=last_name, + name=aaa_user, + phone=phone, + pwd=aaa_password, + pwdLifeTime=aaa_password_lifetime, + pwdUpdateRequired=aaa_password_update_required, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="aaaUser") + + 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_aaa_user_certificate.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_user_certificate.py new file mode 100644 index 000000000..4eea3198d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_user_certificate.py @@ -0,0 +1,300 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (dagwieers) <dag@wieers.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_aaa_user_certificate +short_description: Manage AAA user certificates (aaa:UserCert) +description: +- Manage AAA user certificates on Cisco ACI fabrics. +options: + aaa_user: + description: + - The name of the user to add a certificate to. + type: str + required: true + aaa_user_type: + description: + - Whether this is a normal user or an appuser. + type: str + choices: [ appuser, user ] + default: user + certificate: + description: + - The PEM format public key extracted from the X.509 certificate. + type: str + aliases: [ cert_data, certificate_data ] + name: + description: + - The name of the user certificate entry in ACI. + type: str + aliases: [ cert_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(aaa_user) must exist before using this module in your playbook. + The M(cisco.aci.aci_aaa_user) module can be used for this. +seealso: +- module: cisco.aci.aci_aaa_user +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(aaa:UserCert). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add a certificate to user + cisco.aci.aci_aaa_user_certificate: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: admin + name: admin + certificate_data: '{{ lookup("file", "pki/admin.crt") }}' + state: present + delegate_to: localhost + +- name: Remove a certificate of a user + cisco.aci.aci_aaa_user_certificate: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: admin + name: admin + state: absent + delegate_to: localhost + +- name: Query a certificate of a user + cisco.aci.aci_aaa_user_certificate: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: admin + name: admin + state: query + delegate_to: localhost + register: query_result + +- name: Query all certificates of a user + cisco.aci.aci_aaa_user_certificate: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: admin + 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 + +ACI_MAPPING = dict( + appuser=dict( + aci_class="aaaAppUser", + aci_mo="userext/appuser-", + ), + user=dict( + aci_class="aaaUser", + aci_mo="userext/user-", + ), +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + aaa_user=dict(type="str", required=True), + aaa_user_type=dict(type="str", default="user", choices=["appuser", "user"]), + certificate=dict(type="str", aliases=["cert_data", "certificate_data"]), + name=dict(type="str"), # Not required for querying all objects + 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", ["aaa_user", "name"]], + ["state", "present", ["aaa_user", "certificate", "name"]], + ], + ) + + aaa_user = module.params.get("aaa_user") + aaa_user_type = module.params.get("aaa_user_type") + certificate = module.params.get("certificate") + name = module.params.get("name") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=ACI_MAPPING.get(aaa_user_type).get("aci_class"), + aci_rn=ACI_MAPPING.get(aaa_user_type).get("aci_mo") + aaa_user, + module_object=aaa_user, + target_filter={"name": aaa_user}, + ), + subclass_1=dict( + aci_class="aaaUserCert", + aci_rn="usercert-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="aaaUserCert", + class_config=dict( + data=certificate, + name=name, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="aaaUserCert") + + 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_aaa_user_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_user_domain.py new file mode 100644 index 000000000..7f2dd120e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_user_domain.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_user_domain +short_description: Manage AAA user domains (aaa:UserDomain) +description: +- Manage AAA user domain configuration on Cisco ACI fabrics. +options: + aaa_user: + description: + - The name of an existing AAA user + type: str + aliases: [ user_name ] + aaa_user_type: + description: + - Whether this is a normal user or an appuser. + type: str + choices: [ appuser, user ] + default: user + name: + description: + - The name of the user domain + type: str + aliases: [ domain_name, user_domain ] + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + 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(aaa_user) must exist before using this module in your playbook. + The M(cisco.aci.aci_aaa_user) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(aaaUserDomain). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Add a security domain to a aaa_user + cisco.aci.aci_aaa_user_domain: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + name: my_domain + state: present + delegate_to: localhost + +- name: Remove a security domain from a aaa_user + cisco.aci.aci_aaa_user_domain: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + name: my_domain + state: absent + delegate_to: localhost + +- name: Add list of security domains to a aaa_user + cisco.aci.aci_aaa_user_domain: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + name: "{{ item.name }}" + state: present + with_items: + - name: common + - name: all + - name: mgmt + delegate_to: localhost + +- name: Query a security domain from a aaa_user + cisco.aci.aci_aaa_user_domain: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + name: my_domain + state: query + delegate_to: localhost + register: query_result + +- name: Query all security domains of a aaa_user + cisco.aci.aci_aaa_user_domain: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + state: query + delegate_to: localhost + register: query_results + +- name: Query all security domains to user associations + cisco.aci.aci_aaa_user_domain: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_all_domains +""" + +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 + +ACI_MAPPING = dict( + appuser=dict( + aci_class="aaaAppUser", + aci_mo="userext/appuser-", + ), + user=dict( + aci_class="aaaUser", + aci_mo="userext/user-", + ), +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + aaa_user=dict(type="str", aliases=["user_name"]), + name=dict(type="str", aliases=["domain_name", "user_domain"]), + aaa_user_type=dict(type="str", default="user", choices=["appuser", "user"]), + 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", ["aaa_user", "name"]], + ["state", "present", ["aaa_user", "name"]], + ], + ) + + aaa_user = module.params.get("aaa_user") + name = module.params.get("name") + aaa_user_type = module.params.get("aaa_user_type") + name_alias = module.params.get("name_alias") + state = module.params.get("state") + + child_classes = ["aaaUserRole"] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=ACI_MAPPING.get(aaa_user_type).get("aci_class"), + aci_rn="{0}{1}".format(ACI_MAPPING.get(aaa_user_type).get("aci_mo"), aaa_user), + module_object=aaa_user, + target_filter={"name": aaa_user}, + ), + subclass_1=dict( + aci_class="aaaUserDomain", + aci_rn="userdomain-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="aaaUserDomain", + class_config=dict( + name=name, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="aaaUserDomain") + + 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_aaa_user_role.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_user_role.py new file mode 100644 index 000000000..bdd6e275f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_user_role.py @@ -0,0 +1,366 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_user_role +short_description: Manage AAA user roles (aaa:UserRole) +description: +- Manage AAA User Role configuration on Cisco ACI fabrics. +options: + aaa_user: + description: + - The name of the existing user to add roles and privileges + type: str + aliases: [ user_name ] + aaa_user_type: + description: + - Whether this is a normal user or an appuser. + type: str + choices: [ appuser, user ] + default: user + domain_name: + description: + - The name of the security domain + type: str + aliases: [ user_domain ] + name: + description: + - Name of the AAA role + type: str + aliases: [ role_name, user_role ] + privilege_type: + description: + - Privilege for the role + type: str + aliases: [ priv_type ] + choices: [ read, write ] + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + 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(aaa_user) and C(domain_name) must exist before using this module in your playbook. + The M(cisco.aci.aci_aaa_user) and M(cisco.aci.aci_aaa_user_domain) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(aaaUserRole). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Add a user role to a user security domain + cisco.aci.aci_aaa_user_role: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + domain_name: my_domain + name: my_role + privilege_type: read + state: present + delegate_to: localhost + +- name: Add list of user roles to a user domain + cisco.aci.aci_aaa_user_role: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + domain_name: my_domain + name: "{{ item.name }}" + privilege_type: "{{ item.privilege_type }}" + state: present + with_items: + - name: aaa + privilege_type: write + - name: access-admin + privilege_type: write + - name: ops + privilege_type: write + delegate_to: localhost + +- name: Query a user role from a user security domain + cisco.aci.aci_aaa_user_role: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + domain_name: my_domain + name: my_role + state: query + delegate_to: localhost + register: query_result + +- name: Query all user roles from a user security domain + cisco.aci.aci_aaa_user_role: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + domain_name: my_domain + state: query + delegate_to: localhost + register: query_results + +- name: Query all user roles from a user + cisco.aci.aci_aaa_user_role: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + state: query + delegate_to: localhost + register: query_all_roles_of_user + +- name: Query all user roles + cisco.aci.aci_aaa_user_role: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_all_user_roles + +- name: Remove user role from a user domain + cisco.aci.aci_aaa_user_role: + host: apic + username: admin + password: SomeSecretPassword + aaa_user: my_user + domain_name: my_domain + name: my_role + 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 + +ACI_MAPPING = dict( + appuser=dict( + aci_class="aaaAppUser", + aci_mo="userext/appuser-", + ), + user=dict( + aci_class="aaaUser", + aci_mo="userext/user-", + ), +) + +PRIV_TYPE_MAPPING = { + "read": "readPriv", + "write": "writePriv", +} + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + aaa_user=dict(type="str", aliases=["user_name"]), + aaa_user_type=dict(type="str", default="user", choices=["appuser", "user"]), + domain_name=dict(type="str", aliases=["user_domain"]), + name=dict(type="str", aliases=["role_name", "user_role"]), + privilege_type=dict(type="str", aliases=["priv_type"], choices=["read", "write"]), + 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", ["aaa_user", "domain_name", "name"]], + ["state", "present", ["aaa_user", "domain_name", "name"]], + ], + ) + + aaa_user = module.params.get("aaa_user") + aaa_user_type = module.params.get("aaa_user_type") + domain_name = module.params.get("domain_name") + name = module.params.get("name") + privilege_type = module.params.get("privilege_type") + name_alias = module.params.get("name_alias") + state = module.params.get("state") + + if privilege_type is not None: + privilege_type = PRIV_TYPE_MAPPING[privilege_type] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=ACI_MAPPING.get(aaa_user_type).get("aci_class"), + aci_rn="{0}{1}".format(ACI_MAPPING.get(aaa_user_type).get("aci_mo"), aaa_user), + module_object=aaa_user, + target_filter={"name": aaa_user}, + ), + subclass_1=dict( + aci_class="aaaUserDomain", + aci_rn="userdomain-{0}".format(domain_name), + module_object=domain_name, + target_filter={"name": domain_name}, + ), + subclass_2=dict( + aci_class="aaaUserRole", + aci_rn="role-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="aaaUserRole", + class_config=dict( + name=name, + privType=privilege_type, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="aaaUserRole") + + 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_port_block_to_access_port.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_block_to_access_port.py new file mode 100644 index 000000000..646423290 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_block_to_access_port.py @@ -0,0 +1,438 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Simon Metzger <smnmtzgr@gmail.com> +# Copyright: (c) 2020, Shreyas Srish <ssrish@cisco.com> +# Copyright: (c) 2020, Zak Lantz (@manofcolombia) <zakodewald@gmail.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_port_block_to_access_port +short_description: Manage port blocks of Fabric interface policy leaf profile interface selectors (infra:HPortS, infra:PortBlk) +description: +- Manage port blocks of Fabric interface policy leaf profile interface selectors on Cisco ACI fabrics. +options: + interface_profile: + description: + - The name of the Fabric access policy leaf interface profile. + type: str + aliases: [ leaf_interface_profile_name, leaf_interface_profile, interface_profile_name ] + access_port_selector: + description: + - The name of the Fabric access policy leaf interface profile access port selector. + type: str + aliases: [ name, access_port_selector_name ] + port_blk: + description: + - The name of the Fabric access policy leaf interface profile access port block. + type: str + aliases: [ leaf_port_blk_name, leaf_port_blk ] + port_blk_description: + description: + - The description to assign to the C(leaf_port_blk). + type: str + aliases: [ leaf_port_blk_description ] + from_port: + description: + - The beginning (from-range) of the port range block for the leaf access port block. + type: str + aliases: [ from, fromPort, from_port_range ] + to_port: + description: + - The end (to-range) of the port range block for the leaf access port block. + type: str + aliases: [ to, toPort, to_port_range ] + from_card: + description: + - The beginning (from-range) of the card range block for the leaf access port block. + type: str + aliases: [ from_card_range ] + to_card: + description: + - The end (to-range) of the card range block for the leaf access port block. + type: str + aliases: [ to_card_range ] + type: + description: + - The type of access port block to be created under respective access port. + type: str + choices: [ fex, leaf ] + default: leaf + 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 C(interface_profile) and C(access_port_selector) must exist before using this module in your playbook. + The M(cisco.aci.aci_interface_policy_leaf_profile) and M(cisco.aci.aci_access_port_to_interface_policy_leaf_profile) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:HPortS) and B(infra:PortBlk). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Simon Metzger (@smnmtzgr) +""" + +EXAMPLES = r""" +- name: Associate an access port block (single port) to an interface selector + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + port_blk: leafportblkname + from_port: 13 + to_port: 13 + state: present + delegate_to: localhost + +- name: Associate an access port block (port range) to an interface selector + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + port_blk: leafportblkname + from_port: 13 + to_port: 16 + state: present + delegate_to: localhost + +- name: Associate an access port block (single port) to an interface selector of type fex + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + type: fex + interface_profile: leafintprfname_fex + access_port_selector: accessportselectorname_fex + port_blk: leafportblkname_fex + from_port: 13 + to_port: 13 + state: present + delegate_to: localhost + +- name: Associate an access port block (port range) to an interface selector of type fex + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + type: fex + interface_profile: leafintprfname_fex + access_port_selector: accessportselectorname_fex + port_blk: leafportblkname_fex + from_port: 13 + to_port: 16 + state: present + delegate_to: localhost + +- name: Remove an access port block from an interface selector + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + port_blk: leafportblkname + from_port: 13 + to_port: 13 + state: absent + delegate_to: localhost + +- name: Remove an access port block from an interface selector of type fex + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + type: fex + interface_profile: leafintprfname_fex + access_port_selector: accessportselectorname_fex + port_blk: leafportblkname_fex + from_port: 13 + to_port: 13 + state: absent + delegate_to: localhost + +- name: Query Specific access port block under given access port selector + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + port_blk: leafportblkname + state: query + delegate_to: localhost + register: query_result + +- name: Query Specific access port block under given access port selector of type fex + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + type: fex + interface_profile: leafintprfname_fex + access_port_selector: accessportselectorname_fex + port_blk: leafportblkname_fex + state: query + delegate_to: localhost + register: query_result + +- name: Query all access port blocks under given leaf interface profile + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + state: query + delegate_to: localhost + register: query_result + +- name: Query all access port blocks under given leaf interface profile of type fex + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + type: fex + interface_profile: leafintprfname_fex + state: query + delegate_to: localhost + register: query_result + +- name: Query all access port blocks in the fabric + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query all access port blocks in the fabric of type fex + cisco.aci.aci_access_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + type: fex + 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( + interface_profile=dict(type="str", aliases=["leaf_interface_profile_name", "leaf_interface_profile", "interface_profile_name"]), + access_port_selector=dict(type="str", aliases=["name", "access_port_selector_name"]), # Not required for querying all objects + port_blk=dict(type="str", aliases=["leaf_port_blk_name", "leaf_port_blk"]), # Not required for querying all objects + port_blk_description=dict(type="str", aliases=["leaf_port_blk_description"]), + from_port=dict(type="str", aliases=["from", "fromPort", "from_port_range"]), + to_port=dict(type="str", aliases=["to", "toPort", "to_port_range"]), + from_card=dict(type="str", aliases=["from_card_range"]), + to_card=dict(type="str", aliases=["to_card_range"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + type=dict(type="str", default="leaf", choices=["fex", "leaf"]), # This parameter is not required for querying all objects + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["access_port_selector", "port_blk", "interface_profile"]], + ["state", "present", ["access_port_selector", "port_blk", "from_port", "to_port", "interface_profile"]], + ], + ) + + interface_profile = module.params.get("interface_profile") + access_port_selector = module.params.get("access_port_selector") + port_blk = module.params.get("port_blk") + port_blk_description = module.params.get("port_blk_description") + from_port = module.params.get("from_port") + to_port = module.params.get("to_port") + from_card = module.params.get("from_card") + to_card = module.params.get("to_card") + state = module.params.get("state") + type_port = module.params.get("type") + + aci = ACIModule(module) + aci_class = "infraAccPortP" + aci_rn = "accportprof" + if type_port == "fex": + aci_class = "infraFexP" + aci_rn = "fexprof" + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn="infra/" + aci_rn + "-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_1=dict( + aci_class="infraHPortS", + # NOTE: normal rn: hports-{name}-typ-{type}, hence here hardcoded to range for purposes of module + aci_rn="hports-{0}-typ-range".format(access_port_selector), + module_object=access_port_selector, + target_filter={"name": access_port_selector}, + ), + subclass_2=dict( + aci_class="infraPortBlk", + aci_rn="portblk-{0}".format(port_blk), + module_object=port_blk, + target_filter={"name": port_blk}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraPortBlk", + class_config=dict( + descr=port_blk_description, + name=port_blk, + fromPort=from_port, + toPort=to_port, + fromCard=from_card, + toCard=to_card, + # type='range', + ), + ) + + aci.get_diff(aci_class="infraPortBlk") + + 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_port_to_interface_policy_leaf_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py new file mode 100644 index 000000000..0768e4d0c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py @@ -0,0 +1,492 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> +# Copyright: (c) 2020, Shreyas Srish <ssrish@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_port_to_interface_policy_leaf_profile +short_description: Manage Fabric interface policy leaf profile interface selectors (infra:HPortS, infra:RsAccBaseGrp, infra:PortBlk) +description: +- Manage Fabric interface policy leaf profile interface selectors on Cisco ACI fabrics. +options: + interface_profile: + description: + - The name of the Fabric access policy leaf interface profile. + type: str + aliases: [ leaf_interface_profile_name, leaf_interface_profile, interface_profile_name ] + access_port_selector: + description: + - The name of the Fabric access policy leaf interface profile access port selector. + type: str + aliases: [ name, access_port_selector_name ] + description: + description: + - The description to assign to the C(access_port_selector) + type: str + port_blk: + description: + - B(Deprecated) + - Starting with Ansible 2.8 we recommend using M(cisco.aci.aci_access_port_block_to_access_port) instead. + - The parameter will be removed in Ansible 2.12. + - HORIZONTALLINE + - The name of the Fabric access policy leaf interface profile access port block. + type: str + aliases: [ leaf_port_blk_name, leaf_port_blk, port_blk_name ] + leaf_port_blk_description: + description: + - B(Deprecated) + - Starting with Ansible 2.8 we recommend using M(cisco.aci.aci_access_port_block_to_access_port) instead. + - The parameter will be removed in Ansible 2.12. + - HORIZONTALLINE + - The description to assign to the C(leaf_port_blk) + type: str + from_port: + description: + - B(Deprecated) + - Starting with Ansible 2.8 we recommend using M(cisco.aci.aci_access_port_block_to_access_port) instead. + - The parameter will be removed in Ansible 2.12. + - HORIZONTALLINE + - The beginning (from-range) of the port range block for the leaf access port block. + type: str + aliases: [ from, fromPort, from_port_range ] + to_port: + description: + - B(Deprecated) + - Starting with Ansible 2.8 we recommend using M(cisco.aci.aci_access_port_block_to_access_port) instead. + - The parameter will be removed in Ansible 2.12. + - HORIZONTALLINE + - The end (to-range) of the port range block for the leaf access port block. + type: str + aliases: [ to, toPort, to_port_range ] + from_card: + description: + - B(Deprecated) + - Starting with Ansible 2.8 we recommend using M(cisco.aci.aci_access_port_block_to_access_port) instead. + - The parameter will be removed in Ansible 2.12. + - HORIZONTALLINE + - The beginning (from-range) of the card range block for the leaf access port block. + type: str + aliases: [ from_card_range ] + to_card: + description: + - B(Deprecated) + - Starting with Ansible 2.8 we recommend using M(cisco.aci.aci_access_port_block_to_access_port) instead. + - The parameter will be removed in Ansible 2.12. + - HORIZONTALLINE + - The end (to-range) of the card range block for the leaf access port block. + type: str + aliases: [ to_card_range ] + policy_group: + description: + - The name of the fabric access policy group to be associated with the leaf interface profile interface selector. + type: str + aliases: [ policy_group_name ] + interface_type: + description: + - The type of interface for the static EPG deployment. + - The interface_type fex_profile can not be configured with a profile of type fex. + type: str + choices: [ breakout, fex, port_channel, switch_port, vpc, fex_port_channel, fex_vpc , fex_profile] + default: switch_port + fex_id: + description: + - Id of the fex profile, a valid FEX ID is between 101 to 199. + type: int + fex_profile: + description: + - The name of the Fex Profile. Value of the fex_profile is overridden by the policy_group. + type: str + aliases: [ fex_profile_name ] + type: + description: + - The type of access port to be created under respective profile. + type: str + aliases: [ profile_type ] + choices: [ fex, leaf ] + default: leaf + 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(interface_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_interface_policy_leaf_profile) modules can be used for this. +seealso: +- module: cisco.aci.aci_access_port_block_to_access_port +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:HPortS), B(infra:RsAccBaseGrp) and B(infra:PortBlk). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Associate an Interface Access Port Selector to an Interface Policy Leaf Profile with a 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 + port_blk: leafportblkname + from_port: 13 + to_port: 16 + policy_group: policygroupname + 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) + cisco.aci.aci_access_port_to_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + port_blk: leafportblkname + from_port: 13 + to_port: 16 + state: present + delegate_to: localhost + +- name: Remove an interface access port selector associated with an Interface Policy Leaf Profile + cisco.aci.aci_access_port_to_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + state: absent + delegate_to: localhost + +- name: Remove an interface access port selector associated with an Interface Policy Fex Profile + cisco.aci.aci_access_port_to_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: fexintprfname + access_port_selector: accessportselectorname + state: absent + delegate_to: localhost + +- name: Query Specific access_port_selector under given leaf_interface_profile + cisco.aci.aci_access_port_to_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + access_port_selector: accessportselectorname + state: query + delegate_to: localhost + register: query_result + +- name: Query Specific access_port_selector under given Fex leaf_interface_profile + cisco.aci.aci_access_port_to_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: fexintprfname + access_port_selector: accessportselectorname + state: query + delegate_to: localhost + register: query_result + +- name: Create and Bind Access Port Selector with Fex Profile Policy Group + cisco.aci.aci_access_port_to_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + leaf_interface_profile: leafintprftest + fex_profile: fexintprftest + policy_group: fexintprftest + access_port_selector_name: anstest_fex_accessportselector + interface_type: fex_profile + from_port: 13 + to_port: 13 + port_blk: block2 + fex_id: 105 + state: present + 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 + +port_channels_dn = "uni/infra/funcprof/accbundle-{0}" + +INTERFACE_TYPE_MAPPING = dict( + breakout="uni/infra/funcprof/brkoutportgrp-{0}", + fex="uni/infra/funcprof/accportgrp-{0}", + fex_profile="uni/infra/fexprof-{0}/fexbundle-{1}", + port_channel=port_channels_dn, + switch_port="uni/infra/funcprof/accportgrp-{0}", + vpc=port_channels_dn, + fex_port_channel=port_channels_dn, + fex_vpc=port_channels_dn, +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + interface_profile=dict(type="str", aliases=["leaf_interface_profile_name", "leaf_interface_profile", "interface_profile_name"]), + access_port_selector=dict(type="str", aliases=["name", "access_port_selector_name"]), # Not required for querying all objects + description=dict(type="str"), + port_blk=dict(type="str", aliases=["leaf_port_blk_name", "leaf_port_blk", "port_blk_name"]), + leaf_port_blk_description=dict(type="str"), + from_port=dict(type="str", aliases=["from", "fromPort", "from_port_range"]), + to_port=dict(type="str", aliases=["to", "toPort", "to_port_range"]), + from_card=dict(type="str", aliases=["from_card_range"]), + to_card=dict(type="str", aliases=["to_card_range"]), + policy_group=dict(type="str", aliases=["policy_group_name"]), + interface_type=dict( + type="str", default="switch_port", choices=["breakout", "fex", "port_channel", "switch_port", "vpc", "fex_port_channel", "fex_vpc", "fex_profile"] + ), + fex_id=dict(type="int"), + fex_profile=dict(type="str", aliases=["fex_profile_name"]), + type=dict(type="str", default="leaf", choices=["fex", "leaf"], aliases=["profile_type"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["interface_profile", "access_port_selector"]], + ["state", "present", ["interface_profile", "access_port_selector"]], + ], + ) + + interface_profile = module.params.get("interface_profile") + access_port_selector = module.params.get("access_port_selector") + description = module.params.get("description") + port_blk = module.params.get("port_blk") + leaf_port_blk_description = module.params.get("leaf_port_blk_description") + from_port = module.params.get("from_port") + to_port = module.params.get("to_port") + from_card = module.params.get("from_card") + to_card = module.params.get("to_card") + policy_group = module.params.get("policy_group") + interface_type = module.params.get("interface_type") + fex_id = module.params.get("fex_id") + fex_profile = module.params.get("fex_profile") + state = module.params.get("state") + type_profile = module.params.get("type") + + # Build child_configs dynamically + child_configs = [ + dict( + infraPortBlk=dict( + attributes=dict( + descr=leaf_port_blk_description, + name=port_blk, + fromPort=from_port, + toPort=to_port, + fromCard=from_card, + toCard=to_card, + ), + ), + ) + ] + + # Add infraRsAccBaseGrp only when policy_group was defined + if policy_group is not None: + infra_rs_acc_base_grp = dict( + infraRsAccBaseGrp=dict( + attributes=dict(), + ), + ) + + if interface_type == "fex_profile": + if type_profile == "fex": + module.fail_json(msg="Invalid Configuration - interface_type fex_profile can not be configured with a profile of type fex") + elif fex_profile is not None: + infra_rs_acc_base_grp["infraRsAccBaseGrp"]["attributes"]["tDn"] = INTERFACE_TYPE_MAPPING[interface_type].format(fex_profile, policy_group) + elif fex_profile is None: + infra_rs_acc_base_grp["infraRsAccBaseGrp"]["attributes"]["tDn"] = INTERFACE_TYPE_MAPPING[interface_type].format(policy_group, policy_group) + + if fex_id is not None: + if fex_id in range(101, 200): + infra_rs_acc_base_grp["infraRsAccBaseGrp"]["attributes"]["fexId"] = fex_id + else: + module.fail_json(msg="A valid FEX ID is between 101 to 199") + else: + module.fail_json(msg="The fex_id must not be None, when interface_type is fex_profile") + + else: + infra_rs_acc_base_grp["infraRsAccBaseGrp"]["attributes"]["tDn"] = INTERFACE_TYPE_MAPPING[interface_type].format(policy_group) + + child_configs.append(infra_rs_acc_base_grp) + + aci = ACIModule(module) + aci_class = "infraAccPortP" + aci_rn = "accportprof" + if type_profile == "fex": + aci_class = "infraFexP" + aci_rn = "fexprof" + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn="infra/" + aci_rn + "-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_1=dict( + aci_class="infraHPortS", + # NOTE: normal rn: hports-{name}-typ-{type}, hence here hardcoded to range for purposes of module + aci_rn="hports-{0}-typ-range".format(access_port_selector), + module_object=access_port_selector, + target_filter={"name": access_port_selector}, + ), + child_classes=["infraPortBlk", "infraRsAccBaseGrp"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraHPortS", + class_config=dict( + descr=description, + name=access_port_selector, + # type='range', + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="infraHPortS") + + 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_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py new file mode 100644 index 000000000..2da778c75 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_dst_group.py @@ -0,0 +1,485 @@ +#!/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_dst_group +short_description: Manage Access SPAN destination groups (span:DestGrp) +description: +- Manage Access SPAN destination groups on Cisco ACI fabrics. +options: + destination_group: + description: + - The name of the Access SPAN destination group. + type: str + aliases: [ name, dst_group ] + description: + description: + - The description of the Access SPAN destination group. + type: str + aliases: [ descr ] + access_interface: + description: + - The destination access interface. + - The I(access_interface) and I(destination_epg) cannot be configured simultaneously. + type: dict + suboptions: + pod: + description: + - The pod id part of the destination path. + type: int + required: true + aliases: [ pod_id, pod_number ] + node: + description: + - The node id part of the destination path. + type: int + required: true + aliases: [ node_id ] + path: + description: + - The interface part of the destination 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 policy group like C(test_PolGrp) must be provided. + type: str + required: true + mtu: + description: + - The MTU truncation size for the packets. + - The APIC defaults to C(1518) when unset during creation. + type: int + destination_epg: + description: + - The destination end point group. + - The I(access_interface) and I(destination_epg) cannot be configured simultaneously. + 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 + epg: + description: + - The name of the end point group. + type: str + required: true + 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 Access SPAN destination group of type EPG + cisco.aci.aci_access_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: Add a Access SPAN destination group of type access interface port + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + description: Test span + access_interface: + pod: 1 + node: 101 + path: 1/1 + mtu: 1500 + state: present + delegate_to: localhost + +- name: Add a Access SPAN destination group of type access interface direct_port_channel + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + description: Test span + access_interface: + pod: 1 + node: 101 + path: Switch101_1-ports-1-2_PolGrp + mtu: 1500 + state: present + delegate_to: localhost + +- name: Remove a Access SPAN destination group + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + state: absent + delegate_to: localhost + +- name: Query a Access SPAN destination group + cisco.aci.aci_access_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + destination_group: group1 + state: query + delegate_to: localhost + +- name: Query all Access SPAN destination groups + cisco.aci.aci_access_span_dst_group: + 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 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", + ], + ), + ) + + +def access_interface_spec(): + return dict( + pod=dict(type="int", required=True, aliases=["pod_id", "pod_number"]), + node=dict(type="int", required=True, aliases=["node_id"]), + path=dict(type="str", required=True), + mtu=dict(type="int"), + ) + + +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"]), + access_interface=dict(type="dict", options=access_interface_spec()), + 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"]], + ["state", "present", ["access_interface", "destination_epg"], True], + ], + mutually_exclusive=[ + ("access_interface", "destination_epg"), + ], + ) + + aci = ACIModule(module) + + destination_group = module.params.get("destination_group") + description = module.params.get("description") + access_interface = module.params.get("access_interface") + 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="infra", + aci_rn="infra", + ), + 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": + if destination_epg: + 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)) + + else: + attributes = dict( + tDn="topology/pod-{0}/paths-{1}/pathep-[{2}]".format(access_interface.get("pod"), access_interface.get("node"), access_interface.get("path")) + ) + if access_interface.get("mtu") is not None: + attributes["mtu"] = str(access_interface.get("mtu")) + span_rs_dest = dict(spanRsDestPathEp=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_access_span_filter_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py new file mode 100644 index 000000000..c715027f6 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group.py @@ -0,0 +1,248 @@ +#!/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_filter_group +short_description: Manage Access SPAN filter groups (span:FilterGrp) +description: +- Manage Access SPAN filter groups on Cisco ACI fabrics. +options: + filter_group: + description: + - The name of the Access SPAN filter group. + type: str + aliases: [ 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 + +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 Access SPAN filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: present + delegate_to: localhost + +- name: Remove a Access SPAN filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: absent + delegate_to: localhost + +- name: Query a Access SPAN filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: query + delegate_to: localhost + +- name: Query all Access SPAN filter groups + cisco.aci.aci_access_span_filter_group: + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + filter_group=dict(type="str", aliases=["name"]), # Not required for querying all objects + 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", ["filter_group"]], + ["state", "present", ["filter_group"]], + ], + ) + + aci = ACIModule(module) + + filter_group = module.params.get("filter_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="spanFilterGrp", + aci_rn="filtergrp-{0}".format(filter_group), + module_object=filter_group, + target_filter={"name": filter_group}, + ), + child_classes=["spanFilterEntry"], + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="spanFilterGrp", class_config=dict(name=filter_group, nameAlias=name_alias)) + + aci.get_diff(aci_class="spanFilterGrp") + + 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_filter_group_entry.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py new file mode 100644 index 000000000..5d5516751 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_span_filter_group_entry.py @@ -0,0 +1,346 @@ +#!/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_filter_group_entry +short_description: Manage Access SPAN filter group entries (span:FilterEntry) +description: +- Manage Access SPAN filter group entries on Cisco ACI fabrics. +options: + filter_group: + description: + - The name of the Access SPAN filter group. + type: str + source_ip: + description: + - The source IP Prefix. + type: str + destination_ip: + description: + - The destination IP Prefix. + type: str + first_src_port: + description: + - The first source port (from port). + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + last_src_port: + description: + - The last source port (to port). + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + first_dest_port: + description: + - The first destination port (from port). + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + last_dest_port: + description: + - The last destination port (to port). + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + ip_protocol: + description: + - The IP Protocol. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ eigrp, egp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp, 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 + +notes: +- The C(filter_group) used must exist before using this module in your playbook. + The M(cisco.aci.aci_access_span_filter_group) module can be used for this. +seealso: +- module: cisco.aci.aci_access_span_filter_group +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:FilterEntry). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a Access SPAN filter entry + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: present + delegate_to: localhost + +- name: Remove a Access SPAN filter entry + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: absent + delegate_to: localhost + +- name: Query a Access SPAN filter group + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: query + delegate_to: localhost + +- name: Query all Access SPAN filter groups + cisco.aci.aci_access_span_filter_group_entry: + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import VALID_IP_PROTOCOLS, FILTER_PORT_MAPPING + + +def get_port_value(port): + return FILTER_PORT_MAPPING.get(port, port) if port else "unspecified" + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + filter_group=dict(type="str"), # Not required for querying all objects + source_ip=dict(type="str"), # Not required for querying all objects + destination_ip=dict(type="str"), # Not required for querying all objects + first_src_port=dict(type="str"), + last_src_port=dict(type="str"), + first_dest_port=dict(type="str"), + last_dest_port=dict(type="str"), + ip_protocol=dict(type="str", choices=VALID_IP_PROTOCOLS), + 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", ["filter_group", "source_ip", "destination_ip"]], + ["state", "present", ["filter_group", "source_ip", "destination_ip"]], + ], + ) + + aci = ACIModule(module) + + filter_group = module.params.get("filter_group") + source_ip = module.params.get("source_ip") + destination_ip = module.params.get("destination_ip") + first_src_port = module.params.get("first_src_port") + last_src_port = module.params.get("last_src_port") + first_dest_port = module.params.get("first_dest_port") + last_dest_port = module.params.get("last_dest_port") + ip_protocol = module.params.get("ip_protocol") + 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="spanFilterGrp", + aci_rn="filtergrp-{0}".format(filter_group), + module_object=filter_group, + target_filter={"name": filter_group}, + ), + subclass_2=dict( + aci_class="spanFilterEntry", + aci_rn="proto-{0}-src-[{1}]-dst-[{2}]-srcPortFrom-{3}-srcPortTo-{4}-dstPortFrom-{5}-dstPortTo-{6}".format( + ip_protocol if module.params.get("ip_protocol") else "unspecified", + source_ip, + destination_ip, + get_port_value(first_src_port), + get_port_value(last_src_port), + get_port_value(first_dest_port), + get_port_value(last_dest_port), + ), + target_filter={ + "dstAddr": destination_ip, + "dstPortFrom": first_dest_port, + "dstPortTo": last_dest_port, + "ipProto": ip_protocol, + "srcAddr": source_ip, + "srcPortFrom": first_src_port, + "srcPortTo": last_src_port, + }, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="spanFilterEntry", + class_config=dict( + dstAddr=destination_ip, + dstPortFrom=first_dest_port, + dstPortTo=last_dest_port, + ipProto=ip_protocol, + nameAlias=name_alias, + srcAddr=source_ip, + srcPortFrom=first_src_port, + srcPortTo=last_src_port, + ), + ) + + aci.get_diff(aci_class="spanFilterEntry") + + 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_sub_port_block_to_access_port.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_sub_port_block_to_access_port.py new file mode 100644 index 000000000..8073c6ca2 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_sub_port_block_to_access_port.py @@ -0,0 +1,365 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Simon Metzger <smnmtzgr@gmail.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_sub_port_block_to_access_port +short_description: Manage sub port blocks of Fabric interface policy leaf profile interface selectors (infra:HPortS, infra:SubPortBlk) +description: +- Manage sub port blocks of Fabric interface policy leaf profile interface selectors on Cisco ACI fabrics. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:HPortS) and B(infra:SubPortBlk). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Simon Metzger (@smnmtzgr) +options: + leaf_interface_profile: + description: + - The name of the Fabric access policy leaf interface profile. + type: str + aliases: [ leaf_interface_profile_name ] + access_port_selector: + description: + - The name of the Fabric access policy leaf interface profile access port selector. + type: str + aliases: [ name, access_port_selector_name ] + leaf_port_blk: + description: + - The name of the Fabric access policy leaf interface profile access port block. + type: str + aliases: [ leaf_port_blk_name ] + leaf_port_blk_description: + description: + - The description to assign to the C(leaf_port_blk). + type: str + from_port: + description: + - The beginning (from-range) of the port range block for the leaf access port block. + type: str + aliases: [ from, fromPort, from_port_range ] + to_port: + description: + - The end (to-range) of the port range block for the leaf access port block. + type: str + aliases: [ to, toPort, to_port_range ] + from_sub_port: + description: + - The beginning (from-range) of the sub port range block for the leaf access port block. + type: str + aliases: [ fromSubPort, from_sub_port_range ] + to_sub_port: + description: + - The end (to-range) of the sub port range block for the leaf access port block. + type: str + aliases: [ toSubPort, to_sub_port_range ] + from_card: + description: + - The beginning (from-range) of the card range block for the leaf access port block. + type: str + aliases: [ from_card_range ] + to_card: + description: + - The end (to-range) of the card range block for the leaf access port block. + type: str + aliases: [ to_card_range ] + 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 + +""" + +EXAMPLES = r""" +- name: Associate an access sub port block (single port) to an interface selector + cisco.aci.aci_access_sub_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + leaf_interface_profile: leafintprfname + access_port_selector: accessportselectorname + leaf_port_blk: leafportblkname + from_port: 13 + to_port: 13 + from_sub_port: 1 + to_sub_port: 1 + state: present + delegate_to: localhost + +- name: Associate an access sub port block (port range) to an interface selector + cisco.aci.aci_access_sub_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + leaf_interface_profile: leafintprfname + access_port_selector: accessportselectorname + leaf_port_blk: leafportblkname + from_port: 13 + to_port: 13 + from_sub_port: 1 + to_sub_port: 3 + state: present + delegate_to: localhost + +- name: Remove an access sub port block from an interface selector + cisco.aci.aci_access_sub_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + leaf_interface_profile: leafintprfname + access_port_selector: accessportselectorname + leaf_port_blk: leafportblkname + from_port: 13 + to_port: 13 + from_sub_port: 1 + to_sub_port: 1 + state: absent + delegate_to: localhost + +- name: Query Specific access sub port block under given access port selector + cisco.aci.aci_access_sub_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + leaf_interface_profile: leafintprfname + access_port_selector: accessportselectorname + leaf_port_blk: leafportblkname + state: query + delegate_to: localhost + register: query_result + +- name: Query all access sub port blocks under given leaf interface profile + cisco.aci.aci_access_sub_port_block_to_access_port: + host: apic + username: admin + password: SomeSecretPassword + leaf_interface_profile: leafintprfname + state: query + delegate_to: localhost + register: query_result + +- name: Query all access sub port blocks in the fabric + cisco.aci.aci_access_sub_port_block_to_access_port: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + leaf_interface_profile=dict(type="str", aliases=["leaf_interface_profile_name"]), # Not required for querying all objects + access_port_selector=dict(type="str", aliases=["name", "access_port_selector_name"]), # Not required for querying all objects + leaf_port_blk=dict(type="str", aliases=["leaf_port_blk_name"]), # Not required for querying all objects + leaf_port_blk_description=dict(type="str"), + from_port=dict(type="str", aliases=["from", "fromPort", "from_port_range"]), # Not required for querying all objects and deleting sub port blocks + to_port=dict(type="str", aliases=["to", "toPort", "to_port_range"]), # Not required for querying all objects and deleting sub port blocks + from_sub_port=dict(type="str", aliases=["fromSubPort", "from_sub_port_range"]), # Not required for querying all objects and deleting sub port blocks + to_sub_port=dict(type="str", aliases=["toSubPort", "to_sub_port_range"]), # Not required for querying all objects and deleting sub port blocks + from_card=dict(type="str", aliases=["from_card_range"]), + to_card=dict(type="str", aliases=["to_card_range"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["access_port_selector", "leaf_port_blk", "leaf_interface_profile"]], + ["state", "present", ["access_port_selector", "leaf_port_blk", "from_port", "to_port", "from_sub_port", "to_sub_port", "leaf_interface_profile"]], + ], + ) + + leaf_interface_profile = module.params.get("leaf_interface_profile") + access_port_selector = module.params.get("access_port_selector") + leaf_port_blk = module.params.get("leaf_port_blk") + leaf_port_blk_description = module.params.get("leaf_port_blk_description") + from_port = module.params.get("from_port") + to_port = module.params.get("to_port") + from_sub_port = module.params.get("from_sub_port") + to_sub_port = module.params.get("to_sub_port") + from_card = module.params.get("from_card") + to_card = module.params.get("to_card") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="infraAccPortP", + aci_rn="infra/accportprof-{0}".format(leaf_interface_profile), + module_object=leaf_interface_profile, + target_filter={"name": leaf_interface_profile}, + ), + subclass_1=dict( + aci_class="infraHPortS", + # NOTE: normal rn: hports-{name}-typ-{type}, hence here hardcoded to range for purposes of module + aci_rn="hports-{0}-typ-range".format(access_port_selector), + module_object=access_port_selector, + target_filter={"name": access_port_selector}, + ), + subclass_2=dict( + aci_class="infraSubPortBlk", + aci_rn="subportblk-{0}".format(leaf_port_blk), + module_object=leaf_port_blk, + target_filter={"name": leaf_port_blk}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraSubPortBlk", + class_config=dict( + descr=leaf_port_blk_description, + name=leaf_port_blk, + fromPort=from_port, + toPort=to_port, + fromSubPort=from_sub_port, + toSubPort=to_sub_port, + fromCard=from_card, + toCard=to_card, + # type='range', + ), + ) + + aci.get_diff(aci_class="infraSubPortBlk") + + 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.py b/ansible_collections/cisco/aci/plugins/modules/aci_aep.py new file mode 100644 index 000000000..aa77d8f4f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aep.py @@ -0,0 +1,288 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Shreyas Srish <ssrish@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_aep +short_description: Manage attachable Access Entity Profile (AEP) objects (infra:AttEntityP, infra:ProvAcc) +description: +- Connect to external virtual and physical domains by using + attachable Access Entity Profiles (AEP) on Cisco ACI fabrics. +options: + aep: + description: + - The name of the Attachable Access Entity Profile. + type: str + aliases: [ aep_name, name ] + description: + description: + - Description for the AEP. + type: str + aliases: [ descr ] + infra_vlan: + description: + - Enable infrastructure VLAN. + - The hypervisor functions of the AEP. + - C(false) will disable the infrastructure vlan if it is enabled. + type: bool + aliases: [ infrastructure_vlan ] + state: + description: + - 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. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- module: cisco.aci.aci_aep_to_domain +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:AttEntityP) and B(infra:ProvAcc). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Swetha Chunduri (@schunduri) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new AEP + cisco.aci.aci_aep: + host: apic + username: admin + password: SomeSecretPassword + aep: ACI-AEP + description: default + infra_vlan: true + state: present + delegate_to: localhost + +- name: Remove an existing AEP + cisco.aci.aci_aep: + host: apic + username: admin + password: SomeSecretPassword + aep: ACI-AEP + state: absent + delegate_to: localhost + +- name: Query all AEPs + cisco.aci.aci_aep: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific AEP + cisco.aci.aci_aep: + host: apic + username: admin + password: SomeSecretPassword + aep: ACI-AEP + 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( + aep=dict(type="str", aliases=["name", "aep_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + infra_vlan=dict(type="bool", aliases=["infrastructure_vlan"]), + 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", ["aep"]], + ["state", "present", ["aep"]], + ], + ) + + aep = module.params.get("aep") + description = module.params.get("description") + infra_vlan = module.params.get("infra_vlan") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + if infra_vlan: + child_configs = [dict(infraProvAcc=dict(attributes=dict(name="provacc")))] + elif infra_vlan is False: + child_configs = [dict(infraProvAcc=dict(attributes=dict(name="provacc", status="deleted")))] + else: + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="infraAttEntityP", + aci_rn="infra/attentp-{0}".format(aep), + module_object=aep, + target_filter={"name": aep}, + ), + child_classes=["infraProvAcc"], + ) + + aci.get_existing() + + try: + if len(aci.existing[0]["infraAttEntityP"]) == 1 and infra_vlan is False: + child_configs = [] + except Exception: + pass + + if state == "present": + aci.payload( + aci_class="infraAttEntityP", + class_config=dict( + name=aep, + descr=description, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="infraAttEntityP") + + 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 new file mode 100644 index 000000000..d3a8e9e1b --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_domain.py @@ -0,0 +1,314 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Dag Wieers <dag@wieers.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_aep_to_domain +short_description: Bind AEPs to Physical or Virtual Domains (infra:RsDomP) +description: +- Bind AEPs to Physical or Virtual Domains on Cisco ACI fabrics. +options: + aep: + description: + - The name of the Attachable Access Entity Profile. + type: str + aliases: [ aep_name ] + domain: + description: + - Name of the physical or virtual domain being associated with the AEP. + type: str + aliases: [ domain_name, domain_profile ] + domain_type: + description: + - Determines if the Domain is physical (phys) or virtual (vmm). + type: str + choices: [ fc, l2dom, l3dom, phys, vmm ] + aliases: [ type ] + 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 + vm_provider: + description: + - The VM platform for VMM Domains. + - Support for Kubernetes was added in ACI v3.0. + - Support for CloudFoundry, OpenShift and Red Hat was added in ACI v3.1. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(aep) and C(domain) parameters should exist before using this module. + The M(cisco.aci.aci_aep) and M(cisco.aci.aci_domain) can be used for these. +seealso: +- module: cisco.aci.aci_aep +- module: cisco.aci.aci_domain +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:RsDomP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add AEP to domain binding + cisco.aci.aci_aep_to_domain: &binding_present + host: apic + username: admin + password: SomeSecretPassword + aep: test_aep + domain: phys_dom + domain_type: phys + state: present + delegate_to: localhost + +- name: Remove AEP to domain binding + cisco.aci.aci_aep_to_domain: &binding_absent + host: apic + username: admin + password: SomeSecretPassword + aep: test_aep + domain: phys_dom + domain_type: phys + state: absent + delegate_to: localhost + +- name: Query our AEP to domain binding + cisco.aci.aci_aep_to_domain: + host: apic + username: admin + password: SomeSecretPassword + aep: test_aep + domain: phys_dom + domain_type: phys + state: query + delegate_to: localhost + register: query_result + +- name: Query all AEP to domain bindings + cisco.aci.aci_aep_to_domain: &binding_query + 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 + +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + aep=dict(type="str", aliases=["aep_name"]), # Not required for querying all objects + 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"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["domain_type", "vmm", ["vm_provider"]], + ["state", "absent", ["aep", "domain", "domain_type"]], + ["state", "present", ["aep", "domain", "domain_type"]], + ], + required_together=[ + ["domain", "domain_type"], + ], + ) + + aep = module.params.get("aep") + domain = module.params.get("domain") + domain_type = module.params.get("domain_type") + vm_provider = module.params.get("vm_provider") + state = module.params.get("state") + + # Report when vm_provider is set when type is not virtual + if domain_type != "vmm" and vm_provider is not None: + module.fail_json(msg="Domain type '{0}' cannot have a 'vm_provider'".format(domain_type)) + + # Compile the full domain for URL building + if domain_type == "fc": + domain_mo = "uni/fc-{0}".format(domain) + elif domain_type == "l2dom": + domain_mo = "uni/l2dom-{0}".format(domain) + elif domain_type == "l3dom": + domain_mo = "uni/l3dom-{0}".format(domain) + elif domain_type == "phys": + domain_mo = "uni/phys-{0}".format(domain) + elif domain_type == "vmm": + domain_mo = "uni/vmmp-{0}/dom-{1}".format(VM_PROVIDER_MAPPING[vm_provider], domain) + else: + domain_mo = None + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="infraAttEntityP", + aci_rn="infra/attentp-{0}".format(aep), + module_object=aep, + target_filter={"name": aep}, + ), + subclass_1=dict( + aci_class="infraRsDomP", + aci_rn="rsdomP-[{0}]".format(domain_mo), + module_object=domain_mo, + target_filter={"tDn": domain_mo}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraRsDomP", + class_config=dict(tDn=domain_mo), + ) + + aci.get_diff(aci_class="infraRsDomP") + + 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_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_epg.py new file mode 100644 index 000000000..c77417073 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_epg.py @@ -0,0 +1,333 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_aep_to_epg +short_description: Bind EPG to AEP (infra:RsFuncToEpg). +description: +- Bind EPG to AEP. +options: + aep: + description: + - The name of the Attachable Access Entity Profile. + type: str + aliases: [ aep_name ] + tenant: + description: + - The name of an existing 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: + - The name of the end point group. + type: str + aliases: [ epg_name ] + encap: + description: + - The VLAN associated with this application EPG. + type: int + aliases: [ vlan, vlan_id, encap_id ] + primary_encap: + description: + - The primary VLAN associated with this EPG + type: int + aliases: [ primary_vlan, primary_vlan_id, primary_encap_id ] + interface_mode: + description: + - Determines how layer 2 tags will be read from and added to frames. + - Values C(802.1p) and C(native) are identical. + - Values C(access) and C(untagged) are identical. + - Values C(regular), C(tagged) and C(trunk) are identical. + - The APIC defaults to C(trunk) when unset during creation. + type: str + choices: [ 802.1p, access, native, regular, tagged, trunk, untagged ] + aliases: [ mode, mode_name, interface_mode_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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +author: +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Associate EPG with AEP + cisco.aci.aci_aep_to_epg: + host: apic + username: admin + password: SomeSecretPassword + aep: aep1 + tenant: tenant1 + ap: ap1 + epg: epg1 + encap_id: 222 + interface_mode: access + state: present + delegate_to: localhost + +- name: Associate EPG with AEP + cisco.aci.aci_aep_to_epg: + host: apic + username: admin + password: SomeSecretPassword + aep: aep1 + tenant: tenant1 + ap: ap1 + epg: epg1 + encap_id: 222 + interface_mode: access + state: absent + delegate_to: localhost + +- name: Get specific EPG with AEP association + cisco.aci.aci_aep_to_epg: + host: apic + username: admin + password: SomeSecretPassword + aep: aep1 + tenant: tenant1 + ap: ap1 + epg: epg1 + encap_id: 222 + interface_mode: access + state: query + delegate_to: localhost + register: query_result + +- name: Get all EPG with AEP association + cisco.aci.aci_aep_to_epg: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +INTERFACE_MODE_MAPPING = { + "802.1p": "native", + "access": "untagged", + "native": "native", + "regular": "regular", + "tagged": "regular", + "trunk": "regular", + "untagged": "untagged", +} + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + aep=dict(type="str", aliases=["aep_name"]), + tenant=dict(type="str", aliases=["tenant_name"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), + epg=dict(type="str", aliases=["epg_name"]), + encap=dict(type="int", aliases=["vlan", "vlan_id", "encap_id"]), + primary_encap=dict(type="int", aliases=["primary_vlan", "primary_vlan_id", "primary_encap_id"]), + interface_mode=dict( + type="str", choices=["802.1p", "access", "native", "regular", "tagged", "trunk", "untagged"], aliases=["mode_name", "mode", "interface_mode_name"] + ), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["aep", "epg", "ap", "tenant"]], + ["state", "present", ["interface_mode", "encap", "aep", "epg", "ap", "tenant"]], + ], + ) + + aep = module.params.get("aep") + tenant = module.params.get("tenant") + ap = module.params.get("ap") + epg = module.params.get("epg") + encap = module.params.get("encap") + primary_encap = module.params.get("primary_encap") + interface_mode = module.params.get("interface_mode") + state = module.params.get("state") + + if interface_mode is not None: + interface_mode = INTERFACE_MODE_MAPPING[interface_mode] + + if encap is not None: + encap = "vlan-{0}".format(encap) + + if primary_encap is not None: + primary_encap = "vlan-{0}".format(primary_encap) + + epg_mo = None + if tenant is not None and ap is not None and epg is not None: + epg_mo = "uni/tn-{0}/ap-{1}/epg-{2}".format(tenant, ap, epg) + + aci = ACIModule(module) + aci.construct_url( + root_class=dict(aci_class="infraAttEntityP", aci_rn="infra/attentp-{0}".format(aep), module_object=aep, target_filter={"name": aep}), + subclass_1=dict(aci_class="infraGeneric", aci_rn="gen-default", module_object="default", target_filter={"name": "default"}), + subclass_2=dict(aci_class="infraRsFuncToEpg", aci_rn="rsfuncToEpg-[{0}]".format(epg_mo), module_object=epg_mo, target_filter={"tDn": epg_mo}), + ) + + aci.get_existing() + + if state == "present": + # Post configuration on infraGeneric (subclass_1) level instead of on + # infraRsFuncToEpg (subclass_2) level. + # The reason being that the MO "gen-default" (of class infraGeneric) does not + # exist until the first EPG to AEP association is created. + aci.construct_url( + root_class=dict(aci_class="infraAttEntityP", aci_rn="infra/attentp-{0}".format(aep), module_object=aep, target_filter={"name": aep}), + subclass_1=dict(aci_class="infraGeneric", aci_rn="gen-default", module_object="default", target_filter={"name": "default"}), + child_classes=["infraRsFuncToEpg"], + ) + + aci.get_existing() + + child_configs = [dict(infraRsFuncToEpg=dict(attributes=dict(encap=encap, primaryEncap=primary_encap, mode=interface_mode, tDn=epg_mo)))] + + aci.payload(aci_class="infraGeneric", class_config=dict(name="default"), child_configs=child_configs) + + aci.get_diff(aci_class="infraGeneric") + + 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_ap.py b/ansible_collections/cisco/aci/plugins/modules/aci_ap.py new file mode 100644 index 000000000..d5655efef --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_ap.py @@ -0,0 +1,293 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_ap +short_description: Manage top level Application Profile (AP) objects (fv:Ap) +description: +- Manage top level Application Profile (AP) objects on Cisco ACI fabrics +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of the application network profile. + type: str + aliases: [ app_profile, app_profile_name, name ] + description: + description: + - Description for the AP. + type: str + aliases: [ descr ] + monitoring_policy: + description: + - The name of the monitoring policy. + 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: +- This module does not manage EPGs, see M(cisco.aci.aci_epg) to do this. +- The used C(tenant) 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(fv:Ap). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Swetha Chunduri (@schunduri) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new AP + cisco.aci.aci_ap: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: default + description: default ap + monitoring_policy: default + state: present + delegate_to: localhost + +- name: Remove an AP + cisco.aci.aci_ap: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: default + state: absent + delegate_to: localhost + +- name: Query an AP + cisco.aci.aci_ap: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: default + state: query + delegate_to: localhost + register: query_result + +- name: Query all APs + cisco.aci.aci_ap: + 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 + + +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 + ap=dict(type="str", aliases=["app_profile", "app_profile_name", "name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + monitoring_policy=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "ap"]], + ["state", "present", ["tenant", "ap"]], + ], + ) + + ap = module.params.get("ap") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + monitoring_policy = module.params.get("monitoring_policy") + + child_configs = [dict(fvRsApMonPol=dict(attributes=dict(tnMonEPGPolName=monitoring_policy)))] + + 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="fvAp", + aci_rn="ap-{0}".format(ap), + module_object=ap, + target_filter={"name": ap}, + ), + child_classes=["fvRsApMonPol"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvAp", + class_config=dict( + name=ap, + descr=description, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fvAp") + + 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_bd.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd.py new file mode 100644 index 000000000..13d9d1938 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd.py @@ -0,0 +1,489 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_bd +short_description: Manage Bridge Domains (BD) objects (fv:BD) +description: +- Manages Bridge Domains (BD) on Cisco ACI fabrics. +options: + arp_flooding: + description: + - Determines if the Bridge Domain should flood ARP traffic. + - The APIC defaults to C(false) when unset during creation. + type: bool + bd: + description: + - The name of the Bridge Domain. + type: str + aliases: [ bd_name, name ] + bd_type: + description: + - The type of traffic on the Bridge Domain. + - The APIC defaults to C(ethernet) when unset during creation. + type: str + choices: [ ethernet, fc ] + description: + description: + - Description for the Bridge Domain. + type: str + enable_multicast: + description: + - Determines if PIM is enabled. + - The APIC defaults to C(false) when unset during creation. + type: bool + enable_routing: + description: + - Determines if IP forwarding should be allowed. + - The APIC defaults to C(true) when unset during creation. + type: bool + endpoint_clear: + description: + - Clears all End Points in all Leaves when C(true). + - The value is not reset to disabled once End Points have been cleared; that requires a second task. + - The APIC defaults to C(false) when unset during creation. + type: bool + endpoint_move_detect: + description: + - Determines if GARP should be enabled to detect when End Points move. + type: str + choices: [ default, garp ] + endpoint_retention_action: + description: + - Determines if the Bridge Domain should inherit or resolve the End Point Retention Policy. + - The APIC defaults to C(resolve) when unset during creation. + type: str + choices: [ inherit, resolve ] + endpoint_retention_policy: + description: + - The name of the End Point Retention Policy the Bridge Domain should use when + overriding the default End Point Retention Policy. + type: str + igmp_snoop_policy: + description: + - The name of the IGMP Snooping Policy the Bridge Domain should use when + overriding the default IGMP Snooping Policy. + type: str + ip_learning: + description: + - Determines if the Bridge Domain should learn End Point IPs. + - The APIC defaults to C(true) when unset during creation. + type: bool + ipv6_nd_policy: + description: + - The name of the IPv6 Neighbor Discovery Policy the Bridge Domain should use when + overridding the default IPV6 ND Policy. + type: str + l2_unknown_unicast: + description: + - Determines what forwarding method to use for unknown l2 destinations. + - The APIC defaults to C(proxy) when unset during creation. + type: str + choices: [ proxy, flood ] + l3_unknown_multicast: + description: + - Determines the forwarding method to use for unknown multicast destinations. + - The APIC defaults to C(flood) when unset during creation. + type: str + choices: [ flood, opt-flood ] + ipv6_l3_unknown_multicast: + description: + - Determines the forwarding method to use for IPv6 unknown multicast destinations. + - The APIC defaults to C(flood) when unset during creation. + type: str + choices: [ flood, opt-flood ] + limit_ip_learn: + description: + - Determines if the BD should limit IP learning to only subnets owned by the Bridge Domain. + - The APIC defaults to C(true) when unset during creation. + type: bool + mac_address: + description: + - The MAC Address to assign to the C(bd) instead of using the default. + - The APIC defaults to C(00:22:BD:F8:19:FF) when unset during creation. + type: str + aliases: [ mac ] + multi_dest: + description: + - Determines the forwarding method for L2 multicast, broadcast, and link layer traffic. + - The APIC defaults to C(bd-flood) when unset during creation. + type: str + choices: [ bd-flood, drop, encap-flood ] + 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 + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] + vrf: + description: + - The name of the VRF. + type: str + aliases: [ vrf_name ] + route_profile: + description: + - The Route Profile to associate with the Bridge Domain. + type: str + route_profile_l3out: + description: + - The L3 Out that contains the associated Route Profile. + 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(fv:BD). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Add Bridge Domain + cisco.aci.aci_bd: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + tenant: prod + bd: web_servers + mac_address: 00:22:BD:F8:19:FE + vrf: prod_vrf + state: present + delegate_to: localhost + +- name: Add an FC Bridge Domain + cisco.aci.aci_bd: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + tenant: prod + bd: storage + bd_type: fc + vrf: fc_vrf + enable_routing: false + state: present + delegate_to: localhost + +- name: Modify a Bridge Domain + cisco.aci.aci_bd: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: true + tenant: prod + bd: web_servers + arp_flooding: true + l2_unknown_unicast: flood + state: present + delegate_to: localhost + +- name: Query All Bridge Domains + cisco.aci.aci_bd: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: true + state: query + delegate_to: localhost + register: query_result + +- name: Query a Bridge Domain + cisco.aci.aci_bd: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: true + tenant: prod + bd: web_servers + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Bridge Domain + cisco.aci.aci_bd: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: true + tenant: prod + bd: web_servers + 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( + arp_flooding=dict(type="bool"), + bd=dict(type="str", aliases=["bd_name", "name"]), # Not required for querying all objects + bd_type=dict(type="str", choices=["ethernet", "fc"]), + description=dict(type="str"), + enable_multicast=dict(type="bool"), + enable_routing=dict(type="bool"), + endpoint_clear=dict(type="bool"), + endpoint_move_detect=dict(type="str", choices=["default", "garp"]), + endpoint_retention_action=dict(type="str", choices=["inherit", "resolve"]), + endpoint_retention_policy=dict(type="str"), + igmp_snoop_policy=dict(type="str"), + ip_learning=dict(type="bool"), + ipv6_nd_policy=dict(type="str"), + l2_unknown_unicast=dict(type="str", choices=["proxy", "flood"]), + l3_unknown_multicast=dict(type="str", choices=["flood", "opt-flood"]), + ipv6_l3_unknown_multicast=dict(type="str", choices=["flood", "opt-flood"]), + limit_ip_learn=dict(type="bool"), + mac_address=dict(type="str", aliases=["mac"]), + multi_dest=dict(type="str", choices=["bd-flood", "drop", "encap-flood"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + vrf=dict(type="str", aliases=["vrf_name"]), + route_profile=dict(type="str"), + route_profile_l3out=dict(type="str"), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["bd", "tenant"]], + ["state", "present", ["bd", "tenant"]], + ], + ) + + aci = ACIModule(module) + + arp_flooding = aci.boolean(module.params.get("arp_flooding")) + bd = module.params.get("bd") + bd_type = module.params.get("bd_type") + if bd_type == "ethernet": + # ethernet type is represented as regular, but that is not clear to the users + bd_type = "regular" + description = module.params.get("description") + enable_multicast = aci.boolean(module.params.get("enable_multicast")) + enable_routing = aci.boolean(module.params.get("enable_routing")) + endpoint_clear = aci.boolean(module.params.get("endpoint_clear")) + endpoint_move_detect = module.params.get("endpoint_move_detect") + if endpoint_move_detect == "default": + # the ACI default setting is an empty string, but that is not a good input value + endpoint_move_detect = "" + endpoint_retention_action = module.params.get("endpoint_retention_action") + endpoint_retention_policy = module.params.get("endpoint_retention_policy") + igmp_snoop_policy = module.params.get("igmp_snoop_policy") + ip_learning = aci.boolean(module.params.get("ip_learning")) + ipv6_nd_policy = module.params.get("ipv6_nd_policy") + l2_unknown_unicast = module.params.get("l2_unknown_unicast") + l3_unknown_multicast = module.params.get("l3_unknown_multicast") + ipv6_l3_unknown_multicast = module.params.get("ipv6_l3_unknown_multicast") + limit_ip_learn = aci.boolean(module.params.get("limit_ip_learn")) + mac_address = module.params.get("mac_address") + multi_dest = module.params.get("multi_dest") + state = module.params.get("state") + tenant = module.params.get("tenant") + vrf = module.params.get("vrf") + route_profile = module.params.get("route_profile") + route_profile_l3out = module.params.get("route_profile_l3out") + 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="fvBD", + aci_rn="BD-{0}".format(bd), + module_object=bd, + target_filter={"name": bd}, + ), + child_classes=["fvRsCtx", "fvRsIgmpsn", "fvRsBDToNdP", "fvRsBdToEpRet", "fvRsBDToProfile"], + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + arpFlood=arp_flooding, + descr=description, + epClear=endpoint_clear, + epMoveDetectMode=endpoint_move_detect, + ipLearning=ip_learning, + limitIpLearnToSubnets=limit_ip_learn, + mac=mac_address, + mcastAllow=enable_multicast, + multiDstPktAct=multi_dest, + name=bd, + type=bd_type, + unicastRoute=enable_routing, + unkMacUcastAct=l2_unknown_unicast, + unkMcastAct=l3_unknown_multicast, + nameAlias=name_alias, + ) + + if ipv6_l3_unknown_multicast is not None: + class_config["v6unkMcastAct"] = ipv6_l3_unknown_multicast + + aci.payload( + aci_class="fvBD", + class_config=class_config, + child_configs=[ + {"fvRsCtx": {"attributes": {"tnFvCtxName": vrf}}}, + {"fvRsIgmpsn": {"attributes": {"tnIgmpSnoopPolName": igmp_snoop_policy}}}, + {"fvRsBDToNdP": {"attributes": {"tnNdIfPolName": ipv6_nd_policy}}}, + {"fvRsBdToEpRet": {"attributes": {"resolveAct": endpoint_retention_action, "tnFvEpRetPolName": endpoint_retention_policy}}}, + {"fvRsBDToProfile": {"attributes": {"tnL3extOutName": route_profile_l3out, "tnRtctrlProfileName": route_profile}}}, + ], + ) + + aci.get_diff(aci_class="fvBD") + + 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_bd_dhcp_label.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd_dhcp_label.py new file mode 100644 index 000000000..484bdb2f6 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd_dhcp_label.py @@ -0,0 +1,317 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Dag Wieers (@dagwieers) +# Copyright: (c) 2020, sig9org (@sig9org) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_bd_dhcp_label +short_description: Manage DHCP Labels (dhcp:Lbl) +description: +- Manage DHCP Labels on Cisco ACI fabrics. +options: + bd: + description: + - The name of the Bridge Domain. + type: str + aliases: [ bd_name ] + description: + description: + - The description for the DHCP Label. + type: str + aliases: [ descr ] + dhcp_label: + description: + - The name of the DHCP Relay Label. + type: str + aliases: [ name ] + dhcp_option: + description: + - Name of the DHCP Option Policy to be associated with the DCHP Relay Policy. + This policy need to be present in the same tenant as the bridge domain. + - The DHCP option is used to supply DHCP clients with configuration parameters + such as a domain, name server, subnet, and network address. + type: str + scope: + description: + - Represents the target relay servers ownership. + type: str + choices: [ infra, tenant ] + default: infra + aliases: [ owner ] + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- A DHCP relay label contains a C(name) for the label, the C(scope), and a DHCP option policy. + The scope is the C(owner) of the relay server and the DHCP option policy supplies DHCP clients + with configuration parameters such as domain, nameserver, and subnet router addresses. +- The C(tenant) and C(bd) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module and M(cisco.aci.aci_bd) can be used for these. +seealso: +- module: cisco.aci.aci_bd +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(dhcp:Lbl). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- sig9 (@sig9org) +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a new DHCP Relay Label to a Bridge Domain + cisco.aci.aci_bd_dhcp_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + dhcp_label: label1 + scope: infra + state: present + +- name: Query a DHCP Relay Label of a Bridge Domain + cisco.aci.aci_bd_dhcp_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + dhcp_label: label1 + scope: infra + state: query + +- name: Query all DHCP Relay Labels of a Bridge Domain + cisco.aci.aci_bd_dhcp_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + state: query + +- name: Remove a DHCP Relay Label for a Bridge Domain + cisco.aci.aci_bd_dhcp_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + dhcp_label: label1 + scope: infra + state: absent +""" + +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( + bd=dict(type="str", aliases=["bd_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + dhcp_label=dict(type="str", aliases=["name"]), # Not required for querying all objects + dhcp_option=dict(type="str"), + scope=dict(type="str", default="infra", choices=["infra", "tenant"], aliases=["owner"]), # Not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["bd", "tenant", "dhcp_label", "scope"]], + ["state", "present", ["bd", "tenant", "dhcp_label", "scope"]], + ], + ) + + tenant = module.params.get("tenant") + bd = module.params.get("bd") + description = module.params.get("description") + dhcp_label = module.params.get("dhcp_label") + dhcp_option = module.params.get("dhcp_option") + scope = module.params.get("scope") + state = module.params.get("state") + + 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="fvBD", + aci_rn="BD-{0}".format(bd), + module_object=bd, + target_filter={"name": bd}, + ), + subclass_2=dict( + aci_class="dhcpLbl", + aci_rn="dhcplbl-{0}".format(dhcp_label), + module_object=dhcp_label, + target_filter={"name": dhcp_label}, + ), + child_classes=["dhcpRsDhcpOptionPol"], + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="dhcpLbl", + class_config=dict( + descr=description, + name=dhcp_label, + owner=scope, + ), + child_configs=[ + {"dhcpRsDhcpOptionPol": {"attributes": {"tnDhcpOptionPolName": dhcp_option}}}, + ], + ) + + aci.get_diff(aci_class="dhcpLbl") + + 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_bd_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py new file mode 100644 index 000000000..c10db5c8c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd_subnet.py @@ -0,0 +1,468 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_bd_subnet +short_description: Manage Subnets (fv:Subnet) +description: +- Manage Subnets on Cisco ACI fabrics. +options: + bd: + description: + - The name of the Bridge Domain. + type: str + aliases: [ bd_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; used when the BD is extended to multiple sites. + - 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_l3_out: + description: + - The L3 Out 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, no_gw, querier_ip, unspecified ] + subnet_name: + description: + - The name of the Subnet. + type: str + aliases: [ name ] + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_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 + +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) and C(bd) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module and M(cisco.aci.aci_bd) can be used for these. +seealso: +- module: cisco.aci.aci_bd +- 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: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Create a tenant + cisco.aci.aci_tenant: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + state: present + delegate_to: localhost + +- name: Create a bridge domain + cisco.aci.aci_bd: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + state: present + delegate_to: localhost + +- name: Create a subnet + cisco.aci.aci_bd_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + gateway: 10.1.1.1 + mask: 24 + state: present + delegate_to: localhost + +- name: Create a subnet with options + cisco.aci.aci_bd_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + subnet_name: sql + gateway: 10.1.2.1 + mask: 23 + description: SQL Servers + scope: public + route_profile_l3_out: corp + route_profile: corp_route_profile + state: present + delegate_to: localhost + +- name: Update a subnets scope to private and shared + cisco.aci.aci_bd_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + gateway: 10.1.1.1 + mask: 24 + scope: [private, shared] + state: present + delegate_to: localhost + +- name: Get all subnets + cisco.aci.aci_bd_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_bd_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_bd_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + gateway: 10.1.1.1 + mask: 24 + state: query + delegate_to: localhost + register: query_result + +- name: Delete a subnet + cisco.aci.aci_bd_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + 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.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="", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + bd=dict(type="str", aliases=["bd_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_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"]), + 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"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_together=[["gateway", "mask"]], + required_if=[ + ["state", "present", ["bd", "gateway", "mask", "tenant"]], + ["state", "absent", ["bd", "gateway", "mask", "tenant"]], + ], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + enable_vip = aci.boolean(module.params.get("enable_vip")) + tenant = module.params.get("tenant") + 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 gateway is not None: + gateway = "{0}/{1}".format(gateway, str(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_l3_out = module.params.get("route_profile_l3_out") + scope = module.params.get("scope") + 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="fvBD", + aci_rn="BD-{0}".format(bd), + module_object=bd, + target_filter={"name": bd}, + ), + subclass_2=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, + ), + child_configs=[ + {"fvRsBDSubnetToProfile": {"attributes": {"tnL3extOutName": route_profile_l3_out, "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_bd_to_l3out.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd_to_l3out.py new file mode 100644 index 000000000..fffe4cabf --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd_to_l3out.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_bd_to_l3out +short_description: Bind Bridge Domain to L3 Out (fv:RsBDToOut) +description: +- Bind Bridge Domain to L3 Out on Cisco ACI fabrics. +options: + bd: + description: + - The name of the Bridge Domain. + type: str + aliases: [ bd_name, bridge_domain ] + l3out: + description: + - The name of the l3out to associate with th Bridge Domain. + type: str + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(bd) and C(l3out) parameters should exist before using this module. + The M(cisco.aci.aci_bd) and C(aci_l3out) can be used for these. +seealso: +- module: cisco.aci.aci_bd +- module: cisco.aci.aci_l3out +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:RsBDToOut). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Bind Bridge Domain to L3 Out + cisco.aci.aci_bd_to_l3out: + host: apic + username: admin + password: SomeSecretPassword + bd: web_servers + l3out: prod_l3out + tenant: prod + state: present + delegate_to: localhost + +- name: Unbind Bridge Domain from L3 Out + cisco.aci.aci_bd_to_l3out: + host: apic + username: admin + password: SomeSecretPassword + bd: web_servers + l3out: prod_l3out + tenant: prod + state: absent + delegate_to: localhost + +- name: Query all Bridge Domains bound to L3 Outs + cisco.aci.aci_bd_to_l3out: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query specific Bridge Domain(s) bound to an L3 Out + cisco.aci.aci_bd_to_l3out: + host: apic + username: admin + password: SomeSecretPassword + bd: web_servers + l3out: prod_l3out + tenant: prod + 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 + +SUBNET_CONTROL_MAPPING = dict( + nd_ra="nd", + no_gw="no-default-gateway", + querier_ip="querier", + unspecified="", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + bd=dict(type="str", aliases=["bd_name", "bridge_domain"]), # Not required for querying all objects + l3out=dict(type="str"), # Not required for querying all objects + tenant=dict(type="str", aliases=["tenant_name"]), # 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", "present", ["bd", "l3out", "tenant"]], + ["state", "absent", ["bd", "l3out", "tenant"]], + ], + ) + + bd = module.params.get("bd") + l3out = module.params.get("l3out") + state = module.params.get("state") + tenant = module.params.get("tenant") + + 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="fvBD", + aci_rn="BD-{0}".format(bd), + module_object=bd, + target_filter={"name": bd}, + ), + subclass_2=dict( + aci_class="fvRsBDToOut", + aci_rn="rsBDToOut-{0}".format(l3out), + module_object=l3out, + target_filter={"tnL3extOutName": l3out}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvRsBDToOut", + class_config=dict(tnL3extOutName=l3out), + ) + + aci.get_diff(aci_class="fvRsBDToOut") + + 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_rr_asn.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_rr_asn.py new file mode 100644 index 000000000..4eae25e3d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_rr_asn.py @@ -0,0 +1,237 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_bgp_rr_asn +short_description: Manage BGP Route Reflector ASN. +description: +- Manage the BGP Autonomous System Number of the fabric (bgpAsP). +- This module is specifically for fabric BGP, for L3Out BGP use the aci_l3out_bgp_peer module +options: + asn: + description: + - BGP Autonomous System Number + type: int + 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(bgpAsP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Set fabric BGP AS number + cisco.aci.aci_bgp_rr_asn: + host: apic + username: admin + password: SomeSecretPassword + asn: 65001 + state: present + delegate_to: localhost + +- name: Remove fabric BGP AS number + cisco.aci.aci_bgp_rr_asn: + host: apic + username: admin + password: SomeSecretPassword + state: absent + delegate_to: localhost + +- name: Query fabric BGP AS number + cisco.aci.aci_bgp_rr_asn: + 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 "/></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( + asn=dict(type="int"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["asn"]], + ], + ) + + asn = module.params.get("asn") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="bgpInstPol", + aci_rn="fabric/bgpInstP-default", + module_object="default", + target_filter={"name": "default"}, + ), + subclass_1=dict( + aci_class="bgpAsP", + aci_rn="as", + module_object="name", + target_filter={"name": ""}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="bgpAsP", + class_config=dict( + asn=asn, + ), + ) + + aci.get_diff(aci_class="bgpAsP") + + 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_rr_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_rr_node.py new file mode 100644 index 000000000..1f37b769c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_rr_node.py @@ -0,0 +1,269 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_bgp_rr_node +short_description: Manage BGP Route Reflector objects. +description: +- Manage ACI BGP Route Reflector Nodes (bgpRRNodePEp). +options: + node_id: + description: + - ID of the Route Reflector Node + type: int + pod_id: + description: + - Pod the node belongs to + type: int + description: + description: + - Description of the Route Reflector Node + 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(bgpRRNodePEp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new BGP Route Reflector + cisco.aci.aci_bgp_rr_node: + host: apic + username: admin + password: SomeSecretPassword + node_id: 101 + pod_id: 1 + state: present + delegate_to: localhost + +- name: Remove a BGP Route Reflector + cisco.aci.aci_bgp_rr_node: + host: apic + username: admin + password: SomeSecretPassword + node_id: 101 + state: absent + delegate_to: localhost + +- name: Query a BGP Route Reflector + cisco.aci.aci_bgp_rr_node: + host: apic + username: admin + password: SomeSecretPassword + node_id: 101 + state: query + delegate_to: localhost + register: query_result + +- name: Query all BGP Route Reflectors + cisco.aci.aci_bgp_rr_node: + 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 "/></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( + node_id=dict(type="int"), + pod_id=dict(type="int"), + description=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", ["node_id"]], + ["state", "present", ["node_id", "pod_id"]], + ], + ) + + node_id = module.params.get("node_id") + pod_id = module.params.get("pod_id") + description = module.params.get("description") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="bgpInstPol", + aci_rn="fabric/bgpInstP-default", + module_object="default", + target_filter={"name": "default"}, + ), + subclass_1=dict( + aci_class="bgpRRP", + aci_rn="rr", + module_object="name", + target_filter={"name": ""}, + ), + subclass_2=dict( + aci_class="bgpRRNodePEp", + aci_rn="node-{0}".format(node_id), + module_object=node_id, + target_filter={"id": node_id}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="bgpRRNodePEp", + class_config=dict( + descr=description, + id=node_id, + podId=pod_id, + ), + ) + + aci.get_diff(aci_class="bgpRRNodePEp") + + 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 new file mode 100644 index 000000000..6ae4d5d55 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bulk_static_binding_to_epg.py @@ -0,0 +1,608 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_bulk_static_binding_to_epg +short_description: Bind static paths to EPGs (fv:RsPathAtt) +description: +- Bind static paths to EPGs on Cisco ACI fabrics. +options: + tenant: + description: + - Name of the tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of the application profile. + type: str + aliases: [ app_profile, app_profile_name ] + epg: + description: + - The name of the end point group. + type: str + aliases: [ epg_name ] + description: + description: + - Description for the static path to EPG binding. + type: str + aliases: [ descr ] + encap_id: + description: + - The encapsulation ID associating the C(epg) with the interface path. + - This acts as the secondary C(encap_id) when using micro-segmentation. + - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096). + type: int + aliases: [ vlan, vlan_id ] + primary_encap_id: + 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). + - C(unknown) is the default value and using C(unknown) disables the Micro-Segmentation. + type: str + aliases: [ primary_vlan, primary_vlan_id ] + deploy_immediacy: + description: + - The Deployment Immediacy of Static EPG on PC, VPC or Interface. + - The APIC defaults to C(lazy) when unset during creation. + type: str + choices: [ immediate, lazy ] + interface_mode: + description: + - Determines how layer 2 tags will be read from and added to frames. + - Values C(802.1p) and C(native) are identical. + - Values C(access) and C(untagged) are identical. + - Values C(regular), C(tagged) and C(trunk) are identical. + - The APIC defaults to C(trunk) when unset during creation. + type: str + choices: [ 802.1p, access, native, regular, tagged, trunk, untagged ] + aliases: [ interface_mode_name, mode ] + interface_type: + description: + - The type of interface for the static EPG deployment. + type: str + choices: [ fex, port_channel, switch_port, vpc, fex_port_channel, fex_vpc ] + default: switch_port + interface_configs: + description: + - List of interface configurations, elements in the form of a dictionary. + - Module level attributes will be overridden by the path level attributes. + type: list + elements: dict + suboptions: + description: + description: + - Description for the static path to EPG binding. + type: str + aliases: [ descr ] + encap_id: + description: + - The encapsulation ID associating the C(epg) with the interface path. + - This acts as the secondary C(encap_id) when using micro-segmentation. + - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096). + type: int + aliases: [ vlan, vlan_id ] + primary_encap_id: + 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). + - C(unknown) is the default value and using C(unknown) disables the Micro-Segmentation. + type: str + aliases: [ primary_vlan, primary_vlan_id ] + deploy_immediacy: + description: + - The Deployment Immediacy of Static EPG on PC, VPC or Interface. + - The APIC defaults to C(lazy) when unset during creation. + type: str + choices: [ immediate, lazy ] + interface_mode: + description: + - Determines how layer 2 tags will be read from and added to frames. + - Values C(802.1p) and C(native) are identical. + - Values C(access) and C(untagged) are identical. + - Values C(regular), C(tagged) and C(trunk) are identical. + - The APIC defaults to C(trunk) when unset during creation. + type: str + choices: [ 802.1p, access, native, regular, tagged, trunk, untagged ] + aliases: [ interface_mode_name, mode ] + interface_type: + description: + - The type of interface for the static EPG deployment. + type: str + choices: [ fex, port_channel, switch_port, vpc, fex_port_channel, fex_vpc ] + pod_id: + description: + - The pod number part of the tDn. + - C(pod_id) is usually an integer below C(10). + type: int + required: true + aliases: [ pod, pod_number ] + leafs: + description: + - The switch ID(s) that the C(interface) belongs to. + - When C(interface_type) is C(switch_port), C(port_channel), or C(fex), then C(leafs) is a string of the leaf ID. + - When C(interface_type) is C(vpc), then C(leafs) is a list with both leaf IDs. + - The C(leafs) value is usually something like '101' or '101-102' depending on C(connection_type). + type: list + elements: str + required: true + aliases: [ leaves, nodes, paths, switches ] + interface: + description: + - The C(interface) string value part of the tDn. + - Usually a policy group like C(test-IntPolGrp) or an interface of the following format C(1/7) depending on C(interface_type). + type: str + required: true + extpaths: + description: + - The C(extpaths) integer value part of the tDn. + - C(extpaths) is only used if C(interface_type) is C(fex), C(fex_vpc) or C(fex_port_channel). + - When C(interface_type) is C(fex_vpc), then C(extpaths) is a list with both fex IDs. + - Usually something like C(1011). + type: list + elements: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant), C(ap), C(epg) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_ap), M(cisco.aci.aci_epg) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_epg +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:RsPathAtt). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +- Marcel Zehnder (@maercu) +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Create list of interfaces using module level attributes + cisco.aci.aci_bulk_static_binding_to_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: accessport-code-cert + ap: accessport_code_app + epg: accessport_epg1 + encap_id: 221 + interface_mode: trunk + deploy_immediacy: lazy + description: "Module level attributes used to create interfaces" + interface_configs: + - interface: 1/7 + leafs: 101 + pod: 1 + - interface: 1/7 + leafs: 107 + pod: 7 + - interface: 1/8 + leafs: 108 + pod: 8 + encap_id: 108 + state: present + delegate_to: localhost + +- name: Create/Update list of interfaces using path level attributes + cisco.aci.aci_bulk_static_binding_to_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: accessport-code-cert + ap: accessport_code_app + epg: accessport_epg1 + interface_configs: + - interface: 1/7 + leafs: 101 + pod: 1 + encap_id: 221 + interface_mode: trunk + deploy_immediacy: lazy + description: "Path level attributes used to create/update interfaces" + - interface: 1/7 + leafs: 107 + pod: 7 + encap_id: 221 + interface_mode: trunk + deploy_immediacy: lazy + description: "Path level attributes used to create/update interfaces" + - interface: 1/8 + leafs: 108 + pod: 8 + encap_id: 108 + interface_mode: trunk + deploy_immediacy: lazy + description: "Path level attributes used to create/update interfaces" + state: present + delegate_to: localhost + +- name: Query all interfaces of an EPG + cisco.aci.aci_bulk_static_binding_to_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: accessport-code-cert + ap: accessport_code_app + epg: accessport_epg1 + state: query + delegate_to: localhost + +- name: Query all interfaces + cisco.aci.aci_bulk_static_binding_to_epg: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove list of interfaces + cisco.aci.aci_bulk_static_binding_to_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: accessport-code-cert + ap: accessport_code_app + epg: accessport_epg1 + encap_id: 221 + interface_mode: trunk + deploy_immediacy: lazy + interface_configs: + - interface: 1/7 + leafs: 101 + pod: 1 + - interface: 1/7 + leafs: 107 + pod: 7 + - interface: 1/8 + leafs: 108 + pod: 8 + encap_id: 108 + 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 + +INTERFACE_MODE_MAPPING = { + "802.1p": "native", + "access": "untagged", + "native": "native", + "regular": "regular", + "tagged": "regular", + "trunk": "regular", + "untagged": "untagged", +} + +INTERFACE_TYPE_MAPPING = { + "fex": "topology/pod-{pod_id}/paths-{leafs}/extpaths-{extpaths}/pathep-[eth{interface}]", + "fex_port_channel": "topology/pod-{pod_id}/paths-{leafs}/extpaths-{extpaths}/pathep-[{interface}]", + "fex_vpc": "topology/pod-{pod_id}/protpaths-{leafs}/extprotpaths-{extpaths}/pathep-[{interface}]", + "port_channel": "topology/pod-{pod_id}/paths-{leafs}/pathep-[{interface}]", + "switch_port": "topology/pod-{pod_id}/paths-{leafs}/pathep-[eth{interface}]", + "vpc": "topology/pod-{pod_id}/protpaths-{leafs}/pathep-[{interface}]", +} + +INTERFACE_STATUS_MAPPING = {"absent": "deleted"} + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), + epg=dict(type="str", aliases=["epg_name"]), + description=dict(type="str", aliases=["descr"]), + encap_id=dict(type="int", aliases=["vlan", "vlan_id"]), + primary_encap_id=dict(type="str", aliases=["primary_vlan", "primary_vlan_id"]), + deploy_immediacy=dict(type="str", choices=["immediate", "lazy"]), + interface_mode=dict( + type="str", choices=["802.1p", "access", "native", "regular", "tagged", "trunk", "untagged"], aliases=["interface_mode_name", "mode"] + ), + interface_type=dict(type="str", default="switch_port", choices=["fex", "port_channel", "switch_port", "vpc", "fex_port_channel", "fex_vpc"]), + interface_configs=dict( + type="list", + elements="dict", + options=dict( + description=dict(type="str", aliases=["descr"]), + encap_id=dict(type="int", aliases=["vlan", "vlan_id"]), + primary_encap_id=dict(type="str", aliases=["primary_vlan", "primary_vlan_id"]), + deploy_immediacy=dict(type="str", choices=["immediate", "lazy"]), + interface_mode=dict( + type="str", choices=["802.1p", "access", "native", "regular", "tagged", "trunk", "untagged"], aliases=["interface_mode_name", "mode"] + ), + interface_type=dict(type="str", choices=["fex", "port_channel", "switch_port", "vpc", "fex_port_channel", "fex_vpc"]), + pod_id=dict(type="int", required=True, aliases=["pod", "pod_number"]), + leafs=dict(type="list", elements="str", required=True, aliases=["leaves", "nodes", "paths", "switches"]), + interface=dict(type="str", required=True), + extpaths=dict(type="list", elements="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", ["ap", "epg", "tenant"]], + ["state", "present", ["ap", "epg", "tenant"]], + ], + ) + + tenant = module.params.get("tenant") + ap = module.params.get("ap") + epg = module.params.get("epg") + module_description = module.params.get("description") + module_encap_id = module.params.get("encap_id") + module_primary_encap_id = module.params.get("primary_encap_id") + module_deploy_immediacy = module.params.get("deploy_immediacy") + module_interface_mode = module.params.get("interface_mode") + module_interface_type = module.params.get("interface_type") + interface_configs = module.params.get("interface_configs") + state = module.params.get("state") + + aci = ACIModule(module) + children = [] + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter=dict(name=tenant), + ), + subclass_1=dict( + aci_class="fvAp", + aci_rn="ap-{0}".format(ap), + module_object=ap, + target_filter=dict(name=ap), + ), + subclass_2=dict( + aci_class="fvAEPg", + aci_rn="epg-{0}".format(epg), + module_object=epg, + target_filter=dict(name=epg), + ), + child_classes=["fvRsPathAtt"], + ) + + aci.get_existing() + + 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") + + description = interface_config.get("description") or module_description + deploy_immediacy = interface_config.get("deploy_immediacy") or module_deploy_immediacy + interface_type = interface_config.get("interface_type") or module_interface_type + encap_id = interface_config.get("encap_id") or module_encap_id + primary_encap_id = interface_config.get("primary_encap_id") or module_primary_encap_id + interface_mode = interface_config.get("interface_mode") or module_interface_mode + + 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') + + if extpaths is not None: + # Process extpaths, and support dash-delimited extpaths + extpaths = [] + for extpath in interface_config.get("extpaths"): + # Users are likely to use integers for extpaths IDs, which would raise an exception when using the join method + extpaths.extend(str(extpath).split("-")) + if len(extpaths) == 1: + if interface_type == "fex_vpc": + aci.fail_json(msg='A interface_type of "fex_vpc" requires 2 extpaths') + extpaths = extpaths[0] + elif len(extpaths) == 2: + if interface_type != "fex_vpc": + aci.fail_json(msg='The interface_types "fex" and "fex_port_channel" do not support using multiple extpaths for a single binding') + extpaths = "-".join(extpaths) + else: + aci.fail_json(msg='The "extpaths" parameter must not have more than 2 entries') + + if encap_id is not None: + if encap_id not in range(1, 4097): + aci.fail_json(msg="Valid VLAN assignments are from 1 to 4096") + encap_id = "vlan-{0}".format(encap_id) + + if primary_encap_id is not None: + try: + primary_encap_id = int(primary_encap_id) + if isinstance(primary_encap_id, int) and primary_encap_id in range(1, 4097): + primary_encap_id = "vlan-{0}".format(primary_encap_id) + else: + aci.fail_json(msg="Valid VLAN assignments are from 1 to 4096 or unknown.") + except Exception as e: + if isinstance(primary_encap_id, str) and primary_encap_id != "unknown": + aci.fail_json(msg="Valid VLAN assignments are from 1 to 4096 or unknown. %s" % e) + + static_path = INTERFACE_TYPE_MAPPING[interface_type].format(pod_id=pod_id, leafs=leafs, extpaths=extpaths, interface=interface) + + interface_mode = INTERFACE_MODE_MAPPING.get(interface_mode) + + interface_status = INTERFACE_STATUS_MAPPING.get(state) + + children.append( + dict( + fvRsPathAtt=dict( + attributes=dict( + descr=description, + encap=encap_id, + primaryEncap=primary_encap_id, + instrImedcy=deploy_immediacy, + mode=interface_mode, + tDn=static_path, + status=interface_status, + ) + ) + ) + ) + + aci.payload( + aci_class="fvAEPg", + class_config=dict(), + child_configs=children, + ) + + aci.get_diff(aci_class="fvAEPg") + + aci.post_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_cloud_ap.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_ap.py new file mode 100644 index 000000000..76dd26d04 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_ap.py @@ -0,0 +1,309 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, nkatarmal-crest (@nirav.katarmal) +# Copyright: (c) 2021, Cindy Zhao (@cizhao) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: aci_cloud_ap +short_description: Manage Cloud Application Profile (AP) (cloud:App) +description: +- Manage Cloud Application Profile (AP) objects on Cisco Cloud ACI +options: + description: + description: + - Description for the cloud application profile. + aliases: [ descr ] + type: str + name: + description: + - The name of the cloud application profile. + aliases: [ app_profile, app_profile_name, ap ] + type: str + tenant: + description: + - The name of an existing tenant. + aliases: [ tenant_name ] + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- More information about the internal APIC class B(cloud:App) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +author: +- Nirav (@nirav) +- Cindy Zhao (@cizhao) +""" + +EXAMPLES = r""" +- name: Add a new cloud AP + cisco.aci.aci_cloud_ap: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + name: intranet + description: Web Intranet EPG + state: present + delegate_to: localhost + +- name: Remove a cloud AP + cisco.aci.aci_cloud_ap: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + tenant: production + name: intranet + state: absent + delegate_to: localhost + +- name: Query a cloud AP + cisco.aci.aci_cloud_ap: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + name: ticketing + state: query + delegate_to: localhost + register: query_result + +- name: Query all cloud APs + cisco.aci.aci_cloud_ap: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query all cloud APs with a Specific Name + cisco.aci.aci_cloud_ap: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + name: ticketing + state: query + delegate_to: localhost + register: query_result + +- name: Query all cloud APs of a tenant + cisco.aci.aci_cloud_ap: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + 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_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 + + +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"]), + name=dict(type="str", aliases=["app_profile", "app_profile_name", "ap"]), + tenant=dict(type="str", aliases=["tenant_name"]), + 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", + "tenant", + ], + ], + [ + "state", + "present", + [ + "name", + "tenant", + ], + ], + ], + ) + + description = module.params["description"] + name = module.params["name"] + tenant = module.params["tenant"] + state = module.params["state"] + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + target_filter={"name": tenant}, + module_object=tenant, + ), + subclass_1=dict( + aci_class="cloudApp", + aci_rn="cloudapp-{0}".format(name), + target_filter={"name": name}, + module_object=name, + ), + child_classes=[], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="cloudApp", + class_config=dict( + descr=description, + name=name, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudApp") + + 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_cloud_aws_provider.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_aws_provider.py new file mode 100644 index 000000000..d6d663f7f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_aws_provider.py @@ -0,0 +1,290 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Shreyas Srish <ssrish@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_aws_provider +short_description: Manage Cloud AWS Provider (cloud:AwsProvider) +description: +- Manage AWS provider on Cisco Cloud ACI. +author: +- Shreyas Srish (@shrsr) +options: + access_key_id: + description: + - Cloud Access Key ID. + type: str + account_id: + description: + - AWS Account ID. + type: str + is_account_in_org: + description: + - Is Account in Organization. + type: bool + is_trusted: + description: + - Trusted Tenant + type: bool + secret_access_key: + description: + - Cloud Secret Access Key. + type: str + tenant: + description: + - Name of tenant. + 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: + - More information about the internal APIC class B(cloud:AwsProvider) from + - L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +""" + +EXAMPLES = r""" +- name: Create aws provider again after deletion as not trusted + cisco.aci.aci_cloud_aws_provider: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_test + account_id: 111111111111 + is_trusted: true + state: present + delegate_to: localhost + +- name: Delete aws provider + cisco.aci.aci_cloud_aws_provider: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_test + account_id: 111111111111 + is_trusted: true + state: absent + delegate_to: localhost + +- name: Query aws provider + cisco.aci.aci_cloud_aws_provider: + host: apic + username: admin + password: SomeSecretePassword + state: query + delegate_to: localhost + +- name: Query all aws provider + cisco.aci.aci_cloud_aws_provider: + host: apic + username: admin + password: SomeSecretePassword + 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_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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + { + "access_key_id": dict(type="str"), + "account_id": dict(type="str"), + "is_account_in_org": dict(type="bool"), + "is_trusted": dict(type="bool"), + "secret_access_key": dict(type="str", no_log=True), + "tenant": 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", ["tenant"]], + ["state", "present", ["tenant"]], + ], + ) + + aci = ACIModule(module) + + access_key_id = module.params.get("access_key_id") + account_id = module.params.get("account_id") + annotation = module.params.get("annotation") + is_account_in_org = aci.boolean(module.params.get("is_account_in_org")) + is_trusted = aci.boolean(module.params.get("is_trusted")) + secret_access_key = module.params.get("secret_access_key") + tenant = module.params.get("tenant") + state = module.params.get("state") + child_configs = [] + + aci.construct_url( + root_class={ + "aci_class": "fvTenant", + "aci_rn": "tn-{0}".format(tenant), + "target_filter": 'eq(fvTenant.name, "{0}")'.format(tenant), + "module_object": tenant, + }, + subclass_1={ + "aci_class": "cloudAwsProvider", + "aci_rn": "awsprovider", + "target_filter": {"account_id": account_id}, + "module_object": account_id, + }, + child_classes=[], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="cloudAwsProvider", + class_config={ + "accessKeyId": access_key_id, + "accountId": account_id, + "annotation": annotation, + "isAccountInOrg": is_account_in_org, + "isTrusted": is_trusted, + "secretAccessKey": secret_access_key, + }, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudAwsProvider") + + 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_cloud_bgp_asn.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_bgp_asn.py new file mode 100644 index 000000000..c5dcca746 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_bgp_asn.py @@ -0,0 +1,250 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: aci_cloud_bgp_asn +short_description: Manage Cloud APIC BGP Autonomous System Profile (cloud:BgpAsP) +description: +- Manage the BGP Autonomous System Profile for Cloud APIC +author: +- Anvitha Jain (@anvitha-jain) +version_added: '2.1.0' +options: + asn: + description: + - The BGP Autonomous System Number. + type: str + description: + description: + - Description of the BGP Autonomous System Profile. + aliases: [ descr ] + type: str + name: + description: + - object name + aliases: [ autonomous_system_profile, autonomous_system_profile_name ] + type: str + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- More information about the internal APIC class B(cloud:BgpAsP) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +""" + +EXAMPLES = r""" +- name: Add a new cloud BGP ASN + cisco.aci.aci_cloud_bgp_asn: + host: apic + username: admin + password: SomeSecretPassword + asn: 64601 + description: ASN description + name: ASN_1 + state: present + delegate_to: localhost +- name: Remove a cloud BGP ASN + cisco.aci.aci_cloud_bgp_asn: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + state: absent + delegate_to: localhost +- name: Query a cloud BGP ASN + cisco.aci.aci_cloud_bgp_asn: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + { + "asn": dict(type="str"), + "description": dict(type="str", aliases=["descr"]), + "name": dict(type="str", aliases=["autonomous_system_profile", "autonomous_system_profile_name"]), + "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, + ) + + annotation = module.params["annotation"] + asn = module.params["asn"] + description = module.params["description"] + name = module.params["name"] + name_alias = module.params["name_alias"] + state = module.params["state"] + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class={"aci_class": "cloudBgpAsP", "aci_rn": "clouddomp/as", "target_filter": {"name": name}, "module_object": None}, child_classes=[] + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="cloudBgpAsP", + class_config={ + "annotation": annotation, + "asn": asn, + "descr": description, + "name": name, + "nameAlias": name_alias, + }, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudBgpAsP") + + 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_cloud_cidr.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_cidr.py new file mode 100644 index 000000000..7f999eaa2 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_cidr.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, nkatarmal-crest <nirav.katarmal@crestdatasys.com> +# Copyright: (c) 2020, Cindy Zhao <cizhao@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_cidr +short_description: Manage CIDR under Cloud Context Profile (cloud:Cidr) +description: +- Manage Cloud CIDR on Cisco Cloud ACI. +author: +- Nirav (@nirav) +- Cindy Zhao (@cizhao) +options: + address: + description: + - CIDR ip and its netmask. + type: str + aliases: [ cidr ] + description: + description: + - Description of the Cloud CIDR. + type: str + name_alias: + description: + - An alias for the name of the current object. This relates to the nameAlias field in ACI and is used to rename object without changing the DN. + type: str + tenant: + description: + - The name of the Tenant. + type: str + required: true + cloud_context_profile: + description: + - The name of the Cloud Context Profile. + type: str + required: true + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- This module is only used to manage non_primary Cloud CIDR, see M(cisco.aci.aci_cloud_ctx_profile) to create the primary CIDR. +- More information about the internal APIC class B(cloud:Cidr) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +""" + +EXAMPLES = r""" +- name: Create non_primary CIDR + cisco.aci.aci_cloud_cidr: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + address: 10.10.0.0/16 + cloud_context_profile: ctxProfileName + state: present + delegate_to: localhost + +- name: Remove non_primary CIDR + cisco.aci.aci_cloud_cidr: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + address: 10.10.0.0/16 + cloud_context_profile: ctxProfileName + state: absent + delegate_to: localhost + +- name: Query all CIDRs under given cloud context profile + cisco.aci.aci_cloud_cidr: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + cloud_context_profile: ctxProfileName + state: query + delegate_to: localhost + +- name: Query specific CIDR under given cloud context profile + cisco.aci.aci_cloud_cidr: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + cloud_context_profile: ctxProfileName + address: 10.10.0.0/16 + 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_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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + address=dict(type="str", aliases=["cidr"]), + description=dict(type="str"), + name_alias=dict(type="str"), + tenant=dict(type="str", required=True), + cloud_context_profile=dict(type="str", required=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["address"]], + ["state", "present", ["address"]], + ], + ) + + address = module.params.get("address") + description = module.params.get("description") + name_alias = module.params.get("name_alias") + tenant = module.params.get("tenant") + cloud_context_profile = module.params.get("cloud_context_profile") + state = module.params.get("state") + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict(aci_class="fvTenant", aci_rn="tn-{0}".format(tenant), target_filter='eq(fvTenant.name, "{0}")'.format(tenant), module_object=tenant), + subclass_1=dict( + aci_class="cloudCtxProfile", + aci_rn="ctxprofile-{0}".format(cloud_context_profile), + target_filter='eq(cloudCtxProfile.name, "{0}")'.format(cloud_context_profile), + module_object=cloud_context_profile, + ), + subclass_2=dict( + aci_class="cloudCidr", aci_rn="cidr-[{0}]".format(address), target_filter='eq(cloudCidr.addr, "{0}")'.format(address), module_object=address + ), + child_classes=[], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="cloudCidr", + class_config=dict( + addr=address, + descr=description, + nameAlias=name_alias, + primary="no", + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudCidr") + + 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_cloud_ctx_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_ctx_profile.py new file mode 100644 index 000000000..82c4ebfd6 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_ctx_profile.py @@ -0,0 +1,309 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, nkatarmal-crest <nirav.katarmal@crestdatasys.com> +# Copyright: (c) 2020, Cindy Zhao <cizhao@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_ctx_profile +short_description: Manage Cloud Context Profile (cloud:CtxProfile) +description: +- Manage the Cloud Context Profile objects on Cisco Cloud ACI. +notes: +- More information about the internal APIC class B(cloud:CtxProfile) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +author: +- Nirav (@crestdatasys) +- Cindy Zhao (@cizhao) +options: + name: + description: + - The name of the Cloud Context Profile + type: str + aliases: [ cloud_context_profile ] + description: + description: + - Description of the Cloud Context Profile + type: str + name_alias: + description: + - An alias for the name of the current object. This relates to the nameAlias field in ACI and is used to rename object without changing the DN + type: str + tenant: + description: + - The name of the Tenant. + type: str + primary_cidr: + description: + - The subnet with netmask to use as primary CIDR block for the Cloud Context Profile. + type: str + vrf: + description: + - The name of the VRF. + type: str + region: + description: + - The name of the cloud region in which to deploy the network construct. + type: str + cloud: + description: + - The cloud vendor in which the controller runs. + choices: [ aws, azure ] + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + type: str + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner +""" + +EXAMPLES = r""" +- name: Add a new aci cloud ctx profile + cisco.aci.aci_cloud_ctx_profile: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: tenant_1 + name: cloud_ctx_profile + vrf: VRF1 + region: us-west-1 + cloud: aws + primary_cidr: '10.0.10.1/16' + state: present + delegate_to: localhost + +- name: Remove an aci cloud ctx profile + cisco.aci.aci_cloud_ctx_profile: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: tenant_1 + name: cloud_ctx_profile + state: absent + delegate_to: localhost + +- name: Query a specific aci cloud ctx profile + cisco.aci.aci_cloud_ctx_profile: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: anstest + name: ctx_profile_1 + state: query + delegate_to: localhost + +- name: Query all aci cloud ctx profile + cisco.aci.aci_cloud_ctx_profile: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: anstest + 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_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 + + +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", + ), + name=dict(type="str", aliases=["cloud_context_profile"]), + name_alias=dict( + type="str", + ), + tenant=dict( + type="str", + ), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + primary_cidr=dict( + type="str", + ), + # FIXME: didn't find the flow_log in UI + # flow_log=dict(type='str'), + vrf=dict(type="str"), + region=dict(type="str"), + cloud=dict(type="str", choices=["aws", "azure"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "tenant"]], + ["state", "present", ["name", "tenant", "vrf", "region", "primary_cidr", "cloud"]], + ], + ) + + description = module.params.get("description") + name = module.params.get("name") + name_alias = module.params.get("name_alias") + tenant = module.params.get("tenant") + state = module.params.get("state") + primary_cidr = module.params.get("primary_cidr") + child_configs = [] + + vrf = module.params.get("vrf") + region = module.params.get("region") + cloud = module.params.get("cloud") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict(aci_class="fvTenant", aci_rn="tn-{0}".format(tenant), target_filter='eq(fvTenant.name, "{0}")'.format(tenant), module_object=tenant), + subclass_1=dict( + aci_class="cloudCtxProfile", aci_rn="ctxprofile-{0}".format(name), target_filter='eq(cloudCtxProfile.name, "{0}")'.format(name), module_object=name + ), + child_classes=["cloudRsToCtx", "cloudRsCtxProfileToRegion", "cloudRouterP", "cloudCidr"], + ) + + aci.get_existing() + + if state == "present": + child_configs.append(dict(cloudRsToCtx=dict(attributes=dict(tnFvCtxName=vrf)))) + child_configs.append(dict(cloudRsCtxProfileToRegion=dict(attributes=dict(tDn="uni/clouddomp/provp-{0}/region-{1}".format(cloud, region))))) + child_configs.append(dict(cloudCidr=dict(attributes=dict(addr=primary_cidr, primary="yes")))) + aci.payload( + aci_class="cloudCtxProfile", + class_config=dict( + descr=description, + name=name, + name_alias=name_alias, + type="regular", + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudCtxProfile") + 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_cloud_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_epg.py new file mode 100644 index 000000000..d705b9bcc --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_epg.py @@ -0,0 +1,294 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Cindy Zhao <cizhao@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_epg +short_description: Manage Cloud EPG (cloud:EPg) +description: +- Manage Cloud EPG on Cisco Cloud ACI +options: + tenant: + description: + - The name of the existing tenant. + type: str + ap: + description: + - The name of the cloud application profile. + aliases: [ app_profile, app_profile_name ] + type: str + name: + description: + - The name of the cloud EPG. + aliases: [ cloud_epg, cloud_epg_name, epg, epg_name ] + type: str + description: + description: + - Description of the cloud EPG. + aliases: [ descr ] + type: str + vrf: + description: + - The name of the VRF. + type: str + aliases: [ context, vrf_name ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- More information about the internal APIC class B(cloud:EPg) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +author: +- Nirav (@nirav) +- Cindy Zhao (@cizhao) +""" + +EXAMPLES = r""" +- name: Create aci cloud epg (check_mode) + cisco.aci.aci_cloud_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + ap: apName + vrf: vrfName + description: Aci Cloud EPG + name: epgName + state: present + delegate_to: localhost + +- name: Remove cloud epg + cisco.aci.aci_cloud_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + ap: apName + name: cloudName + state: absent + delegate_to: localhost + +- name: query all + cisco.aci.aci_cloud_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + ap: apName + state: query + delegate_to: localhost + +- name: query a specific cloud epg + cisco.aci.aci_cloud_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + ap: apName + name: epgName + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + { + "description": dict(type="str", aliases=["descr"]), + "name": dict(type="str", aliases=["cloud_epg", "cloud_epg_name", "epg", "epg_name"]), + "tenant": dict( + type="str", + ), + "ap": dict(type="str", aliases=["app_profile", "app_profile_name"]), + "state": dict(type="str", default="present", choices=["absent", "present", "query"]), + "vrf": dict(type="str", aliases=["context", "vrf_name"]), + } + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "tenant", "ap"]], + ["state", "present", ["name", "tenant", "ap"]], + ], + ) + + description = module.params.get("description") + name = module.params.get("name") + tenant = module.params.get("tenant") + ap = module.params.get("ap") + state = module.params.get("state") + child_configs = [] + relation_vrf = module.params.get("vrf") + + if relation_vrf: + child_configs.append({"cloudRsCloudEPgCtx": {"attributes": {"tnFvCtxName": relation_vrf}}}) + + aci = ACIModule(module) + aci.construct_url( + root_class={ + "aci_class": "fvTenant", + "aci_rn": "tn-{0}".format(tenant), + "target_filter": 'eq(fvTenant.name, "{0}")'.format(tenant), + "module_object": tenant, + }, + subclass_1={"aci_class": "cloudApp", "aci_rn": "cloudapp-{0}".format(ap), "target_filter": 'eq(cloudApp.name, "{0}")'.format(ap), "module_object": ap}, + subclass_2={ + "aci_class": "cloudEPg", + "aci_rn": "cloudepg-{0}".format(name), + "target_filter": 'eq(cloudEPg.name, "{0}")'.format(name), + "module_object": name, + }, + child_classes=["cloudRsCloudEPgCtx"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="cloudEPg", + class_config={ + "descr": description, + "name": name, + }, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudEPg") + + 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_cloud_epg_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_epg_selector.py new file mode 100644 index 000000000..3c6e12364 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_epg_selector.py @@ -0,0 +1,371 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Cindy Zhao <cizhao@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_epg_selector +short_description: Manage Cloud Endpoint Selector (cloud:EPSelector) +description: +- Manage Cloud Endpoint Selector on Cisco Cloud ACI +notes: +- More information about the internal APIC class B(cloud:EPSelector) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +author: +- Cindy Zhao (@cizhao) +options: + description: + description: + - Description of the Cloud Endpoint Selector. + type: str + expressions: + description: + - Expressions associated to this selector. + type: list + elements: dict + suboptions: + key: + description: + - The key of the expression. + - The key is custom or is one of region, zone and ip + - The key can be zone only when the site is AWS. + required: true + type: str + operator: + description: + - The operator associated to the expression. + - Operator C(has_key) or C(does_not_have_key) is only available for key custom or zone + required: true + type: str + choices: [ not_in, in, equals, not_equals, has_key, does_not_have_key ] + value: + description: + - The value associated to the expression. + - If the operator is C(in) or C(not_in), the value should be a comma separated string. + type: str + name: + description: + - The name of the Cloud Endpoint selector. + aliases: [ selector, selector_name ] + type: str + tenant: + description: + - The name of the existing tenant. + required: true + type: str + ap: + description: + - The name of the cloud application profile. + required: true + type: str + epg: + description: + - The name of the cloud EPG. + required: true + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner +""" + +EXAMPLES = r""" +- name: Create aci cloud epg selector + cisco.aci.aci_cloud_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + ap: apName + epg: epgName + description: cloud epg selector + name: selectorName + expressions: + - key: ip + operator: in + value: 10.10.10.1 + state: present + delegate_to: localhost + +- name: Remove cloud epg selector + cisco.aci.aci_cloud_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + ap: apName + epg: epgName + name: selectorName + state: absent + delegate_to: localhost + +- name: query all + cisco.aci.aci_cloud_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + ap: apName + epg: epgName + state: query + delegate_to: localhost + +- name: query a specific cloud epg selector + cisco.aci.aci_cloud_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + ap: apName + epg: epgName + name: selectorName + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, expression_spec, aci_annotation_spec, aci_owner_spec +from ansible.module_utils.basic import AnsibleModule + +EXPRESSION_KEYS = { + "ip": "IP", + "region": "Region", + "zone": "Zone", +} + +EXPRESSION_OPERATORS = { + "not_in": "notin", + "not_equals": "!=", + "in": "in", + "equals": "==", +} + + +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"), + "expressions": dict(type="list", elements="dict", options=expression_spec()), + "name": dict(type="str", aliases=["selector", "selector_name"]), + "tenant": dict(type="str", required=True), + "ap": dict(type="str", required=True), + "epg": dict(type="str", required=True), + "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"]], + ], + ) + + description = module.params.get("description") + expressions = module.params.get("expressions") + name = module.params.get("name") + tenant = module.params.get("tenant") + ap = module.params.get("ap") + epg = module.params.get("epg") + state = module.params.get("state") + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class={ + "aci_class": "fvTenant", + "aci_rn": "tn-{0}".format(tenant), + "target_filter": 'eq(fvTenant.name, "{0}")'.format(tenant), + "module_object": tenant, + }, + subclass_1={"aci_class": "cloudApp", "aci_rn": "cloudapp-{0}".format(ap), "target_filter": 'eq(cloudApp.name, "{0}")'.format(ap), "module_object": ap}, + subclass_2={ + "aci_class": "cloudEPg", + "aci_rn": "cloudepg-{0}".format(epg), + "target_filter": 'eq(cloudEPg.name, "{0}")'.format(epg), + "module_object": epg, + }, + subclass_3={ + "aci_class": "cloudEPSelector", + "aci_rn": "epselector-{0}".format(name), + "target_filter": 'eq(cloudEPSelector.name, "{0}")'.format(name), + "module_object": name, + }, + child_classes=[], + ) + + aci.get_existing() + + if state == "present": + expressions_list = [] + for expression in expressions: + key = expression.get("key") + operator = expression.get("operator") + if expression.get("value"): + value = "'" + "','".join(expression.get("value").split(",")) + "'" + else: + value = None + if operator in ["has_key", "does_not_have_key"]: + if value: + module.fail_json(msg="Attribute 'value' is not supported for operator '{0}' in expression '{1}'".format(operator, key)) + if key in ["ip", "region"]: + module.fail_json(msg="Operator '{0}' is not supported when expression key is '{1}'".format(operator, key)) + if operator in ["not_in", "in", "equals", "not_equals"] and not value: + module.fail_json(msg="Attribute 'value' needed for operator '{0}' in expression '{1}'".format(operator, key)) + if key in ["ip", "region", "zone"]: + key = EXPRESSION_KEYS.get(key) + else: + key = "custom:" + key + if operator in ["not_in", "in"]: + expressions_list.append("{0} {1}({2})".format(key, EXPRESSION_OPERATORS.get(operator), value)) + elif operator in ["equals", "not_equals"]: + expressions_list.append("{0}{1}{2}".format(key, EXPRESSION_OPERATORS.get(operator), value)) + elif operator == "does_not_have_key": + expressions_list.append("!{0}".format(key)) + else: + expressions_list.append(key) + matchExpression = ",".join(expressions_list) + aci.payload( + aci_class="cloudEPSelector", + class_config={ + "descr": description, + "matchExpression": matchExpression, + "name": name, + }, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudEPSelector") + + 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_cloud_external_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_external_epg.py new file mode 100644 index 000000000..d1c9afff8 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_external_epg.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: aci_cloud_external_epg +short_description: Manage Cloud External EPG (cloud:ExtEPg) +description: +- Configures WAN router connectivity to the cloud infrastructure. +options: + description: + description: + - configuration item description. + aliases: [ descr ] + type: str + name: + description: + - Name of Object cloud_external_epg. + aliases: [ cloud_external_epg, cloud_external_epg_name, external_epg, external_epg_name, extepg, extepg_name ] + type: str + route_reachability: + description: + - Route reachability for this EPG. + choices: [ inter-site, internet, unspecified ] + type: str + tenant: + description: + - The name of tenant. + type: str + ap: + description: + - The name of the cloud application profile. + aliases: [ app_profile, app_profile_name ] + type: str + vrf: + description: + - The name of the VRF. + type: str + aliases: [ context, vrf_name ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- More information about the internal APIC class B(cloud:ExtEPg) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +author: +- Anvitha Jain (@anvitha-jain) +""" + +EXAMPLES = r""" +- name: Add a new cloud external EPG + cisco.aci.aci_cloud_external_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenant1 + ap: ap1 + vrf: vrf1 + description: Cloud External EPG description + name: ext_epg + route_reachability: internet + state: present + delegate_to: localhost + +- name: Remove a cloud external EPG + cisco.aci.aci_cloud_external_epg: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + tenant: tenant1 + ap: ap1 + name: ext_epg + state: absent + delegate_to: localhost + +- name: Query a cloud external EPG + cisco.aci.aci_cloud_external_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenant1 + ap: ap1 + name: ext_epg + state: query + delegate_to: localhost + +- name: query all + cisco.aci.aci_cloud_external_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenant1 + ap: ap1 + 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", + "name_alias": "", + "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", + "name_alias": "", + "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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + { + "description": dict(type="str", aliases=["descr"]), + "name": dict(type="str", aliases=["cloud_external_epg", "cloud_external_epg_name", "external_epg", "external_epg_name", "extepg", "extepg_name"]), + "route_reachability": dict(type="str", choices=["inter-site", "internet", "unspecified"]), + "tenant": dict(type="str"), + "ap": dict(type="str", aliases=["app_profile", "app_profile_name"]), + "state": dict(type="str", default="present", choices=["absent", "present", "query"]), + "vrf": dict(type="str", aliases=["context", "vrf_name"]), + } + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "tenant", "ap"]], + ["state", "present", ["name", "tenant", "ap"]], + ], + ) + + description = module.params.get("description") + name = module.params.get("name") + route_reachability = module.params.get("route_reachability") + tenant = module.params.get("tenant") + ap = module.params.get("ap") + state = module.params.get("state") + child_configs = [] + relation_vrf = module.params.get("vrf") + + if relation_vrf: + child_configs.append({"cloudRsCloudEPgCtx": {"attributes": {"tnFvCtxName": relation_vrf}}}) + + aci = ACIModule(module) + aci.construct_url( + root_class={ + "aci_class": "fvTenant", + "aci_rn": "tn-{0}".format(tenant), + "target_filter": 'eq(fvTenant.name, "{0}")'.format(tenant), + "module_object": tenant, + }, + subclass_1={"aci_class": "cloudApp", "aci_rn": "cloudapp-{0}".format(ap), "target_filter": 'eq(cloudApp.name, "{0}")'.format(ap), "module_object": ap}, + subclass_2={ + "aci_class": "cloudExtEPg", + "aci_rn": "cloudextepg-{0}".format(name), + "target_filter": 'eq(cloudExtEPg.name, "{0}")'.format(name), + "module_object": name, + }, + child_classes=["fvRsCustQosPol", "cloudRsCloudEPgCtx"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="cloudExtEPg", + class_config={ + "descr": description, + "name": name, + "routeReachability": route_reachability, + }, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudExtEPg") + + 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_cloud_external_epg_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_external_epg_selector.py new file mode 100644 index 000000000..1ee35bd6f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_external_epg_selector.py @@ -0,0 +1,183 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: aci_cloud_external_epg_selector +short_description: Manage Cloud Endpoint Selector for External EPGs (cloud:ExtEPSelector) +description: +- Decides which endpoints belong to the EPGs based on several parameters. +options: + name: + description: + - The name of the Cloud Endpoint selector. + aliases: [ selector, cloud_external_epg_selector, external_epg_selector, extepg_selector, selector_name ] + type: str + subnet: + description: + - IP address of the Cloud Subnet. + aliases: [ ip ] + type: str + tenant: + description: + - The name of tenant. + type: str + ap: + description: + - The name of the cloud application profile. + aliases: [ app_profile, app_profile_name ] + type: str + cloud_external_epg: + description: + - Name of Object cloud_external_epg. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- More information about the internal APIC class B(cloud:ExtEPSelector) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +author: +- Anvitha Jain (@anvitha-jain) +""" + +EXAMPLES = r""" +- name: Add a new cloud external EPG selector + cisco.aci.aci_cloud_external_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenant1 + ap: ap1 + cloud_external_epg: ext_epg + name: subnet_name + subnet: 10.0.0.0/16 + state: present + delegate_to: localhost + +- name: Remove a cloud external EPG selector + cisco.aci.aci_cloud_external_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + tenant: tenant1 + ap: ap1 + cloud_external_epg: ext_epg + name: subnet_name + subnet: 10.0.0.0/16 + state: absent + delegate_to: localhost + +- name: Query all cloud external EPG selectors + cisco.aci.aci_cloud_external_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenant1 + ap: ap1 + cloud_external_epg: ext_epg + state: query + delegate_to: localhost +""" + +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 + + +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=["selector", "cloud_external_epg_selector", "external_epg_selector", "extepg_selector", "selector_name"]), + "subnet": dict(type="str", aliases=["ip"]), + "tenant": dict(type="str"), + "cloud_external_epg": dict(type="str"), + "ap": dict(type="str", aliases=["app_profile", "app_profile_name"]), + "state": dict(type="str", default="present", choices=["absent", "present", "query"]), + } + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["subnet", "tenant", "ap", "cloud_external_epg"]], + ["state", "present", ["subnet", "tenant", "ap", "cloud_external_epg"]], + ], + ) + + name = module.params.get("name") + subnet = module.params.get("subnet") + tenant = module.params.get("tenant") + ap = module.params.get("ap") + cloud_external_epg = module.params.get("cloud_external_epg") + state = module.params.get("state") + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class={ + "aci_class": "fvTenant", + "aci_rn": "tn-{0}".format(tenant), + "target_filter": 'eq(fvTenant.name, "{0}")'.format(tenant), + "module_object": tenant, + }, + subclass_1={"aci_class": "cloudApp", "aci_rn": "cloudapp-{0}".format(ap), "target_filter": 'eq(cloudApp.name, "{0}")'.format(ap), "module_object": ap}, + subclass_2={ + "aci_class": "cloudExtEPg", + "aci_rn": "cloudextepg-{0}".format(cloud_external_epg), + "target_filter": 'eq(cloudExtEPg.name, "{0}")'.format(cloud_external_epg), + "module_object": cloud_external_epg, + }, + subclass_3={ + "aci_class": "cloudExtEPSelector", + "aci_rn": "extepselector-[{0}]".format(subnet), + "target_filter": 'eq(cloudExtEPSelector.name, "{0}")'.format(subnet), + "module_object": subnet, + }, + child_classes=[], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="cloudExtEPSelector", + class_config={ + "name": name, + "subnet": subnet, + }, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudExtEPSelector") + + 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_cloud_provider.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_provider.py new file mode 100644 index 000000000..a1a7ba5ab --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_provider.py @@ -0,0 +1,177 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_provider +short_description: Query Cloud Provider information (cloud:ProvP) +description: +- Query Cloud Provider information (cloud:ProvP) on Cisco Cloud ACI. +author: +- Lionel Hercot (@lhercot) +options: + state: + description: + - Use C(query) for listing an object or multiple objects. + choices: [ query ] + default: query + type: str + +notes: +- More information about the internal APIC class B(cloud:ProvP) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +- This module is used to query Cloud Provider information. +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +""" + +EXAMPLES = r""" +- name: Query cloud provider information + cisco.aci.aci_cloud_provider: + host: apic + username: userName + password: somePassword + validate_certs: false + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + state=dict(type="str", default="query", choices=["query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + aci = ACIModule(module) + aci.construct_url(root_class=dict(aci_class="cloudProvP")) + + aci.get_existing() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_cloud_region.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_region.py new file mode 100644 index 000000000..4f2d4d1ca --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_region.py @@ -0,0 +1,216 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, nkatarmal-crest <nirav.katarmal@crestdatasys.com> +# Copyright: (c) 2020, Cindy Zhao <cizhao@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_region +short_description: Manage Cloud Providers Region (cloud:Region) +description: +- Manage Cloud Providers Region on Cisco Cloud ACI. +author: +- Nirav (@nirav) +- Cindy Zhao (@cizhao) +options: + region: + description: + - The name of the cloud provider's region. + aliases: [ name ] + type: str + cloud: + description: + - The vendor of the controller + choices: [ aws, azure ] + type: str + required: true + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ query ] + default: query + type: str + +notes: +- More information about the internal APIC class B(cloud:Region) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +- This module is used to query Cloud Providers Region. +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +""" + +EXAMPLES = r""" +- name: Query all regions + cisco.aci.aci_cloud_region: + host: apic + username: userName + password: somePassword + validate_certs: false + cloud: 'aws' + state: query + delegate_to: localhost + +- name: Query a specific region + cisco.aci.aci_cloud_region: + host: apic + username: userName + password: somePassword + validate_certs: false + cloud: 'aws' + region: eu-west-2 + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + region=dict(type="str", aliases=["name"]), + cloud=dict(type="str", choices=["aws", "azure"], required=True), + state=dict(type="str", default="query", choices=["query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + region = module.params.get("region") + cloud = module.params.get("cloud") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="cloudProvP", aci_rn="clouddomp/provp-{0}".format(cloud), target_filter='eq(cloudProvP.vendor, "{0}")'.format(cloud), module_object=cloud + ), + subclass_1=dict( + aci_class="cloudRegion", aci_rn="region-{0}".format(region), target_filter='eq(cloudRegion.name, "{0}")'.format(region), module_object=region + ), + child_classes=[], + ) + + aci.get_existing() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_cloud_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_subnet.py new file mode 100644 index 000000000..31845f629 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_subnet.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, nkatarmal-crest <nirav.katarmal@crestdatasys.com> +# Copyright: (c) 2020, Cindy Zhao <cizhao@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_subnet +short_description: Manage Cloud Subnet (cloud:Subnet) +description: +- Manage Cloud Subnet on Cisco Cloud ACI. +notes: +- More information about the internal APIC class B(cloud:Subnet) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +author: +- Nirav (@nirav) +- Cindy Zhao (@cizhao) +options: + name: + description: + - The name of the Cloud Subnet. + type: str + description: + description: + - Description of the Cloud Subnet. + type: str + address: + description: + - Ip address of the Cloud Subnet. + type: str + aliases: [subnet] + name_alias: + description: + - An alias for the name of the current object. This relates to the nameAlias field in ACI and is used to rename object without changing the DN. + type: str + tenant: + description: + - The name of tenant. + type: str + required: true + cloud_context_profile: + description: + - The name of cloud context profile. + type: str + required: true + cidr: + description: + - Address of cloud cidr. + type: str + required: true + availability_zone: + description: + - The cloud zone which is attached to the given cloud context profile. + - Only used when it is an aws cloud apic. + type: str + vnet_gateway: + description: + - Determine if a vNet Gateway Router will be deployed or not. + - Only used when it is an azure cloud apic. + type: bool + default: false + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + default: present + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +""" + +EXAMPLES = r""" +- name: Create aci cloud subnet + cisco.aci.aci_cloud_subnet: + host: apic + username: userName + password: somePassword + validate_certs: false + tenant: anstest + cloud_context_profile: aws_cloudCtxProfile + cidr: '10.10.0.0/16' + availability_zone: us-west-1a + address: 10.10.0.1 + delegate_to: localhost + +- name: Query a specific subnet + cisco.aci.aci_cloud_subnet: + host: apic + username: userName + password: somePassword + validate_certs: false + tenant: anstest + cloud_context_profile: ctx_profile_1 + cidr: '10.10.0.0/16' + address: 10.10.0.1 + state: query + delegate_to: localhost + +- name: Query all subnets + cisco.aci.aci_cloud_subnet: + host: apic + username: userName + password: somePassword + validate_certs: false + tenant: anstest + cloud_context_profile: ctx_profile_1 + cidr: '10.10.0.0/16' + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + name=dict(type="str", aliases=["subnet"]), + description=dict(type="str"), + address=dict(type="str"), + name_alias=dict(type="str"), + vnet_gateway=dict(type="bool", default=False), + tenant=dict(type="str", required=True), + cloud_context_profile=dict(type="str", required=True), + cidr=dict(type="str", required=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + availability_zone=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["address"]], + ["state", "present", ["address"]], + ], + ) + + name = module.params.get("name") + description = module.params.get("description") + address = module.params.get("address") + name_alias = module.params.get("name_alias") + vnet_gateway = module.params.get("vnet_gateway") + tenant = module.params.get("tenant") + cloud_context_profile = module.params.get("cloud_context_profile") + cidr = module.params.get("cidr") + state = module.params.get("state") + availability_zone = module.params.get("availability_zone") + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict(aci_class="fvTenant", aci_rn="tn-{0}".format(tenant), target_filter='eq(fvTenant.name, "{0}")'.format(tenant), module_object=tenant), + subclass_1=dict( + aci_class="cloudCtxProfile", + aci_rn="ctxprofile-{0}".format(cloud_context_profile), + target_filter='eq(cloudCtxProfile.name, "{0}")'.format(cloud_context_profile), + module_object=cloud_context_profile, + ), + subclass_2=dict(aci_class="cloudCidr", aci_rn="cidr-[{0}]".format(cidr), target_filter='eq(cloudCidr.addr, "{0}")'.format(cidr), module_object=cidr), + subclass_3=dict( + aci_class="cloudSubnet", aci_rn="subnet-[{0}]".format(address), target_filter='eq(cloudSubnet.ip, "{0}")'.format(address), module_object=address + ), + child_classes=["cloudRsZoneAttach"], + ) + + aci.get_existing() + + if state == "present": + # in aws cloud apic + if availability_zone: + region = availability_zone[:-1] + tDn = "uni/clouddomp/provp-aws/region-{0}/zone-{1}".format(region, availability_zone) + child_configs.append({"cloudRsZoneAttach": {"attributes": {"tDn": tDn}}}) + # in azure cloud apic + if vnet_gateway: + usage = "gateway" + else: + usage = "user" + + aci.payload( + aci_class="cloudSubnet", + class_config=dict( + name=name, + descr=description, + ip=address, + nameAlias=name_alias, + scope="private", + usage=usage, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="cloudSubnet") + + 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_cloud_vpn_gateway.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_vpn_gateway.py new file mode 100644 index 000000000..29f82243c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_vpn_gateway.py @@ -0,0 +1,235 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Cindy Zhao <cizhao@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_vpn_gateway +short_description: Manage cloudRouterP in Cloud Context Profile (cloud:cloudRouterP) +description: +- Manage cloudRouterP objects on Cisco Cloud ACI. +notes: +- More information about the internal APIC class B(cloud:cloudRouterP) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +author: +- Cindy Zhao (@cizhao) +options: + tenant: + description: + - The name of tenant. + type: str + required: true + cloud_context_profile: + description: + - The name of cloud context profile. + type: str + required: true + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + choices: [ absent, present, query ] + type: str + default: query +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner +""" + +EXAMPLES = r""" +- name: Enable VpnGateway + cisco.aci.aci_cloud_vpn_gateway: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: ansible_test + cloud_context_profile: ctx_profile_1 + state: present + delegate_to: localhost + +- name: Disable VpnGateway + cisco.aci.aci_cloud_vpn_gateway: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: ansible_test + cloud_context_profile: ctx_profile_1 + state: absent + delegate_to: localhost + +- name: Query VpnGateway + cisco.aci.aci_cloud_vpn_gateway: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: ansible_test + cloud_context_profile: ctx_profile_1 + 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_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 + + +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", required=True), + cloud_context_profile=dict(type="str", required=True), + state=dict(type="str", default="query", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + tenant = module.params.get("tenant") + cloud_context_profile = module.params.get("cloud_context_profile") + state = module.params.get("state") + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict(aci_class="fvTenant", aci_rn="tn-{0}".format(tenant), target_filter='eq(fvTenant.name, "{0}")'.format(tenant), module_object=tenant), + subclass_1=dict( + aci_class="cloudCtxProfile", + aci_rn="ctxprofile-{0}".format(cloud_context_profile), + target_filter='eq(cloudCtxProfile.name, "{0}")'.format(cloud_context_profile), + module_object=cloud_context_profile, + ), + subclass_2=dict(aci_class="cloudRouterP", aci_rn="routerp-default", target_filter='eq(cloudRouterP.name, "default")', module_object="default"), + child_classes=["cloudRsToVpnGwPol", "cloudRsToHostRouterPol", "cloudIntNetworkP"], + ) + + aci.get_existing() + + if state == "present": + child_configs.append(dict(cloudIntNetworkP=dict(attributes=dict(name="default")))) + aci.payload(aci_class="cloudRouterP", class_config=dict(name="default"), child_configs=child_configs) + + aci.get_diff(aci_class="cloudRouterP") + 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_cloud_zone.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_zone.py new file mode 100644 index 000000000..a2a90d869 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_zone.py @@ -0,0 +1,224 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, nkatarmal-crest <nirav.katarmal@crestdatasys.com> +# Copyright: (c) 2020, Cindy Zhao <cizhao@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 + +DOCUMENTATION = r""" +--- +module: aci_cloud_zone +short_description: Manage Cloud Availability Zone (cloud:Zone) +description: +- Manage Cloud Availability Zone on Cisco Cloud ACI. +notes: +- More information about the internal APIC class B(cloud:Zone) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +- This module is used to query Cloud Availability Zone. +author: +- Nirav (@nirav) +- Cindy Zhao (@cizhao) +options: + name: + description: + - object name + aliases: [ zone ] + type: str + cloud: + description: + - The cloud provider. + choices: [ aws, azure ] + type: str + required: true + region: + description: + - The name of the cloud provider's region. + type: str + required: true + state: + description: + - Use C(query) for listing an object or multiple objects. + choices: [ query ] + default: query + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +""" + +EXAMPLES = r""" +- name: Query all zones in a region + cisco.aci.aci_cloud_zone: + host: apic + username: userName + password: somePassword + validate_certs: false + cloud: 'aws' + region: regionName + state: query + delegate_to: localhost + +- name: Query a specific zone + cisco.aci.aci_cloud_zone: + host: apic + username: userName + password: somePassword + validate_certs: false + cloud: 'aws' + region: regionName + zone: zoneName + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + name=dict(type="str", aliases=["zone"]), + cloud=dict(type="str", choices=["aws", "azure"], required=True), + region=dict(type="str", required=True), + state=dict(type="str", default="query", choices=["query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + name = module.params.get("name") + cloud = module.params.get("cloud") + region = module.params.get("region") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="cloudProvP", aci_rn="clouddomp/provp-{0}".format(cloud), target_filter='eq(cloudProvP.vendor, "{0}")'.format(cloud), module_object=cloud + ), + subclass_1=dict( + aci_class="cloudRegion", aci_rn="region-{0}".format(region), target_filter='eq(cloudRegion.name, "{0}")'.format(region), module_object=region + ), + subclass_2=dict(aci_class="cloudZone", aci_rn="zone-{0}".format(name), target_filter='eq(cloudZone.name, "{0}")'.format(name), module_object=name), + child_classes=[], + ) + + aci.get_existing() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_config_export_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_config_export_policy.py new file mode 100644 index 000000000..9b279bf7d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_config_export_policy.py @@ -0,0 +1,312 @@ +#!/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_config_export_policy +short_description: Manage Configuration Export Policy (config:ExportP) +description: +- Manage Configuration Export Policies on Cisco ACI fabrics. +options: + name: + description: + - The name of the Configuration Export Policy. + type: str + description: + description: + - The description of the Configuration Export Policy. + type: str + format: + description: + - The format of the export file. + - This defaults to json on the APIC when unset on creation. + type: str + choices: [ json, xml ] + target_dn: + description: + - The distinguished name of the object to be exported. + - If no target_dn is included, the APIC will export the policy universe. + type: str + snapshot: + description: + - Enables a snapshot of the configuration export policy. + - This defaults to False on the APIC when unset on creation. + type: bool + export_destination: + description: + - The name of the remote path policy used for storing the generated configuration backup data for the configuration export policy. + type: str + scheduler: + description: + - The name of the scheduler policy used for running scheduled export jobs. + type: str + start_now: + description: + - Specifies if the configuration export policy should be applied now or at another time. + - This defaults to False on the APIC when unset on creation. + type: bool + 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(config:ExportP). + link: https://developer.cisco.com/docs/apic-mim-ref/ + +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a Configuration Export Policy + cisco.aci.aci_config_export_policy: + host: apic + username: admin + password: SomeSecretPassword + name: ans_conf_export + state: present + delegate_to: localhost + +- name: Query a Configuration Export Policy + cisco.aci.aci_config_export_policy: + host: apic + username: admin + password: SomeSecretPassword + name: ans_conf_export + state: query + delegate_to: localhost + +- name: Query all Configuration Export Policies + cisco.aci.aci_config_export_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove a Configuration Export Policies + cisco.aci.aci_file_remote_path: + host: apic + username: admin + password: SomeSecretPassword + name: ans_conf_export + 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"), + format=dict(type="str", choices=["json", "xml"]), + target_dn=dict(type="str"), + snapshot=dict(type="bool"), + export_destination=dict(type="str"), + scheduler=dict(type="str"), + start_now=dict(type="bool"), + 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"]], + ["state", "absent", ["name"]], + ], + ) + + aci = ACIModule(module) + + name = module.params.get("name") + description = module.params.get("description") + format = module.params.get("format") + target_dn = module.params.get("target_dn") + snapshot = aci.boolean(module.params.get("snapshot")) + export_destination = module.params.get("export_destination") + scheduler = module.params.get("scheduler") + start_now = aci.boolean(module.params.get("start_now"), "triggered", "untriggered") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="configExportP", + aci_rn="fabric/configexp-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["configRsExportScheduler", "configRsRemotePath"], + ) + aci.get_existing() + + if state == "present": + child_configs = [] + if scheduler is not None: + child_configs.append( + dict( + configRsExportScheduler=dict( + attributes=dict(tnTrigSchedPName=scheduler), + ) + ) + ) + if export_destination is not None: + child_configs.append( + dict( + configRsRemotePath=dict( + attributes=dict(tnFileRemotePathName=export_destination), + ) + ) + ) + aci.payload( + aci_class="configExportP", + class_config=dict( + name=name, + descr=description, + format=format, + targetDn=target_dn, + snapshot=snapshot, + adminSt=start_now, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="configExportP") + + 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_config_rollback.py b/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py new file mode 100644 index 000000000..86ee44d6e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_config_rollback.py @@ -0,0 +1,324 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_config_rollback +short_description: Provides rollback and rollback preview functionality (config:ImportP) +description: +- Provides rollback and rollback preview functionality for Cisco ACI fabrics. +- Config Rollbacks are done using snapshots C(aci_snapshot) with the configImportP class. +options: + compare_export_policy: + description: + - The export policy that the C(compare_snapshot) is associated to. + type: str + compare_snapshot: + description: + - The name of the snapshot to compare with C(snapshot). + type: str + description: + description: + - The description for the Import Policy. + type: str + aliases: [ descr ] + export_policy: + description: + - The export policy that the C(snapshot) is associated to. + type: str + fail_on_decrypt: + description: + - Determines if the APIC should fail the rollback if unable to decrypt secured data. + - The APIC defaults to C(true) when unset. + type: bool + import_mode: + description: + - Determines how the import should be handled by the APIC. + - The APIC defaults to C(atomic) when unset. + type: str + choices: [ atomic, best-effort ] + import_policy: + description: + - The name of the Import Policy to use for config rollback. + type: str + import_type: + description: + - Determines how the current and snapshot configuration should be compared for replacement. + - The APIC defaults to C(replace) when unset. + type: str + choices: [ merge, replace ] + snapshot: + description: + - The name of the snapshot to rollback to, or the base snapshot to use for comparison. + - The C(aci_snapshot) module can be used to query the list of available snapshots. + type: str + required: true + state: + description: + - Use C(preview) for previewing the diff between two snapshots. + - Use C(rollback) for reverting the configuration to a previous snapshot. + type: str + choices: [ preview, rollback ] + default: rollback +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- module: cisco.aci.aci_config_snapshot +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(config:ImportP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +--- +- name: Create a Snapshot + cisco.aci.aci_config_snapshot: + host: apic + username: admin + password: SomeSecretPassword + export_policy: config_backup + state: present + delegate_to: localhost + +- name: Query Existing Snapshots + cisco.aci.aci_config_snapshot: + host: apic + username: admin + password: SomeSecretPassword + export_policy: config_backup + state: query + delegate_to: localhost + +- name: Compare Snapshot Files + cisco.aci.aci_config_rollback: + host: apic + username: admin + password: SomeSecretPassword + export_policy: config_backup + snapshot: run-2017-08-28T06-24-01 + compare_export_policy: config_backup + compare_snapshot: run-2017-08-27T23-43-56 + state: preview + delegate_to: localhost + +- name: Rollback Configuration + cisco.aci.aci_config_rollback: + host: apic + username: admin + password: SomeSecretPassword + import_policy: rollback_config + export_policy: config_backup + snapshot: run-2017-08-28T06-24-01 + state: rollback + delegate_to: localhost + +- name: Rollback Configuration + cisco.aci.aci_config_rollback: + host: apic + username: admin + password: SomeSecretPassword + import_policy: rollback_config + export_policy: config_backup + snapshot: run-2017-08-28T06-24-01 + description: Rollback 8-27 changes + import_mode: atomic + import_type: replace + fail_on_decrypt: true + state: rollback + delegate_to: localhost +""" + +RETURN = r""" +preview: + description: A preview between two snapshots + returned: when state is preview + type: str +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>' +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_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: + import lxml.etree + from xmljson import cobra + + XML_TO_JSON = True +except ImportError: + XML_TO_JSON = False + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + compare_export_policy=dict(type="str"), + compare_snapshot=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + export_policy=dict(type="str"), + fail_on_decrypt=dict(type="bool"), + import_mode=dict(type="str", choices=["atomic", "best-effort"]), + import_policy=dict(type="str"), + import_type=dict(type="str", choices=["merge", "replace"]), + snapshot=dict(type="str", required=True), + state=dict(type="str", default="rollback", choices=["preview", "rollback"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + required_if=[ + ["state", "preview", ["compare_export_policy", "compare_snapshot"]], + ["state", "rollback", ["import_policy"]], + ], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + export_policy = module.params.get("export_policy") + fail_on_decrypt = aci.boolean(module.params.get("fail_on_decrypt")) + import_mode = module.params.get("import_mode") + import_policy = module.params.get("import_policy") + import_type = module.params.get("import_type") + snapshot = module.params.get("snapshot") + state = module.params.get("state") + + if state == "rollback": + if snapshot.startswith("run-"): + snapshot = snapshot.replace("run-", "", 1) + + if not snapshot.endswith(".tar.gz"): + snapshot += ".tar.gz" + + filename = "ce2_{0}-{1}".format(export_policy, snapshot) + + aci.construct_url( + root_class=dict( + aci_class="configImportP", + aci_rn="fabric/configimp-{0}".format(import_policy), + module_object=import_policy, + target_filter={"name": import_policy}, + ), + ) + + aci.get_existing() + + aci.payload( + aci_class="configImportP", + class_config=dict( + adminSt="triggered", + descr=description, + failOnDecryptErrors=fail_on_decrypt, + fileName=filename, + importMode=import_mode, + importType=import_type, + name=import_policy, + snapshot="yes", + ), + ) + + aci.get_diff(aci_class="configImportP") + + aci.post_config() + + elif state == "preview": + aci.url = "%(protocol)s://%(host)s/mqapi2/snapshots.diff.xml" % module.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 + + # Generate rollback comparison + get_preview(aci) + + aci.exit_json() + + +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") + + # Handle APIC response + if info.get("status") == 200: + xml_to_json(aci, resp.read()) + else: + aci.result["raw"] = resp.read() + aci.fail_json(msg="Request failed: %(code)s %(text)s (see 'raw' output)" % aci.error) + + +def xml_to_json(aci, response_data): + """ + This function is used to convert preview XML data into JSON. + """ + if XML_TO_JSON: + xml = lxml.etree.fromstring(to_bytes(response_data)) + xmldata = cobra.data(xml) + aci.result["preview"] = xmldata + else: + aci.result["preview"] = response_data + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py b/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py new file mode 100644 index 000000000..020b456b4 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py @@ -0,0 +1,348 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_config_snapshot +short_description: Manage Config Snapshots (config:Snapshot, config:ExportP) +description: +- Manage Config Snapshots on Cisco ACI fabrics. +- Creating new Snapshots is done using the configExportP class. +- Removing Snapshots is done using the configSnapshot class. +options: + description: + description: + - The description for the Config Export Policy. + type: str + aliases: [ descr ] + export_policy: + description: + - The name of the Export Policy to use for Config Snapshots. + type: str + aliases: [ name ] + format: + description: + - Sets the config backup to be formatted in JSON or XML. + - The APIC defaults to C(json) when unset. + type: str + choices: [ json, xml ] + include_secure: + description: + - Determines if secure information should be included in the backup. + - The APIC defaults to C(true) when unset. + type: bool + max_count: + description: + - Determines how many snapshots can exist for the Export Policy before the APIC starts to rollover. + - Accepted values range between C(1) and C(10). + - The APIC defaults to C(3) when unset. + type: int + snapshot: + description: + - The name of the snapshot to delete. + 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 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>.' +seealso: +- module: cisco.aci.aci_config_rollback +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(config:Snapshot) and B(config:ExportP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Create a Snapshot + cisco.aci.aci_config_snapshot: + host: apic + username: admin + password: SomeSecretPassword + state: present + export_policy: config_backup + max_count: 10 + description: Backups taken before new configs are applied. + delegate_to: localhost + +- name: Query all Snapshots + cisco.aci.aci_config_snapshot: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query Snapshots associated with a particular Export Policy + cisco.aci.aci_config_snapshot: + host: apic + username: admin + password: SomeSecretPassword + export_policy: config_backup + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Snapshot + cisco.aci.aci_config_snapshot: + host: apic + username: admin + password: SomeSecretPassword + export_policy: config_backup + snapshot: run-2017-08-24T17-20-05 + 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( + description=dict(type="str", aliases=["descr"]), + export_policy=dict(type="str", aliases=["name"]), # Not required for querying all objects + format=dict(type="str", choices=["json", "xml"]), + include_secure=dict(type="bool"), + max_count=dict(type="int"), + snapshot=dict(type="str"), + state=dict(type="str", choices=["absent", "present", "query"], default="present"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + required_if=[ + ["state", "absent", ["export_policy", "snapshot"]], + ["state", "present", ["export_policy"]], + ], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + export_policy = module.params.get("export_policy") + file_format = module.params.get("format") + include_secure = aci.boolean(module.params.get("include_secure")) + max_count = module.params.get("max_count") + if max_count is not None: + if max_count in range(1, 11): + max_count = str(max_count) + else: + module.fail_json(msg="Parameter 'max_count' must be a number between 1 and 10") + snapshot = module.params.get("snapshot") + if snapshot is not None and not snapshot.startswith("run-"): + snapshot = "run-" + snapshot + state = module.params.get("state") + + if state == "present": + aci.construct_url( + root_class=dict( + aci_class="configExportP", + aci_rn="fabric/configexp-{0}".format(export_policy), + module_object=export_policy, + 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( + aci_class="configExportP", + class_config=dict( + adminSt="triggered", + descr=description, + format=file_format, + includeSecureFields=include_secure, + maxSnapshotCount=max_count, + name=export_policy, + snapshot="yes", + ), + ) + + aci.get_diff("configExportP") + + # Create a new Snapshot + 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" + + else: + # Prefix the proper url to export_policy + if export_policy is not None: + export_policy = "uni/fabric/configexp-{0}".format(export_policy) + + aci.construct_url( + root_class=dict( + aci_class="configSnapshotCont", + aci_rn="backupst/snapshots-[{0}]".format(export_policy), + module_object=export_policy, + target_filter={"name": export_policy}, + ), + subclass_1=dict( + aci_class="configSnapshot", + aci_rn="snapshot-{0}".format(snapshot), + module_object=snapshot, + target_filter={"name": snapshot}, + ), + ) + + aci.get_existing() + + if state == "absent": + # Build POST request to used to remove Snapshot + aci.payload( + aci_class="configSnapshot", + class_config=dict( + name=snapshot, + retire="yes", + ), + ) + + if aci.existing: + aci.get_diff("configSnapshot") + + # Mark Snapshot for Deletion + aci.post_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_contract.py b/ansible_collections/cisco/aci/plugins/modules/aci_contract.py new file mode 100644 index 000000000..55803556a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_contract.py @@ -0,0 +1,340 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_contract +short_description: Manage contract resources (vz:BrCP) +description: +- Manage Contract resources on Cisco ACI fabrics. +options: + contract: + description: + - The name of the contract. + type: str + aliases: [ contract_name, name ] + description: + description: + - Description for the contract. + type: str + aliases: [ descr ] + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + scope: + description: + - The scope of a service contract. + - The APIC defaults to C(context) when unset during creation. + type: str + choices: [ application-profile, context, global, tenant ] + priority: + description: + - The desired QoS class to be used. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, unspecified ] + dscp: + description: + - The target Differentiated Service (DSCP) value. + - The APIC defaults to C(unspecified) when unset during creation. + 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 ] + 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: +- This module does not manage Contract Subjects, see M(cisco.aci.aci_contract_subject) to do this. + Contract Subjects can still be removed using this module. +- 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_contract_subject +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vz:BrCP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add a new contract + cisco.aci.aci_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + description: Communication between web-servers and database + scope: application-profile + state: present + delegate_to: localhost + +- name: Remove an existing contract + cisco.aci.aci_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + state: absent + delegate_to: localhost + +- name: Query a specific contract + cisco.aci.aci_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + state: query + delegate_to: localhost + register: query_result + +- name: Query all contracts + cisco.aci.aci_contract: + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + contract=dict(type="str", aliases=["contract_name", "name"]), # Not required for querying all objects + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + scope=dict(type="str", choices=["application-profile", "context", "global", "tenant"]), + priority=dict(type="str", choices=["level1", "level2", "level3", "unspecified"]), # No default provided on purpose + dscp=dict( + 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"], + ), # No default provided on purpose + 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", ["contract", "tenant"]], + ["state", "present", ["contract", "tenant"]], + ], + ) + + contract = module.params.get("contract") + description = module.params.get("description") + scope = module.params.get("scope") + priority = module.params.get("priority") + dscp = module.params.get("dscp") + 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="vzBrCP", + aci_rn="brc-{0}".format(contract), + module_object=contract, + target_filter={"name": contract}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vzBrCP", + class_config=dict( + name=contract, + descr=description, + scope=scope, + prio=priority, + targetDscp=dscp, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="vzBrCP") + + 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_contract_export.py b/ansible_collections/cisco/aci/plugins/modules/aci_contract_export.py new file mode 100644 index 000000000..0f2e99059 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_contract_export.py @@ -0,0 +1,287 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_contract_export +short_description: Manage contract interfaces (vz:CPIf) +description: +- Manage Contract interfaces on Cisco ACI fabrics. +options: + name: + description: + - The name of the contract interface. + type: str + aliases: [ interface_name ] + destination_tenant: + description: + - The The tenant associated with the contract interface. + type: str + description: + description: + - Description for the contract interface. + type: str + aliases: [ descr ] + tenant: + description: + - The name of the tenant hosting the contract to export. + type: str + aliases: [ tenant_name ] + contract: + description: + - The name of the contract to export. + type: str + aliases: [ contract_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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- module: cisco.aci.aci_contract +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vz:BrCP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Create a new contract interface + cisco.aci.aci_contract_export: + host: apic + username: admin + password: SomeSecretPassword + name: contractintf + destination_tenant: tndest + tenant: tnsrc + contract: web_to_db + state: present + delegate_to: localhost + +- name: Remove an existing contract interface + cisco.aci.aci_contract_export: + host: apic + username: admin + password: SomeSecretPassword + name: contractintf + destination_tenant: tndest + tenant: tnsrc + contract: web_to_db + state: absent + delegate_to: localhost + +- name: Query a specific contract interface + cisco.aci.aci_contract_export: + host: apic + username: admin + password: SomeSecretPassword + name: contractintf + destination_tenant: tndest + tenant: tnsrc + contract: web_to_db + state: query + delegate_to: localhost + register: query_result + +- name: Query all contract interfaces + cisco.aci.aci_contract_export: + 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_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 + + +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=["interface_name"]), + destination_tenant=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + tenant=dict(type="str", aliases=["tenant_name"]), + contract=dict(type="str", aliases=["contract_name"]), + 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", "destination_tenant", "tenant", "contract"]], + ["state", "present", ["name", "destination_tenant", "tenant", "contract"]], + ], + ) + + name = module.params.get("name") + destination_tenant = module.params.get("destination_tenant") + description = module.params.get("description") + tenant = module.params.get("tenant") + contract = module.params.get("contract") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(destination_tenant), + module_object=destination_tenant, + target_filter={"name": destination_tenant}, + ), + subclass_1=dict( + aci_class="vzCPIf", + aci_rn="cif-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["vzRsIf"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [dict(vzRsIf=dict(attributes=dict(tDn="uni/tn-{0}/brc-{1}".format(tenant, contract))))] + + aci.payload(aci_class="vzCPIf", class_config=dict(name=name, descr=description), child_configs=child_configs) + + aci.get_diff(aci_class="vzCPIf") + + 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_contract_subject.py b/ansible_collections/cisco/aci/plugins/modules/aci_contract_subject.py new file mode 100644 index 000000000..8c68cb3ab --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_contract_subject.py @@ -0,0 +1,440 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_contract_subject +short_description: Manage initial Contract Subjects (vz:Subj) +description: +- Manage initial Contract Subjects on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + subject: + description: + - The contract subject name. + type: str + aliases: [ contract_subject, name, subject_name ] + apply_both_direction: + description: + - The direction of traffic matching for the filter. + type: str + default: both + choices: [ both, one-way ] + contract: + description: + - The name of the Contract. + type: str + aliases: [ contract_name ] + reverse_filter: + description: + - Determines if the APIC should reverse the src and dst ports to allow the + return traffic back, since ACI is stateless filter. + - The APIC defaults to C(true) when unset during creation. + type: bool + priority: + description: + - The QoS class. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, unspecified ] + dscp: + description: + - The target DSCP. + - The APIC defaults to C(unspecified) when unset during creation. + 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 ] + priority_consumer_to_provider: + description: + - The QoS class of Filter Chain For Consumer to Provider. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, unspecified ] + dscp_consumer_to_provider: + description: + - The target DSCP of Filter Chain For Consumer to Provider. + - The APIC defaults to C(unspecified) when unset during creation. + 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_consumer_to_provider ] + priority_provider_to_consumer: + description: + - The QoS class of Filter Chain For Provider to Consumer. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, unspecified ] + dscp_provider_to_consumer: + description: + - The target DSCP of Filter Chain For Provider to Consumer. + - The APIC defaults to C(unspecified) when unset during creation. + 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_provider_to_consumer ] + description: + description: + - Description for the contract subject. + type: str + aliases: [ descr ] + consumer_match: + description: + - The match criteria across consumers. + - The APIC defaults to C(at_least_one) when unset during creation. + type: str + choices: [ all, at_least_one, at_most_one, none ] + provider_match: + description: + - The match criteria across providers. + - The APIC defaults to C(at_least_one) when unset during creation. + type: str + choices: [ all, at_least_one, at_most_one, none ] + 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(tenant) and C(contract) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_contract) modules can be used for this. +seealso: +- module: cisco.aci.aci_contract +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vz:Subj). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Swetha Chunduri (@schunduri) +""" + +EXAMPLES = r""" +- name: Add a new contract subject + cisco.aci.aci_contract_subject: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: default + description: test + reverse_filter: true + priority: level1 + dscp: unspecified + state: present + register: query_result + +- name: Remove a contract subject + cisco.aci.aci_contract_subject: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: default + state: absent + delegate_to: localhost + +- name: Query a contract subject + cisco.aci.aci_contract_subject: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: default + state: query + delegate_to: localhost + register: query_result + +- name: Query all contract subjects + cisco.aci.aci_contract_subject: + 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_contract_dscp_spec, + aci_contract_qos_spec, +) + +MATCH_MAPPING = dict( + all="All", + at_least_one="AtleastOne", + at_most_one="AtmostOne", + none="None", +) + + +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 + contract=dict(type="str", aliases=["contract_name"]), # Not required for querying all objects + subject=dict(type="str", aliases=["contract_subject", "name", "subject_name"]), # Not required for querying all objects + reverse_filter=dict(type="bool"), + description=dict(type="str", aliases=["descr"]), + consumer_match=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]), + provider_match=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + # default both because of back-worth compatibility and for determining which config to push + apply_both_direction=dict(type="str", default="both", choices=["both", "one-way"]), + priority=aci_contract_qos_spec(), + dscp=aci_contract_dscp_spec(), + priority_consumer_to_provider=aci_contract_qos_spec(), + dscp_consumer_to_provider=aci_contract_dscp_spec("consumer_to_provider"), + priority_provider_to_consumer=aci_contract_qos_spec(), + dscp_provider_to_consumer=aci_contract_dscp_spec("provider_to_consumer"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["contract", "subject", "tenant"]], + ["state", "present", ["contract", "subject", "tenant"]], + ], + ) + + aci = ACIModule(module) + + subject = module.params.get("subject") + priority = module.params.get("priority") + dscp = module.params.get("dscp") + priority_consumer_to_provider = module.params.get("priority_consumer_to_provider") + dscp_consumer_to_provider = module.params.get("dscp_consumer_to_provider") + priority_provider_to_consumer = module.params.get("priority_provider_to_consumer") + dscp_provider_to_consumer = module.params.get("dscp_provider_to_consumer") + reverse_filter = aci.boolean(module.params.get("reverse_filter")) + contract = module.params.get("contract") + description = module.params.get("description") + consumer_match = module.params.get("consumer_match") + if consumer_match is not None: + consumer_match = MATCH_MAPPING.get(consumer_match) + provider_match = module.params.get("provider_match") + if provider_match is not None: + provider_match = MATCH_MAPPING.get(provider_match) + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + direction = module.params.get("apply_both_direction") + + subject_class = "vzSubj" + base_subject_dict = dict( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vzBrCP", + aci_rn="brc-{0}".format(contract), + module_object=contract, + target_filter={"name": contract}, + ), + subclass_2=dict( + aci_class=subject_class, + aci_rn="subj-{0}".format(subject), + module_object=subject, + target_filter={"name": subject}, + ), + ) + + # start logic to be consistent with GUI to only allow both direction or one-way + aci.construct_url( + root_class=base_subject_dict.get("root_class"), + subclass_1=base_subject_dict.get("subclass_1"), + subclass_2=base_subject_dict.get("subclass_2"), + child_classes=["vzInTerm", "vzOutTerm"], + ) + aci.get_existing() + direction_options = ["both", "one-way"] + if state != "query": + if aci.existing and subject_class in aci.existing[0]: + direction_options = ["one-way"] if "children" in aci.existing[0][subject_class] else ["both"] + if direction not in direction_options: + module.fail_json(msg="Direction is not allowed, valid option is {0}.".format(" or ".join(direction_options))) + # end logic to be consistent with GUI to only allow both direction or one-way + + if state == "present": + config = dict( + name=subject, + prio=priority, + revFltPorts=reverse_filter, + targetDscp=dscp, + consMatchT=consumer_match, + provMatchT=provider_match, + descr=description, + nameAlias=name_alias, + ) + + child_configs = [] + if direction == "one-way" and ( + len(direction_options) == 2 + or dscp_consumer_to_provider is not None + or priority_consumer_to_provider is not None + or dscp_provider_to_consumer is not None + or priority_provider_to_consumer is not None + ): + subj_dn = "uni/tn-{0}/brc-{1}/subj-{2}".format(tenant, contract, subject) + child_configs = [ + dict( + vzInTerm=dict(attributes=dict(dn="{0}/intmnl".format(subj_dn), targetDscp=dscp_consumer_to_provider, prio=priority_consumer_to_provider)) + ), + dict( + vzOutTerm=dict(attributes=dict(dn="{0}/outtmnl".format(subj_dn), targetDscp=dscp_provider_to_consumer, prio=priority_provider_to_consumer)) + ), + ] + + aci.payload(aci_class=subject_class, class_config=config, child_configs=child_configs) + + aci.get_diff(aci_class=subject_class) + + 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_contract_subject_to_filter.py b/ansible_collections/cisco/aci/plugins/modules/aci_contract_subject_to_filter.py new file mode 100644 index 000000000..f03c1b258 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_contract_subject_to_filter.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_contract_subject_to_filter +short_description: Bind Contract Subjects to Filters (vz:RsSubjFiltAtt) +description: +- Bind Contract Subjects to Filters on Cisco ACI fabrics. +options: + contract: + description: + - The name of the contract. + type: str + aliases: [ contract_name ] + filter: + description: + - The name of the Filter to bind to the Subject. + type: str + aliases: [ filter_name ] + direction: + description: + - The direction of traffic matching for the filter. + type: str + default: both + choices: [ both, consumer_to_provider, provider_to_consumer ] + action: + description: + - The action required when the condition is met. + - The APIC defaults to C(permit) when unset during creation. + type: str + choices: [ deny, permit ] + priority_override: + description: + - Overrides the filter priority for the a single applied filter. + type: str + choices: [ default, level1, level2, level3 ] + directives: + description: + - Determines if the binding should be set to log. + - The APIC defaults to C(none) when unset during creation. + type: str + choices: [ log, no_stats, none ] + aliases: [ log, directive] + subject: + description: + - The name of the Contract Subject. + type: str + aliases: [ contract_subject, subject_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 + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant), C(contract), C(subject), and C(filter_name) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_contract), M(cisco.aci.aci_contract_subject), and M(cisco.aci.aci_filter) modules can be used for these. +seealso: +- module: cisco.aci.aci_contract_subject +- module: cisco.aci.aci_filter +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vz:RsSubjFiltAtt). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Add a new contract subject to filer binding + cisco.aci.aci_contract_subject_to_filter: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: test + filter: '{{ filter }}' + log: '{{ log }}' + state: present + delegate_to: localhost + +- name: Remove an existing contract subject to filter binding + cisco.aci.aci_contract_subject_to_filter: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: test + filter: '{{ filter }}' + log: '{{ log }}' + state: present + delegate_to: localhost + +- name: Query a specific contract subject to filter binding + cisco.aci.aci_contract_subject_to_filter: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: test + filter: '{{ filter }}' + state: query + delegate_to: localhost + register: query_result + +- name: Query all contract subject to filter bindings + cisco.aci.aci_contract_subject_to_filter: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: test + 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( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + contract=dict(type="str", aliases=["contract_name"]), # Not required for querying all objects + filter=dict(type="str", aliases=["filter_name"]), # Not required for querying all objects + subject=dict(type="str", aliases=["contract_subject", "subject_name"]), # Not required for querying all objects + # default both because of back-worth compatibility and for determining which config to push + direction=dict(type="str", default="both", choices=["both", "consumer_to_provider", "provider_to_consumer"]), + action=dict(type="str", choices=["deny", "permit"]), + # named directives instead of log/directive for readability of code, aliases and input "none are kept for back-worth compatibility + directives=dict(type="str", choices=["log", "no_stats", "none"], aliases=["log", "directive"]), + priority_override=dict(type="str", choices=["default", "level1", "level2", "level3"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["contract", "filter", "subject", "tenant"]], + ["state", "present", ["contract", "filter", "subject", "tenant"]], + ], + ) + + contract = module.params.get("contract") + filter_name = module.params.get("filter") + # "none" is kept because of back-worth compatibility, could be deleted and keep only None + directives = "" if (module.params.get("directives") is None or module.params.get("directives") == "none") else module.params.get("directives") + subject = module.params.get("subject") + direction = module.params.get("direction") + action = module.params.get("action") + priority_override = module.params.get("priority_override") + tenant = module.params.get("tenant") + state = module.params.get("state") + + base_subject_dict = dict( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="vzBrCP", + aci_rn="brc-{0}".format(contract), + module_object=contract, + target_filter={"name": contract}, + ), + subclass_2=dict( + aci_class="vzSubj", + aci_rn="subj-{0}".format(subject), + module_object=subject, + target_filter={"name": subject}, + ), + ) + + aci = ACIModule(module) + + # start logic to be consistent with GUI to only allow both direction or a one-way connection + aci.construct_url( + root_class=base_subject_dict.get("root_class"), + subclass_1=base_subject_dict.get("subclass_1"), + subclass_2=base_subject_dict.get("subclass_2"), + child_classes=["vzInTerm", "vzOutTerm"], + ) + aci.get_existing() + direction_options = ["both"] + if aci.existing: + direction_options = ["consumer_to_provider", "provider_to_consumer"] if "children" in aci.existing[0]["vzSubj"] else ["both"] + + if state != "query" and direction not in direction_options: + module.fail_json(msg="Direction is not allowed, valid option is {0}.".format(" or ".join(direction_options))) + # end logic to be consistent with GUI to only allow both direction or a one-way connection + + if direction == "both": + filter_class = "vzRsSubjFiltAtt" + # dict unpacking with **base_subject_dict raises syntax error in python2.7 thus dict lookup + aci.construct_url( + root_class=base_subject_dict.get("root_class"), + subclass_1=base_subject_dict.get("subclass_1"), + subclass_2=base_subject_dict.get("subclass_2"), + subclass_3=dict( + aci_class=filter_class, + aci_rn="rssubjFiltAtt-{0}".format(filter_name), + module_object=filter_name, + target_filter=dict(tnVzFilterName=filter_name), + ), + ) + else: + term_class, term = ("vzInTerm", "intmnl") if direction == "consumer_to_provider" else ("vzOutTerm", "outtmnl") + filter_class = "vzRsFiltAtt" + # dict unpacking with **base_subject_dict raises syntax error in python2.7 thus dict lookup + aci.construct_url( + root_class=base_subject_dict.get("root_class"), + subclass_1=base_subject_dict.get("subclass_1"), + subclass_2=base_subject_dict.get("subclass_2"), + subclass_3=dict(aci_class=term_class, aci_rn=term), + child_classes=[filter_class], + ) + + aci.get_existing() + + if state == "present": + config = dict(tnVzFilterName=filter_name, directives=directives, action=action, priorityOverride=priority_override) + if direction == "both": + aci.payload(aci_class=filter_class, class_config=config) + aci.get_diff(aci_class=filter_class) + else: + child_config = [dict(vzRsFiltAtt=dict(attributes=config))] + aci.payload(aci_class=term_class, class_config=dict(), child_configs=child_config) + aci.get_diff(aci_class=term_class) + aci.post_config() + + elif state == "absent": + aci.delete_config() + + if direction == "both": + aci.exit_json() + else: + # filter the output of current/previous to tnVzFilterName only since existing consist full vzInTerm/vzOutTerm + def filter_result(input_list, name): + return [ + {key: filter_entry} + for entry in input_list + if "children" in entry[term_class] + for children in entry[term_class]["children"] + for key, filter_entry in children.items() + if filter_entry["attributes"]["tnVzFilterName"] == name + ] + + # pass function to + filter_existing = (filter_result, filter_name) + aci.exit_json(filter_existing) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_contract_subject_to_service_graph.py b/ansible_collections/cisco/aci/plugins/modules/aci_contract_subject_to_service_graph.py new file mode 100644 index 000000000..eb8f9fa32 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_contract_subject_to_service_graph.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_contract_subject_to_service_graph +short_description: Bind contract subject to service graph (vz:RsSubjGraphAtt). +description: +- Bind contract subject to service graph. +options: + tenant: + description: + - The name of an existing tenant. + type: str + contract: + description: + - The name of the contract. + type: str + subject: + description: + - The contract subject name. + type: str + service_graph: + description: + - The service graph name. + 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 + +author: +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Add a new contract subject to service graph binding + cisco.aci.aci_contract_subject_to_service_graph: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: test + service_graph: '{{ service_graph }}' + state: present + delegate_to: localhost +- name: Remove an existing contract subject to service graph binding + cisco.aci.aci_contract_subject_to_service_graph: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: test + service_graph: '{{ service_graph }}' + state: absent + delegate_to: localhost +- name: Query a specific contract subject to service graph binding + cisco.aci.aci_contract_subject_to_service_graph: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web_to_db + subject: test + service_graph: '{{ service_graph }}' + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str"), + contract=dict(type="str"), + subject=dict(type="str"), + service_graph=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", ["contract", "service_graph", "subject", "tenant"]], + ["state", "present", ["contract", "service_graph", "subject", "tenant"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + contract = module.params.get("contract") + subject = module.params.get("subject") + service_graph = module.params.get("service_graph") + state = module.params.get("state") + + 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="vzBrCP", aci_rn="brc-{0}".format(contract), module_object=contract, target_filter={"name": contract}), + subclass_2=dict(aci_class="vzSubj", aci_rn="subj-{0}".format(subject), module_object=subject, target_filter={"name": subject}), + subclass_3=dict(aci_class="vzRsSubjGraphAtt", aci_rn="rsSubjGraphAtt", module_object=service_graph, target_filter={"name": service_graph}), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="vzRsSubjGraphAtt", class_config=dict(tnVnsAbsGraphName=service_graph)) + + aci.get_diff(aci_class="vzRsSubjGraphAtt") + + 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_dhcp_relay.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py new file mode 100644 index 000000000..a1dfc32e9 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py @@ -0,0 +1,269 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_dhcp_relay +short_description: Manage DHCP relay policies. +description: +- Manage DHCP relay policy (dhcpRelayP) configuration on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + name: + description: + - Name of the DHCP relay policy + type: str + aliases: [ relay_policy ] + description: + description: + - Description of the relay policy + 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 C(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(dhcpRelayP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new DHCP relay policy + cisco.aci.aci_dhcp_relay: + host: apic + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + 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 + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + name: my_dhcp_relay + state: absent + delegate_to: localhost + +- name: Query a DHCP relay policy + cisco.aci.aci_dhcp_relay: + host: apic + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + 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 + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + 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 "/></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", aliases=["relay_policy"]), + description=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "tenant"]], + ["state", "present", ["name", "tenant"]], + ], + ) + + name = module.params.get("name") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + child_classes = ["dhcpRsProv"] + + 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="dhcpRelayP", + aci_rn="relayp-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="dhcpRelayP", + class_config=dict(name=name, descr=description, owner="tenant"), + ) + + aci.get_diff(aci_class="dhcpRelayP") + + 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_dhcp_relay_provider.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py new file mode 100644 index 000000000..fefebb6ad --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py @@ -0,0 +1,375 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_dhcp_relay_provider +short_description: Manage DHCP relay policy providers. +description: +- Manage DHCP relay policy providers (dhcpRsProv) configuration on Cisco ACI fabrics. +options: + tenant: + description: + - Name of the tenant the relay_policy is in. + type: str + relay_policy: + description: + - Name of an existing DHCP relay policy + type: str + aliases: [ relay_policy_name ] + 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 + 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. + - Only used when epg_type is app_epg. + type: str + epg: + description: + - Name of the Application EPG the DHCP server is in. + - Only used when epg_type is epg + type: str + aliases: [ app_epg ] + l2out_name: + description: + - Name of the L2out the DHCP server is in. + - Only used when epg_type is l2_external + type: str + l3out_name: + description: + - Name of the L3out the DHCP server is in. + - Only used when epg_type is l3_external. + type: str + external_epg: + description: + - Name of the external network object the DHCP server is in. + - Only used when epg_type is l2_external or l3_external. + type: str + aliases: [ external_net ] + dn: + description: + - dn of the EPG the DHCP server is in + - Only used when epg_type is dn + type: str + dhcp_server_addr: + description: + - DHCP server address + 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 C(tenant) and C(relay_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and C(cisco.aci.aci_dhcp_relay) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(dhcpRsProv). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new DHCP relay App EPG provider + cisco.aci.aci_dhcp_relay_provider: + host: apic + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + relay_policy: my_dhcp_relay + 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 + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + relay_policy: my_dhcp_relay + epg_type: l3_external + l3out_name: my_l3out + external_net: my_l3out_ext_net + dhcp_server_addr: 10.20.30.40 + state: present + delegate_to: localhost + +- name: Remove a DHCP relay provider + cisco.aci.aci_dhcp_relay_provider: + host: apic + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + relay_policy: my_dhcp_relay + 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 + username: admin + password: SomeSecretPassword + tenant: Auto-Demo + relay_policy: my_dhcp_relay + epg_type: epg + anp: my_anp + epg: my_app_epg + 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 "/></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( + relay_policy=dict(type="str", aliases=["relay_policy_name"]), + epg_type=dict(type="str", required=True, choices=["epg", "l2_external", "l3_external", "dn"]), + anp=dict(type="str"), + epg=dict(type="str", aliases=["app_epg"]), + l2out_name=dict(type="str"), + l3out_name=dict(type="str"), + external_epg=dict(type="str", aliases=["external_net"]), + dhcp_server_addr=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str"), + provider_tenant=dict(type="str"), + dn=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["relay_policy", "tenant"]], + ["state", "present", ["relay_policy", "tenant"]], + ["epg_type", "epg", ["anp", "epg"]], + ["epg_type", "l2_external", ["l2out_name", "external_epg"]], + ["epg_type", "l3_external", ["l3out_name", "external_epg"]], + ["epg_type", "dn", ["dn"]], + ], + mutually_exclusive=[ + ["anp", "l2out_name"], + ["anp", "l3out_name"], + ["anp", "external_epg"], + ["anp", "dn"], + ["epg", "l2out_name"], + ["epg", "l3out_name"], + ["epg", "external_epg"], + ["epg", "dn"], + ["l2out_name", "l3out_name"], + ["l2out_name", "dn"], + ["l3out_name", "dn"], + ["external_epg", "dn"], + ], + ) + + relay_policy = module.params.get("relay_policy") + state = module.params.get("state") + tenant = module.params.get("tenant") + epg_type = module.params.get("epg_type") + anp = module.params.get("anp") + epg = module.params.get("epg") + l2out_name = module.params.get("l2out_name") + l3out_name = module.params.get("l3out_name") + external_epg = module.params.get("external_epg") + dhcp_server_addr = module.params.get("dhcp_server_addr") + provider_tenant = module.params.get("provider_tenant") + dn = module.params.get("dn") + + if provider_tenant is None: + provider_tenant = tenant + + if epg_type == "epg": + tdn = "uni/tn-{0}/ap-{1}/epg-{2}".format(provider_tenant, anp, epg) + elif epg_type == "l2_external": + tdn = "uni/tn-{0}/l2out-{1}/instP-{2}".format(provider_tenant, l2out_name, external_epg) + elif epg_type == "l3_external": + tdn = "uni/tn-{0}/out-{1}/instP-{2}".format(provider_tenant, l3out_name, external_epg) + elif epg_type == "dn": + tdn = dn + + 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="dhcpRelayP", + aci_rn="relayp-{0}".format(relay_policy), + module_object=relay_policy, + target_filter={"name": relay_policy}, + ), + subclass_2=dict( + aci_class="dhcpRsProv", + aci_rn="rsprov-[{0}]".format(tdn), + module_object=tdn, + target_filter={"tDn": tdn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="dhcpRsProv", + class_config=dict(addr=dhcp_server_addr, tDn=tdn), + ) + + aci.get_diff(aci_class="dhcpRsProv") + + 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_dns_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_dns_domain.py new file mode 100644 index 000000000..4325c7513 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dns_domain.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_dns_domain +short_description: Manage DNS Provider (dnsDomain) objects. +description: +- Manage DNS Domain configuration on Cisco ACI fabrics. +options: + dns_profile: + description: + - Name of the DNS profile. + type: str + aliases: [ profile_name ] + required: true + domain: + description: + - DNS domain name + type: str + aliases: [ name, domain_name ] + default: + description: + - Whether this is the default domain + type: bool + 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 C(dns_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_dns_profile) modules can be used for this. + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(dnsDomain). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new DNS domain + cisco.aci.aci_dns_domain: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + domain: example.com + state: present + delegate_to: localhost + +- name: Remove a DNS domain + cisco.aci.aci_dns_domain: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + domain: example.com + state: absent + delegate_to: localhost + +- name: Query a DNS domain + cisco.aci.aci_dns_domain: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + domain: example.com + state: query + delegate_to: localhost + register: query_result + +- name: Query all DNS domains within a DNS profile + cisco.aci.aci_dns_domain: + 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 "/></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( + dns_profile=dict(type="str", aliases=["profile_name"], required=True), + domain=dict(type="str", aliases=["name", "domain_name"]), + default=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["domain"]], + ["state", "present", ["domain"]], + ], + ) + + aci = ACIModule(module) + + dns_profile = module.params.get("dns_profile") + domain = module.params.get("domain") + default = aci.boolean(module.params.get("default")) + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="dnsProfile", + aci_rn="fabric/dnsp-{0}".format(dns_profile), + module_object=dns_profile, + target_filter={"name": dns_profile}, + ), + subclass_1=dict(aci_class="dnsDomain", aci_rn="dom-{0}".format(domain), module_object=domain, target_filter={"name": domain}), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="dnsDomain", + class_config=dict(name=domain, isDefault=default), + ) + + aci.get_diff(aci_class="dnsDomain") + + 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_dns_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_dns_profile.py new file mode 100644 index 000000000..47efb62c4 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dns_profile.py @@ -0,0 +1,243 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_dns_profile +short_description: Manage DNS Profile (dnsProfile) objects. +description: +- Manage DNS Profile configuration on Cisco ACI fabrics. +options: + dns_profile: + description: + - Name of the DNS profile. + type: str + aliases: [ name, profile_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 +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(dnsProfile). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new DNS profile + cisco.aci.aci_dns_profile: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + state: present + delegate_to: localhost + +- name: Remove a DNS profile + cisco.aci.aci_dns_profile: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + state: absent + delegate_to: localhost + +- name: Query a DNS profile + cisco.aci.aci_dns_profile: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + state: query + delegate_to: localhost + register: query_result + +- name: Query all DNS profiles + cisco.aci.aci_dns_profile: + 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 "/></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( + dns_profile=dict(type="str", aliases=["name", "profile_name"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["dns_profile"]], + ["state", "present", ["dns_profile"]], + ], + ) + + dns_profile = module.params.get("dns_profile") + state = module.params.get("state") + child_classes = ["dnsProv", "dnsDomain"] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="dnsProfile", + aci_rn="fabric/dnsp-{0}".format(dns_profile), + module_object=dns_profile, + target_filter={"name": dns_profile}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="dnsProfile", + class_config=dict(name=dns_profile), + ) + + aci.get_diff(aci_class="dnsProfile") + + 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_dns_provider.py b/ansible_collections/cisco/aci/plugins/modules/aci_dns_provider.py new file mode 100644 index 000000000..1fe2c2e60 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dns_provider.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_dns_provider +short_description: Manage DNS Provider (dnsProv) objects. +description: +- Manage DNS Provider configuration on Cisco ACI fabrics. +options: + dns_profile: + description: + - Name of the DNS profile. + type: str + aliases: [ profile_name ] + required: true + address: + description: + - address of the DNS server + type: str + aliases: [ addr, ip_address ] + preferred: + description: + - Whether this is the preferred DNS server + type: bool + 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 C(dns_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_dns_profile) modules can be used for this. + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(dnsProv). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new DNS provider + cisco.aci.aci_dns_provider: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + address: 10.20.30.40 + state: present + delegate_to: localhost + +- name: Remove a DNS provider + cisco.aci.aci_dns_profile: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + address: 10.20.30.40 + state: absent + delegate_to: localhost + +- name: Query a DNS provider + cisco.aci.aci_dns_profile: + host: apic + username: admin + password: SomeSecretPassword + dns_profile: my_dns_prof + address: 10.20.30.40 + state: query + delegate_to: localhost + register: query_result + +- name: Query all DNS providers within a DNS profile + cisco.aci.aci_dns_profile: + 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 "/></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( + dns_profile=dict(type="str", aliases=["profile_name"], required=True), + address=dict(type="str", aliases=["addr", "ip_address"]), + preferred=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["address"]], + ["state", "present", ["address"]], + ], + ) + + aci = ACIModule(module) + + dns_profile = module.params.get("dns_profile") + address = module.params.get("address") + preferred = aci.boolean(module.params.get("preferred")) + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="dnsProfile", + aci_rn="fabric/dnsp-{0}".format(dns_profile), + module_object=dns_profile, + target_filter={"name": dns_profile}, + ), + subclass_1=dict(aci_class="dnsProv", aci_rn="prov-{0}".format(address), module_object=address, target_filter={"address": address}), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="dnsProv", + class_config=dict(addr=address, preferred=preferred), + ) + + aci.get_diff(aci_class="dnsProv") + + 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_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_domain.py new file mode 100644 index 000000000..75509b0ef --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_domain.py @@ -0,0 +1,460 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_domain +short_description: Manage physical, virtual, bridged, routed or FC domain profiles (phys:DomP, vmm:DomP, l2ext:DomP, l3ext:DomP, fc:DomP) +description: +- Manage physical, virtual, bridged, routed or FC domain profiles on Cisco ACI fabrics. +options: + domain: + description: + - Name of the physical, virtual, bridged routed or FC domain profile. + type: str + aliases: [ domain_name, domain_profile, name ] + domain_type: + description: + - The type of domain profile. + - 'C(fc): The FC domain profile is a policy pertaining to single FC Management domain' + - 'C(l2dom): The external bridged domain profile is a policy for managing L2 bridged infrastructure bridged outside the fabric.' + - 'C(l3dom): The external routed domain profile is a policy for managing L3 routed infrastructure outside the fabric.' + - 'C(phys): The physical domain profile stores the physical resources and encap resources that should be used for EPGs associated with this domain.' + - 'C(vmm): The VMM domain profile is a policy for grouping VM controllers with similar networking policy requirements.' + type: str + required: true + choices: [ fc, l2dom, l3dom, phys, vmm ] + aliases: [ type ] + dscp: + description: + - The target Differentiated Service (DSCP) value. + - The APIC defaults to C(unspecified) when unset during creation. + 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 ] + encap_mode: + description: + - The layer 2 encapsulation protocol to use with the virtual switch. + type: str + choices: [ unknown, vlan, vxlan ] + add_infra_pg: + description: + - Configure port groups for infra VLAN (e.g. Virtual APIC). + type: bool + aliases: [ infra_pg ] + tag_collection: + description: + - Enables Cisco APIC to collect VMs that have been assigned tags in VMware vCenter for microsegmentation. + type: bool + multicast_address: + description: + - The multicast IP address to use for the virtual switch. + 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 + vm_provider: + description: + - The VM platform for VMM Domains. + - Support for Kubernetes was added in ACI v3.0. + - Support for CloudFoundry, OpenShift and Red Hat was added in ACI v3.1. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] + vswitch: + description: + - The virtual switch to use for vmm domains. + - The APIC defaults to C(default) when unset during creation. + type: str + choices: [ avs, default, dvs, unknown ] + access_mode: + description: + - Access mode for vmm domains + - This parameter cannot be changed after a domain is created + type: str + choices: [ read-only, read-write ] + enable_vm_folder: + description: + - Enable VM folder data retrieval + type: bool +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- module: cisco.aci.aci_aep_to_domain +- module: cisco.aci.aci_domain_to_encap_pool +- module: cisco.aci.aci_domain_to_vlan_pool +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(phys:DomP), + B(vmm:DomP), B(l2ext:DomP), B(l3ext:DomP) and B(fc:DomP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add a new physical domain + cisco.aci.aci_domain: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + domain_type: phys + state: present + +- name: Remove a physical domain + cisco.aci.aci_domain: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + domain_type: phys + state: absent + +- name: Add a new VMM domain + cisco.aci.aci_domain: + host: apic + username: admin + password: SomeSecretPassword + domain: hyperv_dom + domain_type: vmm + vm_provider: microsoft + state: present + delegate_to: localhost + +- name: Remove a VMM domain + cisco.aci.aci_domain: + host: apic + username: admin + password: SomeSecretPassword + domain: hyperv_dom + domain_type: vmm + vm_provider: microsoft + state: absent + delegate_to: localhost + +- name: Query a specific physical domain + cisco.aci.aci_domain: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + domain_type: phys + state: query + delegate_to: localhost + register: query_result + +- name: Query all domains + cisco.aci.aci_domain: + host: apic + username: admin + password: SomeSecretPassword + domain_type: phys + 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 + +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + +VSWITCH_MAPPING = dict( + avs="n1kv", + default="default", + dvs="default", + unknown="unknown", +) + +BOOL_TO_ACI_MAPPING = {True: "yes", False: "no", None: None} + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + domain_type=dict(type="str", required=True, choices=["fc", "l2dom", "l3dom", "phys", "vmm"], aliases=["type"]), + domain=dict(type="str", aliases=["domain_name", "domain_profile", "name"]), # Not required for querying all objects + dscp=dict( + 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"], + ), + encap_mode=dict(type="str", choices=["unknown", "vlan", "vxlan"]), + add_infra_pg=dict(type="bool", aliases=["infra_pg"]), + tag_collection=dict(type="bool"), + multicast_address=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + vm_provider=dict(type="str", choices=["cloudfoundry", "kubernetes", "microsoft", "openshift", "openstack", "redhat", "vmware"]), + vswitch=dict(type="str", choices=["avs", "default", "dvs", "unknown"]), + name_alias=dict(type="str"), + access_mode=dict(type="str", choices=["read-write", "read-only"]), + enable_vm_folder=dict(type="bool"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["domain_type", "vmm", ["vm_provider"]], + ["state", "absent", ["domain", "domain_type"]], + ["state", "present", ["domain", "domain_type"]], + ], + ) + + dscp = module.params.get("dscp") + domain = module.params.get("domain") + domain_type = module.params.get("domain_type") + encap_mode = module.params.get("encap_mode") + add_infra_pg = BOOL_TO_ACI_MAPPING[module.params.get("add_infra_pg")] + tag_collection = BOOL_TO_ACI_MAPPING[module.params.get("tag_collection")] + multicast_address = module.params.get("multicast_address") + vm_provider = module.params.get("vm_provider") + vswitch = module.params.get("vswitch") + if vswitch is not None: + vswitch = VSWITCH_MAPPING.get(vswitch) + access_mode = module.params.get("access_mode") + enable_vm_folder = BOOL_TO_ACI_MAPPING[module.params.get("enable_vm_folder")] + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + if domain_type != "vmm": + if vm_provider is not None: + module.fail_json(msg="Domain type '{0}' cannot have parameter 'vm_provider'".format(domain_type)) + if encap_mode is not None: + module.fail_json(msg="Domain type '{0}' cannot have parameter 'encap_mode'".format(domain_type)) + if multicast_address is not None: + module.fail_json(msg="Domain type '{0}' cannot have parameter 'multicast_address'".format(domain_type)) + if vswitch is not None: + module.fail_json(msg="Domain type '{0}' cannot have parameter 'vswitch'".format(domain_type)) + if access_mode is not None: + module.fail_json(msg="Domain type '{0}' cannot have parameter 'access_mode'".format(domain_type)) + if enable_vm_folder is not None: + module.fail_json(msg="Domain type '{0}' cannot have parameter 'enable_vm_folder'".format(domain_type)) + + if dscp is not None and domain_type not in ["l2dom", "l3dom"]: + module.fail_json(msg="DSCP values can only be assigned to 'l2ext and 'l3ext' domains") + + # Compile the full domain for URL building + if domain_type == "fc": + domain_class = "fcDomP" + domain_mo = "uni/fc-{0}".format(domain) + domain_rn = "fc-{0}".format(domain) + elif domain_type == "l2dom": + domain_class = "l2extDomP" + domain_mo = "uni/l2dom-{0}".format(domain) + domain_rn = "l2dom-{0}".format(domain) + elif domain_type == "l3dom": + domain_class = "l3extDomP" + domain_mo = "uni/l3dom-{0}".format(domain) + domain_rn = "l3dom-{0}".format(domain) + elif domain_type == "phys": + domain_class = "physDomP" + domain_mo = "uni/phys-{0}".format(domain) + domain_rn = "phys-{0}".format(domain) + elif domain_type == "vmm": + domain_class = "vmmDomP" + domain_mo = "uni/vmmp-{0}/dom-{1}".format(VM_PROVIDER_MAPPING.get(vm_provider), domain) + domain_rn = "vmmp-{0}/dom-{1}".format(VM_PROVIDER_MAPPING.get(vm_provider), domain) + + # Ensure that querying all objects works when only domain_type is provided + if domain is None: + domain_mo = None + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=domain_class, + aci_rn=domain_rn, + module_object=domain_mo, + target_filter={"name": domain}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=domain_class, + class_config=dict( + encapMode=encap_mode, + mcastAddr=multicast_address, + configInfraPg=add_infra_pg, + enableTag=tag_collection, + mode=vswitch, + name=domain, + targetDscp=dscp, + nameAlias=name_alias, + accessMode=access_mode, + enableVmFolder=enable_vm_folder, + ), + ) + + aci.get_diff(aci_class=domain_class) + + 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_domain_to_encap_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py new file mode 100644 index 000000000..1fe5e8638 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_encap_pool.py @@ -0,0 +1,379 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Dag Wieers <dag@wieers.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_domain_to_encap_pool +short_description: Bind Domain to Encap Pools (infra:RsVlanNs) +description: +- Bind Domain to Encap Pools on Cisco ACI fabrics. +notes: +- The C(domain) and C(encap_pool) parameters should exist before using this module. + The M(cisco.aci.aci_domain) and M(cisco.aci.aci_encap_pool) can be used for these. +options: + domain: + description: + - Name of the domain being associated with the Encap Pool. + type: str + aliases: [ domain_name, domain_profile ] + domain_type: + description: + - Determines if the Domain is physical (phys) or virtual (vmm). + type: str + required: true + choices: [ fc, l2dom, l3dom, phys, vmm ] + pool: + description: + - The name of the pool. + type: str + aliases: [ pool_name ] + pool_allocation_mode: + description: + - The method used for allocating encaps to resources. + - Only vlan and vsan support allocation modes. + type: str + choices: [ dynamic, static] + aliases: [ allocation_mode, mode ] + pool_type: + description: + - The encap type of C(pool). + type: str + required: true + choices: [ vlan, vsan, vxlan ] + 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 + vm_provider: + description: + - The VM platform for VMM Domains. + - Support for Kubernetes was added in ACI v3.0. + - Support for CloudFoundry, OpenShift and Red Hat was added in ACI v3.1. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_encap_pool +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:RsVlanNs). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add domain to VLAN pool binding + cisco.aci.aci_domain_to_encap_pool: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + domain_type: phys + pool: test_pool + pool_type: vlan + pool_allocation_mode: dynamic + state: present + delegate_to: localhost + +- name: Remove domain to VLAN pool binding + cisco.aci.aci_domain_to_encap_pool: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + domain_type: phys + pool: test_pool + pool_type: vlan + pool_allocation_mode: dynamic + state: absent + delegate_to: localhost + +- name: Query our domain to VLAN pool binding + cisco.aci.aci_domain_to_encap_pool: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + pool: test_pool + pool_type: vlan + pool_allocation_mode: dynamic + state: query + delegate_to: localhost + register: query_result + +- name: Query all domain to VLAN pool bindings + cisco.aci.aci_domain_to_encap_pool: + host: apic + username: admin + password: SomeSecretPassword + domain_type: phys + pool_type: vlan + pool_allocation_mode: dynamic + 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 + +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + +POOL_MAPPING = dict( + vlan=dict( + aci_mo="uni/infra/vlanns-{0}", + child_class="infraRsVlanNs", + ), + vxlan=dict( + aci_mo="uni/infra/vxlanns-{0}", + child_class="vmmRsVxlanNs", + ), + vsan=dict( + aci_mo="uni/infra/vsanns-{0}", + child_class="fcRsVsanNs", + ), +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + domain_type=dict(type="str", required=True, choices=["fc", "l2dom", "l3dom", "phys", "vmm"]), + pool_type=dict(type="str", required=True, choices=["vlan", "vsan", "vxlan"]), + domain=dict(type="str", aliases=["domain_name", "domain_profile"]), # Not required for querying all objects + 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"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["domain_type", "vmm", ["vm_provider"]], + ["state", "absent", ["domain", "domain_type", "pool", "pool_type"]], + ["state", "present", ["domain", "domain_type", "pool", "pool_type"]], + ], + ) + + domain = module.params.get("domain") + domain_type = module.params.get("domain_type") + pool = module.params.get("pool") + pool_allocation_mode = module.params.get("pool_allocation_mode") + pool_type = module.params.get("pool_type") + vm_provider = module.params.get("vm_provider") + state = module.params.get("state") + + # Report when vm_provider is set when type is not virtual + if domain_type != "vmm" and vm_provider is not None: + module.fail_json(msg="Domain type '{0}' cannot have a 'vm_provider'".format(domain_type)) + + # ACI Pool URL requires the allocation mode for vlan and vsan pools (ex: uni/infra/vlanns-[poolname]-static) + pool_name = pool + if pool_type != "vxlan" and 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' for 'pool_type' of 'vlan' and 'vsan' when 'pool' is provided") + + # Vxlan pools do not support allocation modes + if pool_type == "vxlan" and pool_allocation_mode is not None: + module.fail_json(msg="vxlan pools do not support setting the allocation_mode; please remove this parameter from the task") + + # Compile the full domain for URL building + if domain_type == "fc": + domain_class = "fcDomP" + domain_mo = "uni/fc-{0}".format(domain) + domain_rn = "fc-{0}".format(domain) + elif domain_type == "l2dom": + domain_class = "l2extDomP" + domain_mo = "uni/l2dom-{0}".format(domain) + domain_rn = "l2dom-{0}".format(domain) + elif domain_type == "l3dom": + domain_class = "l3extDomP" + domain_mo = "uni/l3dom-{0}".format(domain) + domain_rn = "l3dom-{0}".format(domain) + elif domain_type == "phys": + domain_class = "physDomP" + domain_mo = "uni/phys-{0}".format(domain) + domain_rn = "phys-{0}".format(domain) + elif domain_type == "vmm": + 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) + + # Ensure that querying all objects works when only domain_type is provided + if domain is None: + domain_mo = None + + pool_mo = POOL_MAPPING[pool_type]["aci_mo"].format(pool_name) + child_class = POOL_MAPPING[pool_type]["child_class"] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=domain_class, + aci_rn=domain_rn, + module_object=domain_mo, + target_filter={"name": domain}, + ), + child_classes=[child_class], + ) + + aci.get_existing() + + if state == "present": + # Filter out module params with null values + aci.payload( + aci_class=domain_class, + class_config=dict(name=domain), + child_configs=[ + {child_class: {"attributes": {"tDn": pool_mo}}}, + ], + ) + + # Generate config diff which will be used as POST request body + aci.get_diff(aci_class=domain_class) + + # Submit changes if module not in check_mode and the proposed is different than existing + 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_domain_to_vlan_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py new file mode 100644 index 000000000..28008b712 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_domain_to_vlan_pool.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Dag Wieers <dag@wieers.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_domain_to_vlan_pool +short_description: Bind Domain to VLAN Pools (infra:RsVlanNs) +description: +- Bind Domain to VLAN Pools on Cisco ACI fabrics. +options: + domain: + description: + - Name of the domain being associated with the VLAN Pool. + type: str + aliases: [ domain_name, domain_profile ] + domain_type: + description: + - Determines if the Domain is physical (phys) or virtual (vmm). + type: str + required: true + choices: [ fc, l2dom, l3dom, phys, vmm ] + pool: + description: + - The name of the pool. + type: str + aliases: [ pool_name, vlan_pool ] + pool_allocation_mode: + description: + - The method used for allocating VLANs to resources. + type: str + required: true + choices: [ dynamic, static] + aliases: [ allocation_mode, mode ] + 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 + vm_provider: + description: + - The VM platform for VMM Domains. + - Support for Kubernetes was added in ACI v3.0. + - Support for CloudFoundry, OpenShift and Red Hat was added in ACI v3.1. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(domain) and C(vlan_pool) parameters should exist before using this module. + The M(cisco.aci.aci_domain) and M(cisco.aci.aci_vlan_pool) can be used for these. +seealso: +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_vlan_pool +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:RsVlanNs). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Bind a VMM domain to VLAN pool + cisco.aci.aci_domain_to_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + domain: vmw_dom + domain_type: vmm + pool: vmw_pool + pool_allocation_mode: dynamic + vm_provider: vmware + state: present + delegate_to: localhost + +- name: Remove a VMM domain to VLAN pool binding + cisco.aci.aci_domain_to_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + domain: vmw_dom + domain_type: vmm + pool: vmw_pool + pool_allocation_mode: dynamic + vm_provider: vmware + state: absent + delegate_to: localhost + +- name: Bind a physical domain to VLAN pool + cisco.aci.aci_domain_to_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + domain_type: phys + pool: phys_pool + pool_allocation_mode: static + state: present + delegate_to: localhost + +- name: Bind a physical domain to VLAN pool + cisco.aci.aci_domain_to_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + domain_type: phys + pool: phys_pool + pool_allocation_mode: static + state: absent + delegate_to: localhost + +- name: Query an domain to VLAN pool binding + cisco.aci.aci_domain_to_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + domain: phys_dom + domain_type: phys + pool: phys_pool + pool_allocation_mode: static + state: query + delegate_to: localhost + register: query_result + +- name: Query all domain to VLAN pool bindings + cisco.aci.aci_domain_to_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + domain_type: phys + 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 + +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + domain_type=dict(type="str", required=True, choices=["fc", "l2dom", "l3dom", "phys", "vmm"]), + domain=dict(type="str", aliases=["domain_name", "domain_profile"]), # Not required for querying all objects + 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"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["domain_type", "vmm", ["vm_provider"]], + ["state", "absent", ["domain", "domain_type", "pool"]], + ["state", "present", ["domain", "domain_type", "pool"]], + ], + ) + + domain = module.params.get("domain") + domain_type = module.params.get("domain_type") + pool = module.params.get("pool") + pool_allocation_mode = module.params.get("pool_allocation_mode") + vm_provider = module.params.get("vm_provider") + state = module.params.get("state") + + # Report when vm_provider is set when type is not virtual + if domain_type != "vmm" and vm_provider is not None: + module.fail_json(msg="Domain type '{0}' cannot have a 'vm_provider'".format(domain_type)) + + # ACI Pool URL requires the allocation mode for vlan and vsan pools (ex: uni/infra/vlanns-[poolname]-static) + pool_name = pool + if pool is not None: + pool_name = "[{0}]-{1}".format(pool, pool_allocation_mode) + + # Compile the full domain for URL building + if domain_type == "fc": + domain_class = "fcDomP" + domain_mo = "uni/fc-{0}".format(domain) + domain_rn = "fc-{0}".format(domain) + elif domain_type == "l2dom": + domain_class = "l2extDomP" + domain_mo = "uni/l2dom-{0}".format(domain) + domain_rn = "l2dom-{0}".format(domain) + elif domain_type == "l3dom": + domain_class = "l3extDomP" + domain_mo = "uni/l3dom-{0}".format(domain) + domain_rn = "l3dom-{0}".format(domain) + elif domain_type == "phys": + domain_class = "physDomP" + domain_mo = "uni/phys-{0}".format(domain) + domain_rn = "phys-{0}".format(domain) + elif domain_type == "vmm": + 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) + + # Ensure that querying all objects works when only domain_type is provided + if domain is None: + domain_mo = None + + aci_mo = "uni/infra/vlanns-{0}".format(pool_name) + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=domain_class, + aci_rn=domain_rn, + module_object=domain_mo, + target_filter={"name": domain}, + ), + child_classes=["infraRsVlanNs"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=domain_class, + class_config=dict(name=domain), + child_configs=[ + {"infraRsVlanNs": {"attributes": {"tDn": aci_mo}}}, + ], + ) + + aci.get_diff(aci_class=domain_class) + + 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_encap_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool.py new file mode 100644 index 000000000..5d5ed7833 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool.py @@ -0,0 +1,319 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_encap_pool +short_description: Manage encap pools (fvns:VlanInstP, fvns:VxlanInstP, fvns:VsanInstP) +description: +- Manage vlan, vxlan, and vsan pools on Cisco ACI fabrics. +options: + description: + description: + - Description for the C(pool). + type: str + aliases: [ descr ] + pool: + description: + - The name of the pool. + type: str + aliases: [ name, pool_name ] + pool_allocation_mode: + description: + - The method used for allocating encaps to resources. + - Only vlan and vsan support allocation modes. + type: str + choices: [ dynamic, static ] + aliases: [ allocation_mode, mode ] + pool_type: + description: + - The encap type of C(pool). + type: str + required: true + aliases: [ type ] + choices: [ vlan, vsan, vxlan ] + 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: +- module: cisco.aci.aci_encap_pool_range +- module: cisco.aci.aci_vlan_pool +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fvns:VlanInstP), + B(fvns:VxlanInstP) and B(fvns:VsanInstP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Add a new vlan pool + cisco.aci.aci_encap_pool: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + description: Production VLANs + state: present + delegate_to: localhost + +- name: Remove a vlan pool + cisco.aci.aci_encap_pool: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + state: absent + delegate_to: localhost + +- name: Query a vlan pool + cisco.aci.aci_encap_pool: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + state: query + delegate_to: localhost + register: query_result + +- name: Query all vlan pools + cisco.aci.aci_encap_pool: + host: apic + username: admin + password: SomeSecretPassword + pool_type: vlan + 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 + +ACI_POOL_MAPPING = dict( + vlan=dict( + aci_class="fvnsVlanInstP", + aci_mo="infra/vlanns-", + ), + vxlan=dict( + aci_class="fvnsVxlanInstP", + aci_mo="infra/vxlanns-", + ), + vsan=dict( + aci_class="fvnsVsanInstP", + aci_mo="infra/vsanns-", + ), +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + pool_type=dict(type="str", required=True, aliases=["type"], choices=["vlan", "vsan", "vxlan"]), + description=dict(type="str", aliases=["descr"]), + pool=dict(type="str", aliases=["name", "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"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["pool"]], + ["state", "present", ["pool"]], + ], + ) + + description = module.params.get("description") + pool = module.params.get("pool") + pool_type = module.params.get("pool_type") + pool_allocation_mode = module.params.get("pool_allocation_mode") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci_class = ACI_POOL_MAPPING[pool_type]["aci_class"] + aci_mo = ACI_POOL_MAPPING[pool_type]["aci_mo"] + pool_name = pool + + # ACI Pool URL requires the pool_allocation mode for vlan and vsan pools (ex: uni/infra/vlanns-[poolname]-static) + if pool_type != "vxlan" and 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 parameter 'pool_allocation_mode' for 'pool_type' of 'vlan' and 'vsan' when parameter 'pool' is provided") + + # Vxlan pools do not support pool allocation modes + if pool_type == "vxlan" and pool_allocation_mode is not None: + module.fail_json(msg="vxlan pools do not support setting the 'pool_allocation_mode'; please remove this parameter from the task") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn="{0}{1}".format(aci_mo, pool_name), + module_object=pool, + target_filter={"name": pool}, + ), + ) + + aci.get_existing() + + if state == "present": + # Filter out module parameters with null values + aci.payload( + aci_class=aci_class, + class_config=dict( + allocMode=pool_allocation_mode, + descr=description, + name=pool, + nameAlias=name_alias, + ), + ) + + # Generate config diff which will be used as POST request body + aci.get_diff(aci_class=aci_class) + + # Submit changes if module not in check_mode and the proposed is different than existing + 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_encap_pool_range.py b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py new file mode 100644 index 000000000..577fc996c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py @@ -0,0 +1,450 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_encap_pool_range +short_description: Manage encap ranges assigned to pools (fvns:EncapBlk, fvns:VsanEncapBlk) +description: +- Manage vlan, vxlan, and vsan ranges that are assigned to pools on Cisco ACI fabrics. +options: + allocation_mode: + description: + - The method used for allocating encaps to resources. + - Only vlan and vsan support allocation modes. + type: str + choices: [ dynamic, inherit, static] + aliases: [ mode ] + description: + description: + - Description for the pool range. + type: str + aliases: [ descr ] + pool: + description: + - The name of the pool that the range should be assigned to. + type: str + aliases: [ pool_name ] + pool_allocation_mode: + description: + - The method used for allocating encaps to resources. + - Only vlan and vsan support allocation modes. + type: str + choices: [ dynamic, static] + aliases: [ pool_mode ] + pool_type: + description: + - The encap type of C(pool). + type: str + required: true + aliases: [ type ] + choices: [ vlan, vxlan, vsan] + range_end: + description: + - The end of encap range. + type: int + aliases: [ end ] + range_name: + description: + - The name to give to the encap range. + type: str + aliases: [ name, range ] + range_start: + description: + - The start of the encap range. + type: int + aliases: [ start ] + 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(pool) must exist in order to add or delete a range. +seealso: +- module: cisco.aci.aci_encap_pool +- module: cisco.aci.aci_vlan_pool_encap_block +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fvns:EncapBlk) and B(fvns:VsanEncapBlk). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Add a new VLAN pool range + cisco.aci.aci_encap_pool_range: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + pool_allocation_mode: static + range_name: anstest + range_start: 20 + range_end: 40 + allocation_mode: inherit + state: present + delegate_to: localhost + +- name: Remove a VLAN pool range + cisco.aci.aci_encap_pool_range: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + pool_allocation_mode: static + range_name: anstest + range_start: 20 + range_end: 40 + state: absent + delegate_to: localhost + +- name: Query a VLAN range + cisco.aci.aci_encap_pool_range: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_type: vlan + pool_allocation_mode: static + range_name: anstest + range_start: 20 + range_end: 50 + state: query + delegate_to: localhost + register: query_result + +- name: Query a VLAN pool for ranges by range_name + cisco.aci.aci_encap_pool_range: + host: apic + username: admin + password: SomeSecretPassword + pool_type: vlan + range_name: anstest + state: query + delegate_to: localhost + register: query_result + +- name: Query a VLAN pool for ranges by range_start + cisco.aci.aci_encap_pool_range: + host: apic + username: admin + password: SomeSecretPassword + pool_type: vlan + range_start: 20 + state: query + delegate_to: localhost + register: query_result + +- name: Query a VLAN pool for ranges by range_start and range_end + cisco.aci.aci_encap_pool_range: + host: apic + username: admin + password: SomeSecretPassword + pool_type: vlan + range_start: 20 + range_end: 40 + state: query + delegate_to: localhost + register: query_result + +- name: Query all VLAN pool ranges + cisco.aci.aci_encap_pool_range: + host: apic + username: admin + password: SomeSecretPassword + pool_type: vlan + 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_POOL_MAPPING = dict( + vlan=dict( + aci_class="fvnsVlanInstP", + aci_mo="infra/vlanns-", + ), + vxlan=dict( + aci_class="fvnsVxlanInstP", + aci_mo="infra/vxlanns-", + ), + vsan=dict( + aci_class="fvnsVsanInstP", + aci_mo="infra/vsanns-", + ), +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + pool_type=dict(type="str", required=True, aliases=["type"], choices=["vlan", "vxlan", "vsan"]), + allocation_mode=dict(type="str", aliases=["mode"], choices=["dynamic", "inherit", "static"]), + description=dict(type="str", aliases=["descr"]), + pool=dict(type="str", aliases=["pool_name"]), # Not required for querying all objects + pool_allocation_mode=dict(type="str", aliases=["pool_mode"], choices=["dynamic", "static"]), + range_end=dict(type="int", aliases=["end"]), # Not required for querying all objects + range_name=dict(type="str", aliases=["name", "range"]), # Not required for querying all objects + range_start=dict(type="int", aliases=["start"]), # Not required for querying all objects + 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", ["pool", "range_end", "range_start"]], + ["state", "present", ["pool", "range_end", "range_start"]], + ], + ) + + allocation_mode = module.params.get("allocation_mode") + description = module.params.get("description") + pool = module.params.get("pool") + pool_allocation_mode = module.params.get("pool_allocation_mode") + pool_type = module.params.get("pool_type") + range_end = module.params.get("range_end") + range_name = module.params.get("range_name") + range_start = module.params.get("range_start") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + if range_end is not None: + encap_end = "{0}-{1}".format(pool_type, range_end) + else: + encap_end = None + + if range_start is not None: + encap_start = "{0}-{1}".format(pool_type, range_start) + else: + encap_start = None + + ACI_RANGE_MAPPING = dict( + vlan=dict( + aci_class="fvnsEncapBlk", + aci_mo="from-[{0}]-to-[{1}]".format(encap_start, encap_end), + ), + vxlan=dict( + aci_class="fvnsEncapBlk", + aci_mo="from-[{0}]-to-[{1}]".format(encap_start, encap_end), + ), + vsan=dict( + aci_class="fvnsVsanEncapBlk", + aci_mo="vsanfrom-[{0}]-to-[{1}]".format(encap_start, encap_end), + ), + ) + + # Collect proper class and mo information based on pool_type + aci_range_class = ACI_RANGE_MAPPING[pool_type]["aci_class"] + aci_range_mo = ACI_RANGE_MAPPING[pool_type]["aci_mo"] + aci_pool_class = ACI_POOL_MAPPING[pool_type]["aci_class"] + aci_pool_mo = ACI_POOL_MAPPING[pool_type]["aci_mo"] + pool_name = pool + + # Validate range_end and range_start are valid for its respective encap type + for encap_id in range_end, range_start: + if encap_id is not None: + if pool_type == "vlan": + if not 1 <= encap_id <= 4094: + module.fail_json(msg='vlan pools must have "range_start" and "range_end" values between 1 and 4094') + 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": + if not 1 <= encap_id <= 4093: + module.fail_json(msg='vsan pools must have "range_start" and "range_end" values between 1 and 4093') + + if range_end is not None and range_start is not None: + # Validate range_start is less than range_end + if range_start > range_end: + module.fail_json(msg='The "range_start" must be less than or equal to the "range_end"') + + elif range_end is None and range_start is None: + if range_name is None: + # Reset range managed object to None for aci util to properly handle query + aci_range_mo = None + + # Vxlan does not support setting the allocation mode + if pool_type == "vxlan" and allocation_mode is not None: + module.fail_json(msg='vxlan pools do not support setting the "allocation_mode"; please omit this parameter for vxlan pools') + + # ACI Pool URL requires the allocation mode for vlan and vsan pools (ex: uni/infra/vlanns-[poolname]-static) + if pool_type != "vxlan" and 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" for "pool_type" of "vlan" and "vsan" when the "pool" is provided') + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=aci_pool_class, + aci_rn="{0}{1}".format(aci_pool_mo, pool_name), + module_object=pool, + target_filter={"name": pool}, + ), + subclass_1=dict( + aci_class=aci_range_class, + aci_rn="{0}".format(aci_range_mo), + module_object=aci_range_mo, + target_filter={"from": encap_start, "to": encap_end, "name": range_name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_range_class, + class_config={ + "allocMode": allocation_mode, + "descr": description, + "from": encap_start, + "name": range_name, + "to": encap_end, + "nameAlias": name_alias, + }, + ) + + aci.get_diff(aci_class=aci_range_class) + + 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_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg.py new file mode 100644 index 000000000..9f0eb671a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg.py @@ -0,0 +1,429 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 +short_description: Manage End Point Groups (EPG) objects (fv:AEPg) +description: +- Manage End Point Groups (EPG) on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing 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 ] + bd: + description: + - Name of the bridge domain being associated with the EPG. + type: str + aliases: [ bd_name, bridge_domain ] + priority: + description: + - The QoS class. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, unspecified ] + intra_epg_isolation: + description: + - The Intra EPG Isolation. + - The APIC defaults to C(unenforced) when unset during creation. + type: str + choices: [ enforced, unenforced ] + description: + description: + - Description for the EPG. + type: str + aliases: [ descr ] + fwd_control: + description: + - The forwarding control used by the EPG. + - The APIC defaults to C(none) when unset during creation. + type: str + choices: [ none, proxy-arp ] + preferred_group: + description: + - Whether or not the EPG is part of the Preferred Group and can communicate without contracts. + - This is very convenient for migration scenarios, or when ACI is used for network automation but not for policy. + - The APIC defaults to C(false) when unset during creation. + type: bool + monitoring_policy: + description: + - The name of the monitoring policy. + type: str + custom_qos_policy: + description: + - The name of the custom Quality of Service policy. + type: str + useg: + description: + - Use C(yes) to create uSeg EPG and C(no) is used to create Application EPG. + type: str + choices: [ 'yes', 'no' ] + 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(tenant) and C(app_profile) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_ap) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_ap +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:AEPg). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Swetha Chunduri (@schunduri) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new EPG + cisco.aci.aci_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: intranet + epg: web_epg + description: Web Intranet EPG + bd: prod_bd + monitoring_policy: default + preferred_group: true + state: present + delegate_to: localhost + +- aci_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: ticketing + epg: "{{ item.epg }}" + description: Ticketing EPG + bd: "{{ item.bd }}" + priority: unspecified + intra_epg_isolation: unenforced + state: present + delegate_to: localhost + with_items: + - epg: web + bd: web_bd + - epg: database + bd: database_bd + +- name: Add a new uSeg EPG + cisco.aci.aci_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: intranet + epg: web_epg + description: Web Intranet EPG + bd: prod_bd + monitoring_policy: default + preferred_group: true + useg: 'yes' + state: present + delegate_to: localhost + +- name: Remove an EPG + cisco.aci.aci_epg: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + tenant: production + app_profile: intranet + epg: web_epg + monitoring_policy: default + state: absent + delegate_to: localhost + +- name: Query an EPG + cisco.aci.aci_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: ticketing + epg: web_epg + state: query + delegate_to: localhost + register: query_result + +- name: Query all EPGs + cisco.aci.aci_epg: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query all EPGs with a Specific Name + cisco.aci.aci_epg: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + epg: web_epg + state: query + delegate_to: localhost + register: query_result + +- name: Query all EPGs of an App Profile + cisco.aci.aci_epg: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + ap: ticketing + 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( + epg=dict(type="str", aliases=["epg_name", "name"]), # Not required for querying all objects + bd=dict(type="str", aliases=["bd_name", "bridge_domain"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), # Not required for querying all objects + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + priority=dict(type="str", choices=["level1", "level2", "level3", "unspecified"]), + intra_epg_isolation=dict(choices=["enforced", "unenforced"]), + fwd_control=dict(type="str", choices=["none", "proxy-arp"]), + preferred_group=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + monitoring_policy=dict(type="str"), + custom_qos_policy=dict(type="str"), + useg=dict(type="str", choices=["yes", "no"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["ap", "epg", "tenant"]], + ["state", "present", ["ap", "epg", "tenant"]], + ], + ) + + aci = ACIModule(module) + + epg = module.params.get("epg") + bd = module.params.get("bd") + description = module.params.get("description") + priority = module.params.get("priority") + intra_epg_isolation = module.params.get("intra_epg_isolation") + fwd_control = module.params.get("fwd_control") + preferred_group = aci.boolean(module.params.get("preferred_group"), "include", "exclude") + state = module.params.get("state") + tenant = module.params.get("tenant") + ap = module.params.get("ap") + name_alias = module.params.get("name_alias") + monitoring_policy = module.params.get("monitoring_policy") + custom_qos_policy = module.params.get("custom_qos_policy") + useg = module.params.get("useg") + + child_configs = [dict(fvRsBd=dict(attributes=dict(tnFvBDName=bd))), dict(fvRsAEPgMonPol=dict(attributes=dict(tnMonEPGPolName=monitoring_policy)))] + + if custom_qos_policy is not None: + child_configs.append(dict(fvRsCustQosPol=dict(attributes=dict(tnQosCustomPolName=custom_qos_policy)))) + + 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}, + ), + child_classes=["fvRsBd", "fvRsAEPgMonPol", "fvRsCustQosPol"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvAEPg", + class_config=dict( + name=epg, + descr=description, + prio=priority, + pcEnfPref=intra_epg_isolation, + fwdCtrl=fwd_control, + prefGrMemb=preferred_group, + nameAlias=name_alias, + isAttrBasedEPg=useg, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fvAEPg") + + 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_epg_monitoring_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_monitoring_policy.py new file mode 100644 index 000000000..7f0c81cd7 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_monitoring_policy.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_monitoring_policy +short_description: Manage monitoring policies (mon:EPGPol) +description: +- Manage monitoring policies on Cisco ACI fabrics. +options: + monitoring_policy: + description: + - The name of the monitoring policy. + type: str + aliases: [ name ] + description: + description: + - Description for the monitoring policy. + type: str + aliases: [ descr ] + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_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) 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(mon:EPGPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a monitoring policy + cisco.aci.aci_epg_monitoring_policy: + host: apic + username: admin + password: SomeSecretPassword + monitoring_policy: web_servers_monitoring + tenant: prod + state: present + delegate_to: localhost + +- name: Delete a monitoring policy + cisco.aci.aci_epg_monitoring_policy: + host: apic + username: admin + password: SomeSecretPassword + monitoring_policy: web_servers_monitoring + tenant: prod + state: absent + delegate_to: localhost + +- name: Query all monitoring policy + cisco.aci.aci_epg_monitoring_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific monitoring policy + cisco.aci.aci_epg_monitoring_policy: + host: apic + username: admin + password: SomeSecretPassword + monitoring_policy: web_servers_monitoring + tenant: prod + 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( + monitoring_policy=dict(type="str", aliases=["name"]), # Not required for querying all objects + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + 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", ["monitoring_policy", "tenant"]], + ["state", "present", ["monitoring_policy", "tenant"]], + ], + ) + + monitoring_policy = module.params.get("monitoring_policy") + 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="monEPGPol", + aci_rn="monepg-{0}".format(monitoring_policy), + module_object=monitoring_policy, + target_filter={"name": monitoring_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="monEPGPol", + class_config=dict( + name=monitoring_policy, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="monEPGPol") + + 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_epg_to_contract.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_contract.py new file mode 100644 index 000000000..fe09945a3 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_contract.py @@ -0,0 +1,387 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_to_contract +short_description: Bind EPGs to Contracts (fv:RsCons, fv:RsProv) +description: +- Bind EPGs to Contracts on Cisco ACI fabrics. +notes: +- The C(tenant), C(app_profile), C(EPG), and C(Contract) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_ap), M(cisco.aci.aci_epg), and M(cisco.aci.aci_contract) modules can be used for this. +options: + ap: + description: + - Name of an existing application network profile, that will contain the EPGs. + type: str + aliases: [ app_profile, app_profile_name ] + contract: + description: + - The name of the contract. + type: str + aliases: [ contract_name ] + contract_type: + description: + - Determines if the EPG should Provide or Consume the Contract. + type: str + required: true + choices: [ consumer, provider ] + epg: + description: + - The name of the end point group. + type: str + aliases: [ epg_name ] + priority: + description: + - QoS class. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, level4, level5, level6, unspecified ] + provider_match: + description: + - The matching algorithm for Provided Contracts. + - The APIC defaults to C(at_least_one) when unset during creation. + type: str + choices: [ all, at_least_one, at_most_one, none ] + contract_label: + description: + - Contract label to match + type: str + subject_label: + description: + - Subject label to match + 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 + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_epg +- module: cisco.aci.aci_contract +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fv:RsCons) and B(fv:RsProv). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Add a new contract to EPG binding + cisco.aci.aci_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract: anstest_http + contract_type: provider + contract_label: contractlabel + subject_label: subjlabel + state: present + delegate_to: localhost + +- name: Remove an existing contract to EPG binding + cisco.aci.aci_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract: anstest_http + contract_type: provider + state: absent + delegate_to: localhost + +- name: Query a specific contract to EPG binding + cisco.aci.aci_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract: anstest_http + contract_type: provider + state: query + delegate_to: localhost + register: query_result + +- name: Query all provider contract to EPG bindings + cisco.aci.aci_epg_to_contract: + host: apic + username: admin + password: SomeSecretPassword + contract_type: provider + 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_CLASS_MAPPING = dict( + consumer={ + "class": "fvRsCons", + "rn": "rscons-", + }, + provider={ + "class": "fvRsProv", + "rn": "rsprov-", + }, +) + +PROVIDER_MATCH_MAPPING = dict( + all="All", + at_least_one="AtleastOne", + at_most_one="AtmostOne", + none="None", +) + +CONTRACT_LABEL_MAPPING = dict( + consumer="vzConsLbl", + provider="vzProvLbl", +) + +SUBJ_LABEL_MAPPING = dict( + consumer="vzConsSubjLbl", + provider="vzProvSubjLbl", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + contract_type=dict(type="str", required=True, choices=["consumer", "provider"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), # Not required for querying all objects + epg=dict(type="str", aliases=["epg_name"]), # Not required for querying all objects + contract=dict(type="str", aliases=["contract_name"]), # Not required for querying all objects + priority=dict(type="str", choices=["level1", "level2", "level3", "level4", "level5", "level6", "unspecified"]), + provider_match=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + contract_label=dict(type="str"), + subject_label=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["ap", "contract", "epg", "tenant"]], + ["state", "present", ["ap", "contract", "epg", "tenant"]], + ], + ) + + ap = module.params.get("ap") + contract = module.params.get("contract") + contract_type = module.params.get("contract_type") + epg = module.params.get("epg") + priority = module.params.get("priority") + provider_match = module.params.get("provider_match") + if provider_match is not None: + provider_match = PROVIDER_MATCH_MAPPING[provider_match] + state = module.params.get("state") + tenant = module.params.get("tenant") + contract_label = module.params.get("contract_label") + subject_label = module.params.get("subject_label") + + aci_class = ACI_CLASS_MAPPING[contract_type]["class"] + aci_rn = ACI_CLASS_MAPPING[contract_type]["rn"] + contract_label_class = CONTRACT_LABEL_MAPPING[contract_type] + subject_label_class = SUBJ_LABEL_MAPPING[contract_type] + + if contract_type == "consumer" and provider_match is not None: + module.fail_json(msg="the 'provider_match' is only configurable for Provided Contracts") + + child_classes = [subject_label_class, contract_label_class] + + 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="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=aci_class, + aci_rn="{0}{1}".format(aci_rn, contract), + module_object=contract, + target_filter={"tnVzBrCPName": contract}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if contract_label: + child_configs.append({contract_label_class: {"attributes": {"name": contract_label}}}) + if subject_label: + child_configs.append({subject_label_class: {"attributes": {"name": subject_label}}}) + aci.payload( + aci_class=aci_class, + class_config=dict( + matchT=provider_match, + prio=priority, + tnVzBrCPName=contract, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class=aci_class) + + 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_epg_to_contract_interface.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_contract_interface.py new file mode 100644 index 000000000..bc0ed04fa --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_contract_interface.py @@ -0,0 +1,306 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_to_contract_interface +short_description: Bind EPGs to Consumed Contracts Interface (fv:RsConsIf). + +description: +- Bind EPGs to Consumed Contracts Interface on Cisco ACI fabrics. +notes: +- The C(tenant), C(app_profile), C(EPG), and C(Contract Interface) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_ap), M(cisco.aci.aci_epg), and M(cisco.aci.aci_contract_export) modules can be used for this. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - Name of an existing application profile, that will contain the EPGs. + type: str + aliases: [ app_profile, app_profile_name ] + epg: + description: + - The name of the end point group. + type: str + aliases: [ epg_name ] + contract_interface: + description: + - Name of the Contract interface, which is the contract with a "Global" scope which is exported to the tenant. + type: str + aliases: [ contract_interface_name ] + priority: + description: + - QoS class. + - The default value of QoS is C(unspecified) during the creation. + type: str + choices: [ level1, level2, level3, level4, level5, level6, 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_epg +- module: cisco.aci.aci_contract_export +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fv:RsCons) and B(fv:RsProv). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" +EXAMPLES = r""" +- name: Add a new consumed contract interface to EPG + cisco.aci.aci_epg_to_contract_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract_interface: anstest_http + state: present + delegate_to: localhost + +- name: Remove an existing consumed contract interface + cisco.aci.aci_epg_to_contract_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract_interface: anstest_http + state: absent + delegate_to: localhost + +- name: Query a specific consumed contract interface + cisco.aci.aci_epg_to_contract_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + contract_interface: anstest_http + state: query + delegate_to: localhost + register: query_result + +- name: Query all consumed contract interfaces + cisco.aci.aci_epg_to_contract_interface: + 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 + + +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 + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), # Not required for querying all objects + epg=dict(type="str", aliases=["epg_name"]), # Not required for querying all objects + contract_interface=dict(type="str", aliases=["contract_interface_name"]), # Not required for querying all objects + priority=dict(type="str", choices=["level1", "level2", "level3", "level4", "level5", "level6", "unspecified"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "ap", "epg", "contract_interface"]], + ["state", "present", ["tenant", "ap", "epg", "contract_interface"]], + ], + ) + + tenant = module.params.get("tenant") + ap = module.params.get("ap") + epg = module.params.get("epg") + contract_interface = module.params.get("contract_interface") + priority = module.params.get("priority") + state = module.params.get("state") + + 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="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="fvRsConsIf", + aci_rn="rsconsIf-{0}".format(contract_interface), + module_object=contract_interface, + target_filter={"name": contract_interface}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvRsConsIf", + class_config=dict( + prio=priority, + tnVzCPIfName=contract_interface, + ), + ) + + aci.get_diff(aci_class="fvRsConsIf") + + 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_epg_to_contract_master.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_contract_master.py new file mode 100644 index 000000000..0014f6cf1 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_contract_master.py @@ -0,0 +1,297 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Cindy Zhao <cizhao@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_to_contract_master +short_description: Manage End Point Group (EPG) contract master relationships (fv:RsSecInherited) +description: +- Manage End Point Groups (EPG) contract master relationships on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + required: true + ap: + description: + - Name of an existing application network profile, that will contain the EPGs. + type: str + required: true + aliases: [ app_profile, app_profile_name ] + epg: + description: + - Name of the end point group. + type: str + required: true + aliases: [ epg_name, name ] + contract_master_ap: + description: + - Name of the application profile where the contract master EPG is. + type: str + contract_master_epg: + description: + - Name of the end point group which serves as contract master. + 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 C(tenant) and C(app_profile) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_ap) modules can be used for this. +seealso: +- module: cisco.aci.aci_epg +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:AEPg). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Cindy Zhao (@cizhao) +""" + +EXAMPLES = r""" +- name: Add contract master + cisco.aci.aci_epg_to_contract_master: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: anstest + ap: apName + epg: epgName + contract_master_ap: ap + contract_master_epg: epg + state: present + delegate_to: localhost + +- name: Remove contract master + cisco.aci.aci_epg_to_contract_master: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: anstest + ap: apName + epg: epgName + contract_master_ap: ap + contract_master_epg: epg + state: absent + delegate_to: localhost + +- name: Query contract master + cisco.aci.aci_epg_to_contract_master: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: anstest + ap: apName + epg: epgName + contract_master_ap: ap + contract_master_epg: epg + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"], required=True), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"], required=True), + epg=dict(type="str", aliases=["epg_name", "name"], required=True), + contract_master_ap=dict(type="str"), + contract_master_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", "absent", ["contract_master_ap", "contract_master_epg"]], + ["state", "present", ["contract_master_ap", "contract_master_epg"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + ap = module.params.get("ap") + epg = module.params.get("epg") + contract_master_ap = module.params.get("contract_master_ap") + contract_master_epg = module.params.get("contract_master_epg") + state = module.params.get("state") + + contract_master = "uni/tn-{0}/ap-{1}/epg-{2}".format(tenant, contract_master_ap, contract_master_epg) + + child_configs = [] + + 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="fvRsSecInherited", + aci_rn="rssecInherited-[{0}]".format(contract_master), + module_object=contract_master, + target_filter={"tDn": contract_master}, + ), + child_classes=[], + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="fvRsSecInherited", class_config=dict(tDn=contract_master), child_configs=child_configs) + + aci.get_diff(aci_class="fvRsSecInherited") + + 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_epg_to_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_domain.py new file mode 100644 index 000000000..90f34cc29 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_domain.py @@ -0,0 +1,552 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Jacob McGill <jmcgill298> +# Copyright: (c) 2020, Shreyas Srish <ssrish@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_to_domain +short_description: Bind EPGs to Domains (fv:RsDomAtt) +description: +- Bind EPGs to Physical and Virtual Domains on Cisco ACI fabrics. +options: + allow_useg: + description: + - Allows micro-segmentation. + - The APIC defaults to C(encap) when unset during creation. + type: str + choices: [ encap, useg ] + ap: + description: + - Name of an existing application network profile, that will contain the EPGs. + type: str + aliases: [ app_profile, app_profile_name ] + deploy_immediacy: + description: + - Determines when the policy is pushed to hardware Policy CAM. + - The APIC defaults to C(lazy) when unset during creation. + type: str + choices: [ immediate, lazy ] + domain: + description: + - Name of the physical or virtual domain being associated with the EPG. + type: str + aliases: [ domain_name, domain_profile ] + domain_type: + description: + - Specify whether the Domain is a physical (phys), a virtual (vmm) or an L2 external domain association (l2dom). + type: str + choices: [ l2dom, phys, vmm ] + aliases: [ type ] + encap: + description: + - The VLAN encapsulation for the EPG when binding a VMM Domain with static C(encap_mode). + - This acts as the secondary encap when using useg. + - Accepted values range between C(1) and C(4096). + type: int + encap_mode: + description: + - The encapsulation method to be used. + - The APIC defaults to C(auto) when unset during creation. + - If vxlan is selected, switching_mode must be "AVE". + type: str + choices: [ auto, vlan, vxlan ] + switching_mode: + description: + - Switching Mode used by the switch + type: str + choices: [ AVE, native ] + default: native + epg: + description: + - Name of the end point group. + type: str + aliases: [ epg_name, name ] + enhanced_lag_policy: + description: + - Name of the VMM Domain Enhanced Lag Policy. + type: str + aliases: [ lag_policy ] + vmm_uplink_active: + description: + - A list of active uplink IDs. + - The order decides the order in which active uplinks take over for a failed uplink. + - At least one active uplink must remain specified in the list when an active uplink was previously configured. + type: list + elements: str + vmm_uplink_standby: + description: + - A list of standby uplink IDs. + - At least one standby uplink must remain specified in the list when no active uplink is configured. + type: list + elements: str + netflow: + description: + - Determines if netflow should be enabled. + - The APIC defaults to C(false) when unset during creation. + type: bool + primary_encap: + description: + - Determines the primary VLAN ID when using useg. + - Accepted values range between C(1) and C(4096). + type: int + resolution_immediacy: + description: + - Determines when the policies should be resolved and available. + - The APIC defaults to C(lazy) when unset during creation. + type: str + choices: [ immediate, lazy, pre-provision ] + 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 + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + promiscuous: + description: + - Allow/Disallow promiscuous mode in vmm domain + type: str + choices: [ accept, reject ] + default: reject + vm_provider: + description: + - The VM platform for VMM Domains. + - Support for Kubernetes was added in ACI v3.0. + - Support for CloudFoundry, OpenShift and Red Hat was added in ACI v3.1. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] + custom_epg_name: + description: + - The custom epg name in VMM domain association. + type: str + delimiter: + description: + - The delimiter. + type: str + choices: [ "|", "~", "!", "@", "^", "+", "=" ] + untagged_vlan: + description: + - The access vlan is untagged. + type: bool + port_binding: + description: + - The port binding method. + type: str + choices: [ dynamic, ephemeral, static ] + port_allocation: + description: + - The port allocation method. + type: str + choices: [ elastic, fixed ] + number_of_ports: + description: + - The number of ports. + type: int + forged_transmits: + description: + - Allow forged transmits. A forged transmit occurs when a network adapter starts sending out traffic that identifies itself as something else. + type: str + choices: [ accept, reject ] + default: reject + mac_changes: + description: + - Allows definition of new MAC addresses for the network adapter within the virtual machine (VM). + type: str + choices: [ accept, reject ] + default: reject +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant), C(ap), C(epg), and C(domain) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) M(cisco.aci.aci_ap), M(cisco.aci.aci_epg) M(cisco.aci.aci_domain) modules can be used for this. +- OpenStack VMM domains must not be created using this module. The OpenStack VMM domain is created directly + by the Cisco APIC Neutron plugin as part of the installation and configuration. + This module can be used to query status of an OpenStack VMM domain. +seealso: +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_epg +- module: cisco.aci.aci_domain +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:RsDomAtt). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new physical domain to EPG binding + cisco.aci.aci_epg_to_domain: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + domain: anstest + domain_type: phys + state: present + delegate_to: localhost + +- name: Remove an existing physical domain to EPG binding + cisco.aci.aci_epg_to_domain: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + domain: anstest + domain_type: phys + state: absent + delegate_to: localhost + +- name: Query a specific physical domain to EPG binding + cisco.aci.aci_epg_to_domain: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + domain: anstest + domain_type: phys + state: query + delegate_to: localhost + register: query_result + +- name: Query all domain to EPG bindings + cisco.aci.aci_epg_to_domain: + 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 + +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + allow_useg=dict(type="str", choices=["encap", "useg"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), # Not required for querying all objects + deploy_immediacy=dict(type="str", choices=["immediate", "lazy"]), + domain=dict(type="str", aliases=["domain_name", "domain_profile"]), # Not required for querying all objects + domain_type=dict(type="str", choices=["l2dom", "phys", "vmm"], aliases=["type"]), # Not required for querying all objects + encap=dict(type="int"), + encap_mode=dict(type="str", choices=["auto", "vlan", "vxlan"]), + switching_mode=dict(type="str", default="native", choices=["AVE", "native"]), + epg=dict(type="str", aliases=["name", "epg_name"]), # Not required for querying all objects + enhanced_lag_policy=dict(type="str", aliases=["lag_policy"]), + vmm_uplink_active=dict(type="list", elements="str"), + vmm_uplink_standby=dict(type="list", elements="str"), + netflow=dict(type="bool"), + primary_encap=dict(type="int"), + resolution_immediacy=dict(type="str", choices=["immediate", "lazy", "pre-provision"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + vm_provider=dict(type="str", choices=["cloudfoundry", "kubernetes", "microsoft", "openshift", "openstack", "redhat", "vmware"]), + promiscuous=dict(type="str", default="reject", choices=["accept", "reject"]), + custom_epg_name=dict(type="str"), + delimiter=dict(type="str", choices=["|", "~", "!", "@", "^", "+", "="]), + untagged_vlan=dict(type="bool"), + port_binding=dict(type="str", choices=["dynamic", "ephemeral", "static"]), + port_allocation=dict(type="str", choices=["elastic", "fixed"]), + number_of_ports=dict(type="int"), + forged_transmits=dict(type="str", default="reject", choices=["accept", "reject"]), + mac_changes=dict(type="str", default="reject", choices=["accept", "reject"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["domain_type", "vmm", ["vm_provider"]], + ["state", "absent", ["ap", "domain", "domain_type", "epg", "tenant"]], + ["state", "present", ["ap", "domain", "domain_type", "epg", "tenant"]], + ], + ) + + aci = ACIModule(module) + + allow_useg = module.params.get("allow_useg") + ap = module.params.get("ap") + deploy_immediacy = module.params.get("deploy_immediacy") + domain = module.params.get("domain") + domain_type = module.params.get("domain_type") + vm_provider = module.params.get("vm_provider") + promiscuous = module.params.get("promiscuous") + custom_epg_name = module.params.get("custom_epg_name") + encap = module.params.get("encap") + if encap is not None: + if encap in range(1, 4097): + encap = "vlan-{0}".format(encap) + else: + module.fail_json(msg="Valid VLAN assignments are from 1 to 4096") + encap_mode = module.params.get("encap_mode") + switching_mode = module.params.get("switching_mode") + epg = module.params.get("epg") + enhanced_lag_policy = module.params.get("enhanced_lag_policy") + vmm_uplink_active = module.params.get("vmm_uplink_active") + vmm_uplink_standby = module.params.get("vmm_uplink_standby") + netflow = aci.boolean(module.params.get("netflow"), "enabled", "disabled") + primary_encap = module.params.get("primary_encap") + if primary_encap is not None: + if primary_encap in range(1, 4097): + primary_encap = "vlan-{0}".format(primary_encap) + else: + module.fail_json(msg="Valid VLAN assignments are from 1 to 4096") + resolution_immediacy = module.params.get("resolution_immediacy") + state = module.params.get("state") + tenant = module.params.get("tenant") + + if domain_type in ["l2dom", "phys"] and vm_provider is not None: + module.fail_json(msg="Domain type '%s' cannot have a 'vm_provider'" % domain_type) + + delimiter = module.params.get("delimiter") + untagged_vlan = "yes" if module.params.get("untagged_vlan") is True else "no" + port_binding = module.params.get("port_binding") + if port_binding == "static" or port_binding == "dynamic": + port_binding = "{0}Binding".format(port_binding) + port_allocation = module.params.get("port_allocation") + number_of_ports = module.params.get("number_of_ports") + forged_transmits = module.params.get("forged_transmits") + mac_changes = module.params.get("mac_changes") + + child_classes = None + child_configs = None + + # Compile the full domain for URL building + if domain_type == "vmm": + epg_domain = "uni/vmmp-{0}/dom-{1}".format(VM_PROVIDER_MAPPING[vm_provider], domain) + child_configs = [dict(vmmSecP=dict(attributes=dict(allowPromiscuous=promiscuous, forgedTransmits=forged_transmits, macChanges=mac_changes)))] + # check with child classes added on all versions + child_classes = ["vmmSecP"] + + if vmm_uplink_active is not None or vmm_uplink_standby is not None: + uplink_order_cont = dict(fvUplinkOrderCont=dict(attributes=dict())) + if vmm_uplink_active is not None: + uplink_order_cont["fvUplinkOrderCont"]["attributes"]["active"] = ",".join(vmm_uplink_active) + if vmm_uplink_standby is not None: + uplink_order_cont["fvUplinkOrderCont"]["attributes"]["standby"] = ",".join(vmm_uplink_standby) + child_configs.append(uplink_order_cont) + child_classes.append("fvUplinkOrderCont") + + if enhanced_lag_policy is not None: + lag_policy = epg_domain + "/vswitchpolcont/enlacplagp-{0}".format(enhanced_lag_policy) + child_configs.append( + dict( + fvAEPgLagPolAtt=dict( + attributes=dict(annotation=""), children=[dict(fvRsVmmVSwitchEnhancedLagPol=dict(attributes=dict(annotation="", tDn=lag_policy)))] + ) + ) + ) + child_classes.append("fvAEPgLagPolAtt") + + elif domain_type == "l2dom": + epg_domain = "uni/l2dom-{0}".format(domain) + elif domain_type == "phys": + epg_domain = "uni/phys-{0}".format(domain) + else: + epg_domain = None + + 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="fvRsDomAtt", + aci_rn="rsdomAtt-[{0}]".format(epg_domain), + module_object=epg_domain, + target_filter={"tDn": epg_domain}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvRsDomAtt", + class_config=dict( + classPref=allow_useg, + encap=encap, + encapMode=encap_mode, + switchingMode=switching_mode, + instrImedcy=deploy_immediacy, + netflowPref=netflow, + primaryEncap=primary_encap, + resImedcy=resolution_immediacy, + customEpgName=custom_epg_name, + delimiter=delimiter, + untagged=untagged_vlan, + bindingType=port_binding, + portAllocation=port_allocation, + numPorts=number_of_ports, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fvRsDomAtt") + + 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_esg.py b/ansible_collections/cisco/aci/plugins/modules/aci_esg.py new file mode 100644 index 000000000..e4cdbb9c1 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_esg.py @@ -0,0 +1,374 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + + +DOCUMENTATION = r""" +--- +module: aci_esg +short_description: Manage Endpoint Security Groups (ESGs) objects (fv:ESg) +description: +- Manage Endpoint Security Groups (ESGs) on Cisco ACI fabrics. + +options: + tenant: + description: + - Name of the tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of the application profile. + type: str + aliases: [ app_profile, app_profile_name ] + esg: + description: + - Name of the Endpoint Security Group. + type: str + aliases: [ esg_name, name ] + admin_state: + description: + - Use C(false) to set 'Admin Up' on the ESG Admin state and is the default. + - Use C(true) to set 'Admin Shut' on the ESG Admin state + type: bool + choices: [ false, true ] + vrf: + description: + - Name of the VRF + type: str + aliases: [ vrf_name ] + description: + description: + - Endpoint security group description. + type: str + aliases: [ descr ] + intra_esg_isolation: + description: + - The default value of Intra ESG Isolation is C(unenforced). + type: str + choices: [ enforced, unenforced ] + preferred_group_member: + description: + - The default value of Preferred Group Member is C(exclude). + type: str + choices: [ exclude, include ] + 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 + +seealso: +- module: cisco.aci.aci_aep_to_domain +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:AttEntityP) and B(infra:ProvAcc). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + + +EXAMPLES = r""" +- name: Add a new ESG + cisco.aci.aci_esg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: intranet + esg: web_esg + vrf: 'default' + description: Web Intranet ESG + state: present + delegate_to: localhost + +- name: Add list of ESGs + cisco.aci.aci_esg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: ticketing + esg: "{{ item.esg }}" + description: Ticketing ESG + vrf: 'default' + state: present + delegate_to: localhost + with_items: + - esg: web + - esg: database + +- name: Query an ESG + cisco.aci.aci_esg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: ticketing + esg: web_esg + state: query + delegate_to: localhost + +- name: Query all ESGs + cisco.aci.aci_esg: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Query all ESGs with a Specific Name + cisco.aci.aci_esg: + host: apic + username: admin + password: SomeSecretPassword + esg: web_esg + state: query + delegate_to: localhost + +- name: Query all ESGs of an App Profile + cisco.aci.aci_esg: + host: apic + username: admin + password: SomeSecretPassword + ap: ticketing + state: query + delegate_to: localhost + +- name: Remove an ESG + cisco.aci.aci_esg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + app_profile: intranet + esg: web_esg + 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( + tenant=dict(type="str", aliases=["tenant_name"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), + esg=dict(type="str", aliases=["name", "esg_name"]), + admin_state=dict(type="bool", choices=[False, True]), # ESG Admin State + vrf=dict(type="str", aliases=["vrf_name"]), # ESG VRF name + description=dict(type="str", aliases=["descr"]), + intra_esg_isolation=dict( + type="str", + choices=["enforced", "unenforced"], + ), # Intra ESG Isolation + preferred_group_member=dict(type="str", choices=["exclude", "include"]), # Preferred Group Member + 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", "ap", "esg"]], + ["state", "present", ["tenant", "ap", "esg", "vrf"]], + ], + ) + + aci = ACIModule(module) + tenant = module.params.get("tenant") + ap = module.params.get("ap") + esg = module.params.get("esg") + admin_state = module.params.get("admin_state") + vrf = module.params.get("vrf") + description = module.params.get("description") + intra_esg_isolation = module.params.get("intra_esg_isolation") + preferred_group_member = module.params.get("preferred_group_member") + state = module.params.get("state") + 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="fvESg", + aci_rn="esg-{0}".format(esg), + module_object=esg, + target_filter={"name": esg}, + ), + child_classes=[ + "fvRsScope", + ], + ) + + aci.get_existing() + + if state == "present": + state_mapping = {True: "yes", False: "no"} + shutdown = state_mapping.get(admin_state) + # VRF Selection - fvRsScope + aci.payload( + aci_class="fvESg", + class_config=dict( + name=esg, + descr=description, + shutdown=shutdown, + pcEnfPref=intra_esg_isolation, + prefGrMemb=preferred_group_member, + nameAlias=name_alias, + ), + child_configs=[dict(fvRsScope=dict(attributes=dict(tnFvCtxName=vrf)))], + ) + + aci.get_diff(aci_class="fvESg") + + 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_esg_contract_master.py b/ansible_collections/cisco/aci/plugins/modules/aci_esg_contract_master.py new file mode 100644 index 000000000..2641f4dc6 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_esg_contract_master.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_esg_contract_master +short_description: Manage ESG contract master relationships (fv:RsSecInherited) +description: +- Manage Endpoint Security Groups (ESG) contract master relationships on Cisco ACI fabrics. + +options: + tenant: + description: + - Name of the tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of the application profile. + type: str + aliases: [ app_profile, app_profile_name ] + esg: + description: + - Name of the Endpoint Security Group. + type: str + aliases: [ esg_name ] + contract_master_ap: + description: + - Name of the application profile where the contract master ESG is. + type: str + contract_master_esg: + description: + - Name of the ESG which serves as contract master. + 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 + +seealso: +- module: cisco.aci.aci_esg +- name: Manage Endpoint Security Groups (ESGs) objects (fv:ESg) + description: Manage Endpoint Security Groups (ESGs) on Cisco ACI fabrics. + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Add an ESG contract master + cisco.aci.aci_esg_contract_master: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: anstest + ap: apName + esg: esgName + contract_master_ap: ap + contract_master_esg: contract_esg + state: present + delegate_to: localhost + +- name: Query an ESG contract master + cisco.aci.aci_esg_contract_master: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: anstest + ap: apName + esg: esgName + contract_master_ap: ap + contract_master_esg: contract_esg + state: query + delegate_to: localhost + +- name: Remove an ESG contract master + cisco.aci.aci_esg_contract_master: + host: apic_host + username: admin + password: SomeSecretPassword + tenant: anstest + ap: apName + esg: esgName + contract_master_ap: ap + contract_master_esg: contract_esg + 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( + tenant=dict(type="str", aliases=["tenant_name"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), + esg=dict(type="str", aliases=["esg_name"]), + contract_master_ap=dict(type="str"), + contract_master_esg=dict(type="str"), + 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", "ap", "esg", "contract_master_ap", "contract_master_esg"]], + ["state", "present", ["tenant", "ap", "esg", "contract_master_ap", "contract_master_esg"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + ap = module.params.get("ap") + esg = module.params.get("esg") + contract_master_ap = module.params.get("contract_master_ap") + contract_master_esg = module.params.get("contract_master_esg") + state = module.params.get("state") + + contract_master = None + if contract_master_ap is not None and contract_master_esg is not None: + contract_master = "uni/tn-{0}/ap-{1}/esg-{2}".format(tenant, contract_master_ap, contract_master_esg) + + 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="fvESg", + aci_rn="esg-{0}".format(esg), + module_object=esg, + target_filter={"name": esg}, + ), + subclass_3=dict( + aci_class="fvRsSecInherited", + aci_rn="rssecInherited-[{0}]".format(contract_master), + module_object=contract_master, + target_filter={"tDn": contract_master}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="fvRsSecInherited", class_config=dict(tDn=contract_master)) + + aci.get_diff(aci_class="fvRsSecInherited") + + 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_esg_epg_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_esg_epg_selector.py new file mode 100644 index 000000000..5ad3d344b --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_esg_epg_selector.py @@ -0,0 +1,348 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + + +DOCUMENTATION = r""" +--- +module: aci_esg_epg_selector +short_description: Manage ESG - EPG Selectors (fv:fvEPgSelector) +description: +- Manage Endpoint Security Groups - EPG Selectors on Cisco ACI fabrics. + +options: + tenant: + description: + - Name of the tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of the application profile. + type: str + aliases: [ app_profile, app_profile_name ] + esg: + description: + - Name of the Endpoint Security Group. + type: str + aliases: [ esg_name ] + epg_ap: + description: + - Name of the Application profile which contains the EPG. + type: str + epg: + description: + - Name of the EPG which is used to create EPG Selector object under the ESG. + type: str + aliases: [ epg_name ] + description: + description: + - Description of the ESG Tag Selector. + type: str + aliases: [ epg_selector_description ] + 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 + +seealso: +- module: cisco.aci.aci_esg +- name: Manage Endpoint Security Groups (ESGs) objects (fv:ESg) + description: Manage Endpoint Security Groups (ESGs) on Cisco ACI fabrics. + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + + +EXAMPLES = r""" +- name: Add an EPG selector + cisco.aci.aci_esg_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: web_esg + epg_ap: production_ap1 + epg: production_ap1-epg + description: epg-test-description + state: present + delegate_to: localhost + +- name: Add list of EPG selectors + cisco.aci.aci_esg_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: "{{ item.esg }}" + epg_ap: "{{ item.epg_ap }}" + epg: "{{ item.epg }}" + description: epg-test-description + state: present + delegate_to: localhost + with_items: + - {"epg_ap": "production_ap1", "epg": "epg-test1", "esg": "web_esg"} + - {"epg_ap": "production_ap1", "epg": "epg-test2", "esg": "web_esg"} + +- name: Query an EPG selector with esg and epg name + cisco.aci.aci_esg_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: web_esg + epg_ap: production_ap1 + epg: production_ap1-epg + state: query + delegate_to: localhost + +- name: Query all EPG selectors under a application profile + cisco.aci.aci_esg_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + state: query + delegate_to: localhost + +- name: Query all EPG selectors + cisco.aci.aci_esg_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove an EPG selector + cisco.aci.aci_esg_epg_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: web_esg + epg_ap: production_ap1 + epg: production_ap1-epg + 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( + tenant=dict(type="str", aliases=["tenant_name"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), + esg=dict(type="str", aliases=["esg_name"]), + epg_ap=dict(type="str"), + epg=dict(type="str", aliases=["epg_name"]), + description=dict(type="str", aliases=["epg_selector_description"]), + 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", "ap", "esg", "epg", "epg_ap"]], + ["state", "present", ["tenant", "ap", "esg", "epg", "epg_ap"]], + ], + ) + + aci = ACIModule(module) + tenant = module.params.get("tenant") + ap = module.params.get("ap") + esg = module.params.get("esg") + epg_ap = module.params.get("epg_ap") + epg = module.params.get("epg") + description = module.params.get("description") + state = module.params.get("state") + + matchEpgDn = "uni/tn-{0}/ap-{1}/epg-{2}".format(tenant, epg_ap, epg) + epgselector = "epgselector-[{0}]".format(matchEpgDn) + 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="fvESg", + aci_rn="esg-{0}".format(esg), + module_object=esg, + target_filter={"name": esg}, + ), + subclass_3=dict( + aci_class="fvEPgSelector", + aci_rn=epgselector, + module_object=epg, + target_filter={"matchEpgDn": epg}, + ), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvEPgSelector", + class_config=dict( + matchEpgDn=matchEpgDn, + descr=description, + ), + ) + + aci.get_diff(aci_class="fvEPgSelector") + + 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_esg_ip_subnet_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_esg_ip_subnet_selector.py new file mode 100644 index 000000000..368f9e6dc --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_esg_ip_subnet_selector.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + + +DOCUMENTATION = r""" +--- +module: aci_esg_ip_subnet_selector +short_description: Manage ESG IP Subnet selector(fv:EPSelector) +description: +- Manage Endpoint Security Groups (ESG) IP Subnet selector on Cisco ACI fabrics. + +options: + tenant: + description: + - Name of the tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of the application profile. + type: str + aliases: [ app_profile, app_profile_name ] + esg: + description: + - Name of the Endpoint Security Group. + type: str + aliases: [ esg_name ] + ip: + description: + - IP address of the subnet selector. + type: str + aliases: [ subnet ] + description: + description: + - Description of the ESG IP Subnet selector. + type: str + aliases: [ ip_subnet_selector_description ] + 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 + +seealso: +- module: cisco.aci.aci_esg +- name: Manage Endpoint Security Groups (ESGs) objects (fv:ESg) + description: Manage Endpoint Security Groups (ESGs) on Cisco ACI fabrics. + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + + +EXAMPLES = r""" +- name: Add an IP subnet selector + cisco.aci.aci_esg_tag_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: web_esg + ip: "10.0.0.0" + description: "IP Subnet Selector Description" + state: present + delegate_to: localhost + +- name: Query all IP subnet selector + cisco.aci.aci_esg_tag_selector: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove an IP subnet selector + cisco.aci.aci_esg_tag_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: web_esg + ip: "10.0.0.0" + 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( + tenant=dict(type="str", aliases=["tenant_name"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), + esg=dict(type="str", aliases=["esg_name"]), + ip=dict(type="str", aliases=["subnet"]), + description=dict(type="str", aliases=["ip_subnet_selector_description"]), + 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", "ap", "esg", "ip"]], + ["state", "present", ["tenant", "ap", "esg", "ip"]], + ], + ) + + aci = ACIModule(module) + tenant = module.params.get("tenant") + ap = module.params.get("ap") + esg = module.params.get("esg") + ip = module.params.get("ip") + description = module.params.get("description") + state = module.params.get("state") + + match_expression = "ip=='{0}'".format(ip) + subnet_selector_rn = "epselector-[{0}]".format(match_expression) + 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="fvESg", + aci_rn="esg-{0}".format(esg), + module_object=esg, + target_filter={"name": esg}, + ), + subclass_3=dict( + aci_class="fvEPSelector", + aci_rn=subnet_selector_rn, + module_object=ip, + target_filter={}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvEPSelector", + class_config=dict( + matchExpression=match_expression, + descr=description, + ), + ) + + aci.get_diff(aci_class="fvEPSelector") + + 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_esg_tag_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_esg_tag_selector.py new file mode 100644 index 000000000..834249b49 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_esg_tag_selector.py @@ -0,0 +1,344 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Sabari Jaganathan (@sajagana) <sajagana@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + + +DOCUMENTATION = r""" +--- +module: aci_esg_tag_selector +short_description: Manage ESG Tag Selectors (fv:TagSelector) +description: +- Manage Endpoint Security Groups Tag Selectors on Cisco ACI fabrics. +options: + tenant: + description: + - Name of the tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of the application profile. + type: str + aliases: [ app_profile, app_profile_name ] + esg: + description: + - Name of the Endpoint Security Group. + type: str + aliases: [ esg_name ] + name: + description: + - ESG Tag Selector Key Name + type: str + aliases: [ match_key ] + operator: + description: + - C(equals) is the default operator type of the ESG Tag Selector. + - C(contains) is used to match values partially. + - C(regex) allows to pass patterns values. + type: str + choices: [ contains, equals, regex ] + aliases: [ value_operator ] + match_value: + description: + - Value filed of the ESG Tag Selector. + type: str + description: + description: + - Description of the ESG Tag Selector. + type: str + aliases: [ tag_selector_description ] + 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 + +seealso: +- module: cisco.aci.aci_esg +- name: Manage Endpoint Security Groups (ESGs) objects (fv:ESg) + description: Manage Endpoint Security Groups (ESGs) on Cisco ACI fabrics. + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Add a new Tag Selector + cisco.aci.aci_esg_tag_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: web_esg + name: tag-selector-key + match_value: tag-selector-value + description: tag-selector-description + state: present + delegate_to: localhost + +- name: Add list of Tag Selectors + cisco.aci.aci_esg_tag_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: web_esg + name: "{{ item.key }}" + match_value: "{{ item.value }}" + description: "{{ item.description }}" + state: present + delegate_to: localhost + with_items: + - {key: tag_selector_key_0, value: tag_selector_value_0, description: tag_selector_description_0} + - {key: tag_selector_key_1, value: tag_selector_value_1, description: tag_selector_description_1} + +- name: Query all Tag Selectors + cisco.aci.aci_esg_tag_selector: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Query all Tag Selectors with name and value + cisco.aci.aci_esg_tag_selector: + host: apic + username: admin + password: SomeSecretPassword + name: tag-selector-key + match_value: tag-selector-value + state: query + delegate_to: localhost + +- name: Remove a Tag Selectors + cisco.aci.aci_esg_tag_selector: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: production_ap + esg: web_esg + name: tag-selector-key + match_value: tag-selector-value + 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( + tenant=dict(type="str", aliases=["tenant_name"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), + esg=dict(type="str", aliases=["esg_name"]), + name=dict(type="str", aliases=["match_key"]), # ESG Tag Selector key name + operator=dict(type="str", choices=["contains", "equals", "regex"], aliases=["value_operator"]), # ESG Tag Selector operator type + match_value=dict(type="str"), # ESG Tag Selector match value + description=dict(type="str", aliases=["tag_selector_description"]), + 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", "ap", "esg", "name", "match_value"]], + ["state", "present", ["tenant", "ap", "esg", "name", "match_value"]], + ], + ) + + aci = ACIModule(module) + tenant = module.params.get("tenant") + ap = module.params.get("ap") + esg = module.params.get("esg") + name = module.params.get("name") + operator = module.params.get("operator") + match_value = module.params.get("match_value") + description = module.params.get("description") + state = module.params.get("state") + + 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="fvESg", + aci_rn="esg-{0}".format(esg), + module_object=esg, + target_filter={"name": esg}, + ), + subclass_3=dict( + aci_class="fvTagSelector", + aci_rn="tagselectorkey-[{0}]-value-[{1}]".format(name, match_value), + module_object=name, + target_filter={"matchKey": name, "matchValue": match_value}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvTagSelector", + class_config=dict( + matchKey=name, + matchValue=match_value, + valueOperator=operator, + descr=description, + ), + ) + + aci.get_diff(aci_class="fvTagSelector") + + 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_leaf_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_leaf_profile.py new file mode 100644 index 000000000..09c87a387 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_leaf_profile.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_leaf_profile +short_description: Manage fabric leaf profiles (fabric:LeafP). +description: +- Manage fabric leaf switch profiles in an ACI fabric. +options: + name: + description: + - Name of the fabric leaf switch profile + type: str + aliases: [ leaf_profile, leaf_switch_profile ] + description: + description: + - description of the 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 +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(fabricLeafP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a leaf switch profile + cisco.aci.aci_fabric_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + name: my_leaf_profile + state: present + delegate_to: localhost + +- name: Remove a leaf switch profile + cisco.aci.aci_fabric_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + name: my_leaf_profile + state: absent + delegate_to: localhost + +- name: Query a leaf profile + cisco.aci.aci_fabric_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + name: my_leaf_profile + state: query + delegate_to: localhost + register: query_result + +- name: Query all leaf profiles + cisco.aci.aci_fabric_leaf_profile: + 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_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 + + +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=["leaf_switch_profile", "leaf_profile"]), + description=dict(type="str", aliases=["descr"]), + 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") + child_classes = ["fabricLeafS"] + + aci.construct_url( + root_class=dict( + aci_class="fabricLeafP", + aci_rn="fabric/leprof-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricLeafP", + class_config=dict(name=name, descr=description), + ) + + aci.get_diff(aci_class="fabricLeafP") + + 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_leaf_switch_assoc.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_leaf_switch_assoc.py new file mode 100644 index 000000000..c979f0398 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_leaf_switch_assoc.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_leaf_switch_assoc +short_description: Manage leaf switch bindings to profiles and policy groups (fabric:LeafS and fabric:RsLeNodePGrp). +description: +- Manage fabric leaf switch associations (fabric:LeafS) to an existing fabric + leaf profile (fabric:leafP) in an ACI fabric, and bind them to a + policy group (fabric:RsLeNodePGrp) +options: + profile: + description: + - Name of an existing fabric leaf switch profile + type: str + aliases: [ leaf_profile, leaf_switch_profile ] + name: + description: + - Name of the switch association + type: str + aliases: [ association_name, switch_association ] + policy_group: + description: + - Name of an existing leaf switch policy group + type: str + description: + description: + - Description of the Fabric Switch Association + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_leaf_profile) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fabricLeafS) and B(fabricRsLeNodePGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a leaf switch profile association + cisco.aci.aci_fabric_leaf_switch_assoc: + host: apic + username: admin + password: SomeSecretPassword + profile: my_leaf_profile + name: my_leaf_switch_assoc + policy_group: my_leaf_pol_grp + state: present + delegate_to: localhost +- name: Remove a leaf switch profile association + cisco.aci.aci_fabric_leaf_switch_assoc: + host: apic + username: admin + password: SomeSecretPassword + profile: my_leaf_profile + name: my_leaf_switch_assoc + state: absent + delegate_to: localhost +- name: Query a leaf profile association + cisco.aci.aci_fabric_leaf_switch_assoc: + host: apic + username: admin + password: SomeSecretPassword + profile: my_leaf_profile + name: my_leaf_switch_assoc + state: query + delegate_to: localhost + register: query_result +- name: Query all leaf profiles + cisco.aci.aci_fabric_leaf_switch_assoc: + 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_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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + profile=dict(type="str", aliases=["leaf_profile", "leaf_switch_profile"]), + name=dict(type="str", aliases=["association_name", "switch_association"]), + policy_group=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["profile", "name"]], + ["state", "present", ["profile", "name"]], + ], + ) + + aci = ACIModule(module) + + profile = module.params.get("profile") + name = module.params.get("name") + policy_group = module.params.get("policy_group") + description = module.params.get("description") + state = module.params.get("state") + child_classes = ["fabricRsLeNodePGrp", "fabricNodeBlk"] + + aci.construct_url( + root_class=dict( + aci_class="fabricLeafP", + aci_rn="fabric/leprof-{0}".format(profile), + module_object=profile, + target_filter={"name": profile}, + ), + subclass_1=dict( + aci_class="fabricLeafS", + aci_rn="leaves-{0}-typ-range".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if policy_group: + tDn = "uni/fabric/funcprof/lenodepgrp-{0}".format(policy_group) + child_configs.append(dict(fabricRsLeNodePGrp=dict(attributes=dict(tDn=tDn)))) + aci.payload( + aci_class="fabricLeafS", + class_config=dict(name=name, descr=description), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fabricLeafS") + + 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_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py new file mode 100644 index 000000000..defa4f783 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_node.py @@ -0,0 +1,290 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.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 +short_description: Manage Fabric Node Members (fabric:NodeIdentP) +description: +- Manage Fabric Node Members on Cisco ACI fabrics. +options: + pod_id: + description: + - The pod id of the new Fabric Node Member. + type: int + serial: + description: + - Serial Number for the new Fabric Node Member. + type: str + aliases: [ serial_number ] + node_id: + description: + - Node ID Number for the new Fabric Node Member. + type: int + switch: + description: + - Switch Name for the new Fabric Node Member. + type: str + aliases: [ name, switch_name ] + description: + description: + - Description for the new Fabric Node Member. + type: str + aliases: [ descr ] + role: + description: + - Role for the new Fabric Node Member. + type: str + aliases: [ role_name ] + choices: [ leaf, spine, 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 + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:NodeIdentP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +""" + +EXAMPLES = r""" +- name: Add fabric node + cisco.aci.aci_fabric_node: + host: apic + username: admin + password: SomeSecretPassword + serial: FDO2031124L + node_id: 1011 + switch: fab4-sw1011 + state: present + delegate_to: localhost + +- name: Remove fabric node + cisco.aci.aci_fabric_node: + host: apic + username: admin + password: SomeSecretPassword + serial: FDO2031124L + node_id: 1011 + state: absent + delegate_to: localhost + +- name: Query fabric nodes + cisco.aci.aci_fabric_node: + 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 + + +# NOTE: (This problem is also present on the APIC GUI) +# NOTE: When specifying a C(role) the new Fabric Node Member will be created but Role on GUI will be "unknown", hence not what seems to be a module problem + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + description=dict(type="str", aliases=["descr"]), + 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"]), + 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"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["node_id", "serial"]], + ["state", "present", ["node_id", "serial"]], + ], + ) + + pod_id = module.params.get("pod_id") + serial = module.params.get("serial") + node_id = module.params.get("node_id") + switch = module.params.get("switch") + description = module.params.get("description") + role = module.params.get("role") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fabricNodeIdentP", + aci_rn="controller/nodeidentpol/nodep-{0}".format(serial), + module_object=serial, + target_filter={"serial": serial}, + ) + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricNodeIdentP", + class_config=dict( + descr=description, + name=switch, + 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, + serial=serial, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fabricNodeIdentP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json(**aci.result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_policy_group.py new file mode 100644 index 000000000..59d7ef7df --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_policy_group.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_pod_policy_group +short_description: Manage Fabric Pod Policy Groups (fabric:PodPGrp) +description: +- Fabric Pod Policy Group (fabric:PodPGrp) configuration on Cisco ACI fabrics. +options: + name: + description: + - Name of the policy group + type: str + aliases: [ policy_group, policy_group_name, pod_policy_group ] + date_time_policy: + description: + - NTP policy to bind to the policy group + type: str + aliases: [ ntp_policy ] + isis_policy: + description: + - IS-IS policy to bind to the policy group + type: str + coop_group_policy: + description: + - COOP group policy to bind to the policy group + type: str + aliases: [ coop_policy ] + bgp_rr_policy: + description: + - BGP route reflector policy to bind to the policy group + type: str + management_access_policy: + description: + - Management access policy to bind to the policy group + type: str + aliases: [ management_policy, mgmt_policy ] + snmp_policy: + description: + - SNMP policy to bind to the policy group + type: str + macsec_policy: + description: + - MACSec policy to bind to the policy group + type: str + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + 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 + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabricPodPGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new fabric pod policy group + cisco.aci.aci_fabric_pod_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_pod_pol_grp + snmp_policy: my_snmp_pol + bgp_rr_policy: my_bgp_rr_pol + state: present + delegate_to: localhost + +- name: Remove a fabric pod policy group + cisco.aci.aci_fabric_pod_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_pod_pol_grp + state: absent + delegate_to: localhost + +- name: Query a fabric pod policy group + cisco.aci.aci_fabric_pod_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_pod_pol_grp + state: query + delegate_to: localhost + register: query_result + +- name: Query all fabric pod policy groups + cisco.aci.aci_fabric_pod_policy_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 "/></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", "policy_group_name", "pod_policy_group"]), + date_time_policy=dict(type="str", aliases=["ntp_policy"]), + isis_policy=dict(type="str"), + coop_group_policy=dict(type="str", aliases=["coop_policy"]), + bgp_rr_policy=dict(type="str"), + management_access_policy=dict(type="str", aliases=["management_policy", "mgmt_policy"]), + snmp_policy=dict(type="str"), + macsec_policy=dict(type="str"), + 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", ["name"]], + ["state", "present", ["name"]], + ], + ) + aci = ACIModule(module) + + name = module.params.get("name") + date_time_policy = module.params.get("date_time_policy") + isis_policy = module.params.get("isis_policy") + coop_group_policy = module.params.get("coop_group_policy") + bgp_rr_policy = module.params.get("bgp_rr_policy") + management_access_policy = module.params.get("management_access_policy") + snmp_policy = module.params.get("snmp_policy") + macsec_policy = module.params.get("macsec_policy") + name_alias = module.params.get("name_alias") + state = module.params.get("state") + child_classes = [ + "fabricRsSnmpPol", + "fabricRsPodPGrpIsisDomP", + "fabricRsPodPGrpCoopP", + "fabricRsPodPGrpBGPRRP", + "fabricRsTimePol", + "fabricRsMacsecPol", + "fabricRsCommPol", + ] + + aci.construct_url( + root_class=dict( + aci_class="fabricPodPGrp", + aci_rn="fabric/funcprof/podpgrp-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if date_time_policy is not None: + child_configs.append(dict(fabricRsTimePol=dict(attributes=dict(tnDatetimePolName=date_time_policy)))) + if isis_policy is not None: + child_configs.append(dict(fabricRsPodPGrpIsisDomP=dict(attributes=dict(tnIsisDomPolName=isis_policy)))) + if coop_group_policy is not None: + child_configs.append(dict(fabricRsPodPGrpCoopP=dict(attributes=dict(tnCoopPolName=coop_group_policy)))) + if bgp_rr_policy is not None: + child_configs.append(dict(fabricRsPodPGrpBGPRRP=dict(attributes=dict(tnBgpInstPolName=bgp_rr_policy)))) + if management_access_policy is not None: + child_configs.append(dict(fabricRsCommPol=dict(attributes=dict(tnCommPolName=management_access_policy)))) + if snmp_policy is not None: + child_configs.append(dict(fabricRsSnmpPol=dict(attributes=dict(tnSnmpPolName=snmp_policy)))) + if macsec_policy is not None: + child_configs.append(dict(fabricRsMacsecPol=dict(attributes=dict(tnMacsecFabIfPolName=macsec_policy)))) + aci.payload( + aci_class="fabricPodPGrp", + class_config=dict(name=name, nameAlias=name_alias), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fabricPodPGrp") + + 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 new file mode 100644 index 000000000..c0297d846 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_scheduler.py @@ -0,0 +1,353 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = """ +--- +module: aci_fabric_scheduler + +short_description: This modules creates ACI schedulers. + + +description: + - With the module you can create schedule policies that can be a shell, onetime execution or recurring + +options: + name: + description: + - The name of the Scheduler. + type: str + aliases: [ scheduler_name ] + description: + description: + - Description for the Scheduler. + type: str + 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 + type: bool + windowname: + description: + - This is the name for your what recurring or oneTime execution + type: str + concurCap: + description: + - This is 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 + type: str + date: + description: + - This is the date and time that the scheduler will execute + type: str + hour: + description: + - This set the hour of execution + type: int + minute: + description: + - This sets the minute of execution, used in conjunction with hour + type: int + day: + description: + - This sets the day when execution will take place + type: str + 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. + 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. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +author: + - 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 +""" + +RETURN = """ +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_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 + + +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=["scheduler_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + windowname=dict(type="str"), + recurring=dict(type="bool"), + concurCap=dict(type="int"), # Number of devices it will run against concurrently + maxTime=dict(type="str"), # The amount of minutes a process will be able to run (unlimited or dd:hh:mm:ss) + date=dict(type="str"), # The date the process will run YYYY-MM-DDTHH:MM:SS + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + hour=dict(type="int"), + minute=dict(type="int"), + day=dict( + type="str", + default="every-day", + choices=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", "every-day", "even-day", "odd-day"], + ), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name"]], + ["state", "present", ["name"]], + ], + ) + + state = module.params.get("state") + name = module.params.get("name") + windowname = module.params.get("windowname") + recurring = module.params.get("recurring") + date = module.params.get("date") + hour = module.params.get("hour") + minute = module.params.get("minute") + maxTime = module.params.get("maxTime") + concurCap = module.params.get("concurCap") + day = module.params.get("day") + description = module.params.get("description") + name_alias = module.params.get("name_alias") + + if recurring: + child_configs = [ + dict( + trigRecurrWindowP=dict( + attributes=dict( + name=windowname, + hour=hour, + minute=minute, + procCa=maxTime, + concurCap=concurCap, + day=day, + ) + ) + ) + ] + elif recurring is False: + child_configs = [ + dict( + trigAbsWindowP=dict( + attributes=dict( + name=windowname, + procCap=maxTime, + concurCap=concurCap, + date=date, + ) + ) + ) + ] + else: + child_configs = [] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="trigSchedP", + aci_rn="fabric/schedp-{0}".format(name), + target_filter={"name": name}, + module_object=name, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="trigSchedP", + class_config=dict( + name=name, + descr=description, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="trigSchedP") + + 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_spine_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_spine_profile.py new file mode 100644 index 000000000..364b613ff --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_spine_profile.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_spine_profile +short_description: Manage fabric spine profiles (fabric:SpineP). +description: +- Manage fabric spine switch profiles in an ACI fabric. +options: + name: + description: + - Name of the fabric spine switch profile + type: str + aliases: [ spine_profile, spine_switch_profile ] + description: + description: + - description of the 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 +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(fabricSpineP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a spine switch profile + cisco.aci.aci_fabric_spine_profile: + host: apic + username: admin + password: SomeSecretPassword + name: my_spine_profile + state: present + delegate_to: localhost + +- name: Remove a spine switch profile + cisco.aci.aci_fabric_spine_profile: + host: apic + username: admin + password: SomeSecretPassword + name: my_spine_profile + state: absent + delegate_to: localhost + +- name: Query a spine profile + cisco.aci.aci_fabric_spine_profile: + host: apic + username: admin + password: SomeSecretPassword + name: my_spine_profile + state: query + delegate_to: localhost + register: query_result + +- name: Query all spine profiles + cisco.aci.aci_fabric_spine_profile: + 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_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 + + +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=["spine_switch_profile", "spine_profile"]), + description=dict(type="str", aliases=["descr"]), + 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") + child_classes = ["fabricSpineS"] + + aci.construct_url( + root_class=dict( + aci_class="fabricSpineP", + aci_rn="fabric/spprof-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricSpineP", + class_config=dict(name=name, descr=description), + ) + + aci.get_diff(aci_class="fabricSpineP") + + 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_spine_switch_assoc.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_spine_switch_assoc.py new file mode 100644 index 000000000..ce0a00ce8 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_spine_switch_assoc.py @@ -0,0 +1,286 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_spine_switch_assoc +short_description: Manage spine switch bindings to profiles and policy groups (fabric:SpineS and fabric:RsSpNodePGrp). +description: +- Manage fabric spine switch associations (fabric:SpineS) to an existing fabric + spine profile (fabric:SpineP) in an ACI fabric, and bind them to a + policy group (fabric:RsSpNodePGrp) +options: + profile: + description: + - Name of an existing fabric spine switch profile + type: str + aliases: [ spine_profile, spine_switch_profile ] + name: + description: + - Name of the switch association + type: str + aliases: [ association_name, switch_association ] + policy_group: + description: + - Name of an existing spine switch policy group + type: str + description: + description: + - Description of the Fabric Switch Association + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_spine_profile) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fabricSpineS) and B(fabricRsSpNodePGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a spine switch profile association + cisco.aci.aci_fabric_spine_switch_assoc: + host: apic + username: admin + password: SomeSecretPassword + profile: my_spine_profile + name: my_spine_switch_assoc + policy_group: my_spine_pol_grp + state: present + delegate_to: localhost + +- name: Remove a spine switch profile association + cisco.aci.aci_fabric_spine_switch_assoc: + host: apic + username: admin + password: SomeSecretPassword + profile: my_spine_profile + name: my_spine_switch_assoc + state: absent + delegate_to: localhost + +- name: Query a spine profile association + cisco.aci.aci_fabric_spine_switch_assoc: + host: apic + username: admin + password: SomeSecretPassword + profile: my_spine_profile + name: my_spine_switch_assoc + state: query + delegate_to: localhost + register: query_result + +- name: Query all spine profiles + cisco.aci.aci_fabric_spine_switch_assoc: + 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_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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + profile=dict(type="str", aliases=["spine_profile", "spine_switch_profile"]), + name=dict(type="str", aliases=["association_name", "switch_association"]), + policy_group=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["profile", "name"]], + ["state", "present", ["profile", "name"]], + ], + ) + + aci = ACIModule(module) + + profile = module.params.get("profile") + name = module.params.get("name") + policy_group = module.params.get("policy_group") + description = module.params.get("description") + state = module.params.get("state") + child_classes = ["fabricRsSpNodePGrp", "fabricNodeBlk"] + + aci.construct_url( + root_class=dict( + aci_class="fabricSpineP", + aci_rn="fabric/spprof-{0}".format(profile), + module_object=profile, + target_filter={"name": profile}, + ), + subclass_1=dict( + aci_class="fabricSpineS", + aci_rn="spines-{0}-typ-range".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if policy_group: + tDn = "uni/fabric/funcprof/spnodepgrp-{0}".format(policy_group) + child_configs.append(dict(fabricRsSpNodePGrp=dict(attributes=dict(tDn=tDn)))) + aci.payload( + aci_class="fabricSpineS", + class_config=dict(name=name, descr=description), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fabricSpineS") + + 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_switch_block.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_switch_block.py new file mode 100644 index 000000000..57bd59874 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_switch_block.py @@ -0,0 +1,311 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_switch_block +short_description: Manage switch blocks (fabric:NodeBlk). +description: +- Manage fabric node blocks within switch associations (fabric:SpineS and + fabric:LeafS) contained within fabric switch profiles (fabric:SpineP and fabric:LeafP) +options: + name: + description: + - Name of the block + type: str + aliases: [ block_name ] + switch_type: + description: + - Type of switch profile, leaf or spine + type: str + choices: [ leaf, spine ] + required: true + profile: + description: + - Name of an existing fabric spine or leaf switch profile + type: str + aliases: [ profile_name, switch_profile ] + association: + description: + - Name of an existing switch association + type: str + aliases: [ association_name, switch_association ] + description: + description: + - Description of the Node Block + type: str + aliases: [ descr ] + from_node: + description: + - First Node ID of the block + type: int + aliases: [ from, from_ ] + to_node: + description: + - Last Node ID of the block + type: int + aliases: [ to, to_ ] + 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(fabricNodeBlk) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a spine switch association block + cisco.aci.aci_fabric_switch_block: + host: apic + username: admin + password: SomeSecretPassword + switch_type: spine + profile: my_spine_profile + association: my_spine_switch_assoc + name: my_spine_block + from_node: 101 + to_node: 101 + state: present + delegate_to: localhost + +- name: Remove a spine switch profile association + cisco.aci.aci_fabric_switch_block: + host: apic + username: admin + password: SomeSecretPassword + switch_type: spine + profile: my_spine_profile + association: my_spine_switch_assoc + name: my_spine_block + state: absent + delegate_to: localhost + +- name: Query a spine profile association + cisco.aci.aci_fabric_switch_block: + host: apic + username: admin + password: SomeSecretPassword + switch_type: spine + profile: my_spine_profile + association: my_spine_switch_assoc + name: my_spine_block + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + name=dict(type="str", aliases=["block_name"]), + switch_type=dict(type="str", choices=["leaf", "spine"], required=True), + profile=dict(type="str", aliases=["profile_name", "switch_profile"]), + association=dict(type="str", aliases=["association_name", "switch_association"]), + description=dict(type="str", aliases=["descr"]), + from_node=dict(type="int", aliases=["from", "from_"]), + to_node=dict(type="int", aliases=["to", "to_"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["profile", "association", "name"]], + ["state", "present", ["profile", "association", "name"]], + ], + ) + + aci = ACIModule(module) + + name = module.params.get("name") + profile = module.params.get("profile") + switch_type = module.params.get("switch_type") + association = module.params.get("association") + descr = module.params.get("descr") + from_node = module.params.get("from_node") + to_node = module.params.get("to_node") + state = module.params.get("state") + + if switch_type == "spine": + aci_root_class = "fabricSpineP" + aci_root_rn = "fabric/spprof-{0}".format(profile) + aci_subclass1_class = "fabricSpineS" + aci_subclass1_rn = "spines-{0}-typ-range".format(association) + elif switch_type == "leaf": + aci_root_class = "fabricLeafP" + aci_root_rn = "fabric/leprof-{0}".format(profile) + aci_subclass1_class = "fabricLeafS" + aci_subclass1_rn = "leaves-{0}-typ-range".format(association) + + aci.construct_url( + root_class=dict( + aci_class=aci_root_class, + aci_rn=aci_root_rn, + module_object=profile, + target_filter={"name": profile}, + ), + subclass_1=dict( + aci_class=aci_subclass1_class, + aci_rn=aci_subclass1_rn, + module_object=association, + target_filter={"name": association}, + ), + subclass_2=dict( + aci_class="fabricNodeBlk", + aci_rn="nodeblk-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricNodeBlk", + class_config=dict(name=name, descr=descr, from_=from_node, to_=to_node), + ) + + aci.get_diff(aci_class="fabricNodeBlk") + + 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_switch_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_switch_policy_group.py new file mode 100644 index 000000000..1d766a34e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_switch_policy_group.py @@ -0,0 +1,397 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_switch_policy_group +short_description: Manage Fabric Switch Policy Group objects. +description: +- Manage Fabric Switch Policy Group (fabricLeNodePGrp and fabricSpNodePGrp) configuration on Cisco ACI fabrics. +options: + name: + description: + - The name of the Leaf Policy Group. + type: str + aliases: [ 'policy_group', 'policy_group_name' ] + description: + description: + - Description for the Leaf Policy Group. + type: str + switch_type: + description: + - Whether this is a leaf or spine policy group + type: str + choices: [ leaf, spine ] + required: true + monitoring_policy: + description: + - Monitoring Policy to attach to this Policy Group + type: str + aliases: [ 'monitoring', 'fabricRsMonInstFabricPol' ] + tech_support_export_policy: + description: + - Tech Support Export Policy to attach to this Policy Group + type: str + aliases: [ 'tech_support', 'tech_support_export', 'fabricRsNodeTechSupP'] + core_export_policy: + description: + - Core Export Policy to attach to this Policy Group + type: str + aliases: [ 'core', 'core_export', 'fabricRsNodeCoreP' ] + inventory_policy: + description: + - Inventory Policy to attach to this Policy Group + type: str + aliases: [ 'inventory', 'fabricRsCallhomeInvPol' ] + power_redundancy_policy: + description: + - Power Redundancy Policy to atttach to this Policy Group + type: str + aliases: [ 'power_redundancy', 'fabricRsPsuInstPol' ] + twamp_server_policy: + description: + - TWAMP Server Policy to attach to this Policy Group + type: str + aliases: [ 'twamp_server', 'fabricRsTwampServerPol' ] + twamp_responder_policy: + description: + - TWAMP Responder Policy to attach to this Policy Group + type: str + aliases: [ 'twamp_responder', 'fabricRsTwampResponderPol' ] + node_control_policy: + description: + - Node Control Policy to attach to this Policy Group + type: str + aliases: [ 'node_control', 'fabricRsNodeCtrl' ] + analytics_cluster: + description: + - Name of the analytics cluster. Requires analytics_name to be present + type: str + analytics_name: + description: + - Name of the analytics policy. Requires analytics_cluster to be present + 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 classes B(fabricLeNodePGrp and fabricSpNodePGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new Fabric Leaf Policy Group + cisco.aci.aci_fabric_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_fabric_leaf_policy_group + switch_type: leaf + monitoring_policy: my_monitor_policy + inventory_policy: my_inv_policy + state: present + delegate_to: localhost + +- name: Remove existing analytics and monitoring policy bindings from a Policy Group + cisco.aci.aci_fabric_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_fabric_leaf_policy_group + switch_type: leaf + monitoring_policy: "" + analytics_cluster: "" + analytics_name: "" + state: present + delegate_to: localhost + +- name: Remove a Fabric Leaf Policy Group + cisco.aci.aci_fabric_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_fabric_leaf_policy_group + switch_type: leaf + state: absent + delegate_to: localhost + +- name: Query a Fabric Leaf Policy Group + cisco.aci.aci_fabric_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_fabric_leaf_policy_group + switch_type: leaf + state: query + delegate_to: localhost + register: query_result + +- name: Query all Fabric Leaf Policy Groups + cisco.aci.aci_fabric_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + switch_type: leaf + 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 "/></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_CLASS_MAPPING = dict( + spine={ + "class": "fabricSpNodePGrp", + "rn": "spnodepgrp-", + }, + leaf={ + "class": "fabricLeNodePGrp", + "rn": "lenodepgrp-", + }, +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + name=dict(type="str", aliases=["policy_group", "policy_group_name"]), + switch_type=dict(type="str", choices=["leaf", "spine"], required=True), + monitoring_policy=dict(type="str", aliases=["monitoring", "fabricRsMonInstFabricPol"]), + tech_support_export_policy=dict(type="str", aliases=["tech_support", "tech_support_export", "fabricRsNodeTechSupP"]), + core_export_policy=dict(type="str", aliases=["core", "core_export", "fabricRsNodeCoreP"]), + inventory_policy=dict(type="str", aliases=["inventory", "fabricRsCallhomeInvPol"]), + power_redundancy_policy=dict(type="str", aliases=["power_redundancy", "fabricRsPsuInstPol"]), + twamp_server_policy=dict(type="str", aliases=["twamp_server", "fabricRsTwampServerPol"]), + twamp_responder_policy=dict(type="str", aliases=["twamp_responder", "fabricRsTwampResponderPol"]), + node_control_policy=dict(type="str", aliases=["node_control", "fabricRsNodeCtrl"]), + analytics_cluster=dict(type="str"), + analytics_name=dict(type="str"), + description=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"]], + ], + required_together=[ + ("analytics_cluster", "analytics_name"), + ], + ) + + name = module.params.get("name") + switch_type = module.params.get("switch_type") + description = module.params.get("description") + monitoring_policy = module.params.get("monitoring_policy") + tech_support_export_policy = module.params.get("tech_support_export_policy") + core_export_policy = module.params.get("core_export_policy") + inventory_policy = module.params.get("inventory_policy") + power_redundancy_policy = module.params.get("power_redundancy_policy") + twamp_server_policy = module.params.get("twamp_server_policy") + twamp_responder_policy = module.params.get("twamp_responder_policy") + node_control_policy = module.params.get("node_control_policy") + analytics_cluster = module.params.get("analytics_cluster") + analytics_name = module.params.get("analytics_name") + state = module.params.get("state") + child_classes = [ + "fabricRsMonInstFabricPol", + "fabricRsNodeTechSupP", + "fabricRsNodeCoreP", + "fabricRsCallhomeInvPol", + "fabricRsPsuInstPol", + "fabricRsTwampServerPol", + "fabricRsTwampResponderPol", + "fabricRsNodeCtrl", + "fabricRsNodeCfgSrv", + ] + + aci_class = ACI_CLASS_MAPPING[switch_type]["class"] + aci_rn = ACI_CLASS_MAPPING[switch_type]["rn"] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn="fabric/funcprof/{0}{1}".format(aci_rn, name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + + if monitoring_policy is not None: + child_configs.append(dict(fabricRsMonInstFabricPol=dict(attributes=dict(tnMonFabricPolName=monitoring_policy)))) + if tech_support_export_policy is not None: + child_configs.append(dict(fabricRsNodeTechSupP=dict(attributes=dict(tnDbgexpTechSupPName=tech_support_export_policy)))) + if core_export_policy is not None: + child_configs.append(dict(fabricRsNodeCoreP=dict(attributes=dict(tnDbgexpCorePName=core_export_policy)))) + if inventory_policy is not None: + child_configs.append(dict(fabricRsCallhomeInvPol=dict(attributes=dict(tnCallhomeInvPName=inventory_policy)))) + if power_redundancy_policy is not None: + child_configs.append(dict(fabricRsPsuInstPol=dict(attributes=dict(tnPsuInstPolName=power_redundancy_policy)))) + if twamp_server_policy is not None: + child_configs.append(dict(fabricRsTwampServerPol=dict(attributes=dict(tnTwampServerPolName=twamp_server_policy)))) + if twamp_responder_policy is not None: + child_configs.append(dict(fabricRsTwampResponderPol=dict(attributes=dict(tnTwampResponderPolName=twamp_responder_policy)))) + if node_control_policy is not None: + child_configs.append(dict(fabricRsNodeCtrl=dict(attributes=dict(tnFabricNodeControlName=node_control_policy)))) + if analytics_cluster and analytics_name: + analytics_tdn = "uni/fabric/analytics/cluster-{0}/cfgsrv-{1}".format(analytics_cluster, analytics_name) + child_configs.append(dict(fabricRsNodeCfgSrv=dict(attributes=dict(tDn=analytics_tdn)))) + + aci.payload( + aci_class=aci_class, + class_config=dict( + name=name, + descr=description, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class=aci_class) + + 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.py b/ansible_collections/cisco/aci/plugins/modules/aci_filter.py new file mode 100644 index 000000000..51daa4325 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_filter.py @@ -0,0 +1,282 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_filter +short_description: Manages top level filter objects (vz:Filter) +description: +- Manages top level filter objects on Cisco ACI fabrics. +- This modules does not manage filter entries, see M(cisco.aci.aci_filter_entry) for this functionality. +options: + filter: + description: + - The name of the filter. + type: str + aliases: [ filter_name, name ] + description: + description: + - Description for the filter. + type: str + aliases: [ descr ] + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_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) 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(vz:Filter). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add a new filter to a tenant + cisco.aci.aci_filter: + host: apic + username: admin + password: SomeSecretPassword + filter: web_filter + description: Filter for web protocols + tenant: production + state: present + delegate_to: localhost + +- name: Remove a filter for a tenant + cisco.aci.aci_filter: + host: apic + username: admin + password: SomeSecretPassword + filter: web_filter + tenant: production + state: absent + delegate_to: localhost + +- name: Query a filter of a tenant + cisco.aci.aci_filter: + host: apic + username: admin + password: SomeSecretPassword + filter: web_filter + tenant: production + state: query + delegate_to: localhost + register: query_result + +- name: Query all filters for a tenant + cisco.aci.aci_filter: + host: apic + username: admin + password: SomeSecretPassword + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + filter=dict(type="str", aliases=["name", "filter_name"]), # Not required for querying all objects + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + 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", ["filter", "tenant"]], + ["state", "present", ["filter", "tenant"]], + ], + ) + + filter_name = module.params.get("filter") + 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="vzFilter", + aci_rn="flt-{0}".format(filter_name), + module_object=filter_name, + target_filter={"name": filter_name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vzFilter", + class_config=dict( + name=filter_name, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="vzFilter") + + 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 new file mode 100644 index 000000000..50587e198 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_filter_entry.py @@ -0,0 +1,435 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_filter_entry +short_description: Manage filter entries (vz:Entry) +description: +- Manage filter entries for a filter on Cisco ACI fabrics. +options: + arp_flag: + description: + - The arp flag to use when the ether_type is arp. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ arp_reply, arp_request, unspecified ] + description: + description: + - Description for the Filter Entry. + type: str + aliases: [ descr ] + dst_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: + 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: + 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 + entry: + description: + - Then name of the Filter Entry. + type: str + aliases: [ entry_name, filter_entry, name ] + ether_type: + description: + - The Ethernet type. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ arp, fcoe, ip, ipv4, ipv6, mac_security, mpls_ucast, trill, unspecified ] + filter: + description: + - The name of Filter that the entry should belong to. + type: str + aliases: [ filter_name ] + icmp_msg_type: + description: + - ICMPv4 message type; used when ip_protocol is icmp. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ dst_unreachable, echo, echo_reply, src_quench, time_exceeded, unspecified ] + icmp6_msg_type: + description: + - ICMPv6 message type; used when ip_protocol is icmpv6. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ dst_unreachable, echo_request, echo_reply, neighbor_advertisement, neighbor_solicitation, redirect, time_exceeded, unspecified ] + ip_protocol: + description: + - The IP Protocol type when ether_type is ip. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ eigrp, egp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp, unspecified ] + state: + description: + - present, absent, query + 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. + type: str + stateful: + description: + - Determines the statefulness of the filter entry. + type: bool + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant) and C(filter) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_filter) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_filter +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vz:Entry). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Create a filter entry + 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 + dst_port_start: 443 + dst_port_end: 443 + state: present + delegate_to: localhost + +- name: Delete a filter entry + cisco.aci.aci_filter_entry: + host: apic + username: admin + password: SomeSecretPassword + entry: https_allow + filter: web_filter + tenant: prod + state: absent + delegate_to: localhost + +- name: Query all filter entries + cisco.aci.aci_filter_entry: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific filter entry + cisco.aci.aci_filter_entry: + host: apic + username: admin + password: SomeSecretPassword + entry: https_allow + filter: web_filter + tenant: prod + 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 +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", +) + + +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), + description=dict(type="str", aliases=["descr"]), + dst_port=dict(type="str"), + dst_port_end=dict(type="str"), + dst_port_start=dict(type="str"), + 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), + ip_protocol=dict(choices=VALID_IP_PROTOCOLS, type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + stateful=dict(type="bool"), + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["entry", "filter", "tenant"]], + ["state", "present", ["entry", "filter", "tenant"]], + ], + ) + + aci = ACIModule(module) + + arp_flag = module.params.get("arp_flag") + 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") + 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) + 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) + 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) + ip_protocol = module.params.get("ip_protocol") + state = module.params.get("state") + stateful = aci.boolean(module.params.get("stateful")) + 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'") + elif dst_port is not None: + dst_end = dst_port + dst_start = dst_port + + 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="vzFilter", + aci_rn="flt-{0}".format(filter_name), + module_object=filter_name, + target_filter={"name": filter_name}, + ), + subclass_2=dict( + aci_class="vzEntry", + aci_rn="e-{0}".format(entry), + module_object=entry, + target_filter={"name": entry}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vzEntry", + class_config=dict( + arpOpc=arp_flag, + descr=description, + dFromPort=dst_start, + dToPort=dst_end, + etherT=ether_type, + icmpv4T=icmp_msg_type, + icmpv6T=icmp6_msg_type, + name=entry, + prot=ip_protocol, + stateful=stateful, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="vzEntry") + + 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_firmware_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py new file mode 100644 index 000000000..1a9db1a6c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group.py @@ -0,0 +1,267 @@ +#!/usr/bin/python + +# 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 = """ +--- +module: aci_firmware_group + +short_description: This module creates a firmware group + + +description: + - 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 + type: str + firmwarepol: + 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 + 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 + default: present + choices: [ absent, present, query ] + 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 + +author: + - Steven Gerhart (@sgerhart) +""" + + +EXAMPLES = r""" +- name: Create a firmware group + cisco.aci.aci_firmware_group: + host: apic + username: admin + password: SomeSecretPassword + group: fmgroup + firmwarepol: fmpolicy1 + state: present + delegate_to: localhost + +- name: Delete a firmware group + cisco.aci.aci_firmware_group: + host: apic + username: admin + password: SomeSecretPassword + group: fmgroup + state: absent + delegate_to: localhost + +- name: Query all firmware groups + cisco.aci.aci_firmware_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific firmware group + cisco.aci.aci_firmware_group: + host: apic + username: admin + password: SomeSecretPassword + group: fmgroup + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = """ +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_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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + 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 + 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", ["group"]], + ["state", "present", ["group", "firmwarepol"]], + ], + ) + + state = module.params.get("state") + group = module.params.get("group") + firmwarepol = module.params.get("firmwarepol") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="firmwareFwGrp", + aci_rn="fabric/fwgrp-{0}".format(group), + target_filter={"name": group}, + module_object=group, + ), + child_classes=["firmwareRsFwgrpp"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="firmwareFwGrp", + class_config=dict( + name=group, + nameAlias=name_alias, + ), + child_configs=[ + dict( + firmwareRsFwgrpp=dict( + attributes=dict( + tnFirmwareFwPName=firmwarepol, + ), + ), + ), + ], + ) + + aci.get_diff(aci_class="firmwareFwGrp") + + 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_firmware_group_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py new file mode 100644 index 000000000..b41a2601b --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_group_node.py @@ -0,0 +1,264 @@ +#!/usr/bin/python + +# 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 = """ +--- +module: aci_firmware_group_node + +short_description: This modules adds and remove nodes from the firmware group + + +description: + - This module addes/deletes a node to the firmware group. This modules assigns 1 node at a time. + +options: + group: + description: + - 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 + 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 + default: present + choices: [ absent, present, query ] + 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 + +author: + - Steven Gerhart (@sgerhart) +""" + +EXAMPLES = r""" +- name: Add a firmware group node + cisco.aci.aci_firmware_group_node: + host: apic + username: admin + password: SomeSecretPassword + group: fmgroup + node: 1001 + state: present + delegate_to: localhost + +- name: Remove a firmware group node + cisco.aci.aci_firmware_group_node: + host: apic + username: admin + password: SomeSecretPassword + group: fmgroup + node: 1001 + state: absent + delegate_to: localhost + +- name: Query all firmware groups nodes + cisco.aci.aci_firmware_group_node: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific firmware group node + cisco.aci.aci_firmware_group_node: + host: apic + username: admin + password: SomeSecretPassword + group: fmgroup + node: 1001 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = """ +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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + group=dict(type="str"), # Not required for querying all objects + node=dict(type="str"), + 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", ["node", "group"]], + ["state", "present", ["node", "group"]], + ], + ) + + state = module.params.get("state") + group = module.params.get("group") + node = module.params.get("node") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="firmwareFwGrp", + aci_rn="fabric/fwgrp-{0}".format(group), + target_filter={"name": group}, + module_object=group, + ), + subclass_1=dict( + aci_class="fabricNodeBlk", + aci_rn="nodeblk-blk{0}-{0}".format(node), + target_filter={"name": node}, + module_object=node, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricNodeBlk", + class_config=dict( + from_=node, + to_=node, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fabricNodeBlk") + + 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_firmware_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py new file mode 100644 index 000000000..bbe37ecfd --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_policy.py @@ -0,0 +1,250 @@ +#!/usr/bin/python + + +# 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 = """ +--- +module: aci_firmware_policy + +short_description: This creates a firmware policy + + +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 +options: + name: + description: + - Name of the firmware 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) + type: str + ignoreCompat: + description: + - Check if compatibility checks should be ignored + type: bool + 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 + +author: + - Steven Gerhart (@sgerhart) +""" + +# 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 + +""" + +RETURN = """ +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_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 + + +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"), # Not required for querying all objects + version=dict(type="str"), + ignoreCompat=dict(type="bool"), + 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", ["name"]], + ["state", "present", ["name", "version"]], + ], + ) + + state = module.params.get("state") + name = module.params.get("name") + version = module.params.get("version") + 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", + aci_rn="fabric/fwpol-{0}".format(name), + target_filter={"name": name}, + module_object=name, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="firmwareFwP", + class_config=dict( + name=name, + version=version, + ignoreCompat=ignore, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="firmwareFwP") + + 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_firmware_source.py b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_source.py new file mode 100644 index 000000000..9058e60b2 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_firmware_source.py @@ -0,0 +1,304 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Cindy Zhao (cizhao) <cizhao@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_firmware_source +short_description: Manage firmware image sources (firmware:OSource) +description: +- Manage firmware image sources on Cisco ACI fabrics. +options: + source: + description: + - The identifying name for the outside source of images, such as an HTTP or SCP server. + type: str + aliases: [ name, source_name ] + polling_interval: + description: + - Polling interval in minutes. + type: int + url_protocol: + description: + - The Firmware download protocol. + type: str + choices: [ http, local, scp, usbkey ] + default: scp + aliases: [ url_proto ] + url: + description: + The firmware URL for the image(s) on the source. + type: str + url_password: + description: + The Firmware password or key string. + type: str + url_username: + description: + The username for the source. + 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 + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(firmware:OSource). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add firmware source + cisco.aci.aci_firmware_source: + host: apic + username: admin + password: SomeSecretPassword + source: aci-msft-pkg-3.1.1i.zip + url: foo.bar.cisco.com/download/cisco/aci/aci-msft-pkg-3.1.1i.zip + url_protocol: http + state: present + delegate_to: localhost + +- name: Remove firmware source + cisco.aci.aci_firmware_source: + host: apic + username: admin + password: SomeSecretPassword + source: aci-msft-pkg-3.1.1i.zip + state: absent + delegate_to: localhost + +- name: Query a specific firmware source + cisco.aci.aci_firmware_source: + host: apic + username: admin + password: SomeSecretPassword + source: aci-msft-pkg-3.1.1i.zip + state: query + delegate_to: localhost + register: query_result + +- name: Query all firmware sources + cisco.aci.aci_firmware_source: + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + source=dict(type="str", aliases=["name", "source_name"]), # Not required for querying all objects + polling_interval=dict(type="int"), + url=dict(type="str"), + url_username=dict(type="str"), + url_password=dict(type="str", no_log=True), + url_protocol=dict(type="str", default="scp", choices=["http", "local", "scp", "usbkey"], aliases=["url_proto"]), + 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"]], + ["state", "present", ["url_protocol", "source", "url"]], + ], + ) + + polling_interval = module.params.get("polling_interval") + url_protocol = module.params.get("url_protocol") + state = module.params.get("state") + source = module.params.get("source") + url = module.params.get("url") + url_password = module.params.get("url_password") + url_username = module.params.get("url_username") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fabricInst", + aci_rn="fabric", + module_object="fabric", + target_filter={"name": "fabric"}, + ), + subclass_1=dict( + aci_class="firmwareRepoP", + aci_rn="fwrepop", + module_object="fwrepop", + target_filter={"name": "fwrepop"}, + ), + subclass_2=dict( + aci_class="firmwareOSource", + aci_rn="osrc-{0}".format(source), + module_object=source, + target_filter={"name": source}, + ), + config_only=False, + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="firmwareOSource", + class_config=dict( + name=source, + url=url, + password=url_password, + pollingInterval=polling_interval, + proto=url_protocol, + user=url_username, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="firmwareOSource") + + 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_igmp_interface_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_igmp_interface_policy.py new file mode 100644 index 000000000..7f537871a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_igmp_interface_policy.py @@ -0,0 +1,383 @@ +#!/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_igmp_interface_policy +short_description: Manage IGMP Interface Policies (igmp:IfPol) +description: +- Manage IGMP Interface Policies on Cisco ACI fabrics. +options: + name: + description: + - The name of the IGMP Interface Policy. + type: str + tenant: + description: + - The tenant to build the IGMP Interface Policy under. + type: str + description: + description: + - The description of the IGMP Interface Policy. + type: str + group_timeout: + description: + - The IGMP group timeout in seconds. + - The APIC defaults to 260 when unset during creation. + type: int + query_interval: + description: + - The IGMP query interval in seconds. + - The APIC defaults to 125 when unset during creation. + type: int + query_response_interval: + description: + - The IGMP query response interval in seconds. + - The APIC defaults to 10 when unset during creation. + type: int + last_member_count: + description: + - The last member query count. + - The APIC defaults to 2 when unset during creation. + type: int + last_member_response: + description: + - The last member response time in seconds. + - The APIC defaults to 1 when unset during creation. + type: int + startup_query_count: + description: + - The Startup Query Count. + - The APIC defaults to 2 when unset during creation. + type: int + startup_query_interval: + description: + - The startup query interval in seconds. + - The APIC defaults to 31 when unset during creation. + type: int + querier_timeout: + description: + - The querier timeout in seconds. + - The APIC defaults to 255 when unset during creation. + type: int + robustness_variable: + description: + - The robustness factor. + - The APIC defaults to 2 when unset during creation. + type: int + igmp_version: + description: + - The IGMP version to run. + - The APIC defaults to v2 when unset during creation. + type: str + choices: [ v2, v3 ] + allow_v3_asm: + description: + - Enable the Allow v3 ASM option. + - The APIC defaults to False when unset during creation. + - If this parameter is set, fast_leave and report_link_local_groups must also be set. + type: bool + fast_leave: + description: + - Enable the Fast Leave option. + - The APIC defaults to False when unset during creation. + - If this parameter is set, allow_v3_asm and report_link_local_groups must also be set. + type: bool + report_link_local_groups: + description: + - Enable the Report Link Local Groups option. + - The APIC defaults to False when unset during creation. + - If this parameter is set, allow_v3_asm and fast_leave must also be set. + type: bool + 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(igmp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ + +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add an IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + host: apic + username: admin + password: SomeSecretPassword + name: ans_igmp_intf_pol + query_interval: 200 + state: present + delegate_to: localhost + +- name: Query an IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + host: apic + username: admin + password: SomeSecretPassword + name: ans_igmp_intf_pol + state: query + delegate_to: localhost + +- name: Query all IGMP Interface Policies + cisco.aci.aci_igmp_interface_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove an IGMP Interface Policy + cisco.aci.aci_igmp_interface_policy: + host: apic + username: admin + password: SomeSecretPassword + name: ans_igmp_intf_pol + 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"), + tenant=dict(type="str"), + description=dict(type="str"), + group_timeout=dict(type="int"), + query_interval=dict(type="int"), + query_response_interval=dict(type="int"), + last_member_count=dict(type="int"), + last_member_response=dict(type="int"), + startup_query_count=dict(type="int"), + startup_query_interval=dict(type="int"), + querier_timeout=dict(type="int"), + robustness_variable=dict(type="int"), + igmp_version=dict(type="str", choices=["v2", "v3"]), + allow_v3_asm=dict(type="bool"), + fast_leave=dict(type="bool"), + report_link_local_groups=dict(type="bool"), + 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"]], + ["state", "absent", ["name"]], + ], + required_together=[ + ["allow_v3_asm", "fast_leave", "report_link_local_groups"], + ], + ) + + aci = ACIModule(module) + + name = module.params.get("name") + tenant = module.params.get("tenant") + description = module.params.get("description") + group_timeout = module.params.get("group_timeout") + query_interval = module.params.get("query_interval") + query_response_interval = module.params.get("query_response_interval") + last_member_count = module.params.get("last_member_count") + last_member_response = module.params.get("last_member_response") + startup_query_count = module.params.get("startup_query_count") + startup_query_interval = module.params.get("startup_query_interval") + querier_timeout = module.params.get("querier_timeout") + robustness_variable = module.params.get("robustness_variable") + igmp_version = module.params.get("igmp_version") + allow_v3_asm = module.params.get("allow_v3_asm") + fast_leave = module.params.get("fast_leave") + report_link_local_groups = module.params.get("report_link_local_groups") + state = module.params.get("state") + + 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="igmpIfPol", + aci_rn="igmpIfPol-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + aci.get_existing() + + if state == "present": + if allow_v3_asm is not None: + if_ctrl_list = [] + if allow_v3_asm: + if_ctrl_list.append("allow-v3-asm") + if fast_leave: + if_ctrl_list.append("fast-leave") + if report_link_local_groups: + if_ctrl_list.append("rep-ll") + if_ctrl = ",".join(if_ctrl_list) + else: + if_ctrl = None + + aci.payload( + aci_class="igmpIfPol", + class_config=dict( + name=name, + descr=description, + grpTimeout=group_timeout, + ifCtrl=if_ctrl, + lastMbrCnt=last_member_count, + lastMbrRespTime=last_member_response, + querierTimeout=querier_timeout, + queryIntvl=query_interval, + robustFac=robustness_variable, + rspIntvl=query_response_interval, + startQueryCnt=startup_query_count, + startQueryIntvl=startup_query_interval, + ver=igmp_version, + ), + ) + + aci.get_diff(aci_class="igmpIfPol") + 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_blacklist.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_blacklist.py new file mode 100644 index 000000000..61d5ef44d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_blacklist.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_blacklist +short_description: Enabling or Disabling physical interfaces. +description: +- Enables or Disables physical interfaces on Cisco ACI fabrics. +options: + pod_id: + description: + - The pod number. + - C(pod_id) is usually an integer below C(12) + type: int + aliases: [ pod, pod_number ] + node_id: + description: + - The switch ID that the C(interface) belongs to. + - The C(node_id) value is usually something like '101'. + type: int + aliases: [ leaf, spine, node ] + interface: + description: + - The name of the C(interface) that is targeted. + - Usually an interface name with the following format C(1/7). + type: str + fex_id: + description: + - The fex ID that the C(interface) belongs to. + - The C(fex_id) value is usually something like '123'. + type: int + 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 + +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Disable Interface + cisco.aci.aci_interface_blacklist: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + interface: 1/49 + state: present + delegate_to: localhost + +- name: Enable Interface + cisco.aci.aci_interface_blacklist: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + interface: 1/49 + state: absent + delegate_to: localhost + +- name: Disable Interface on Fex + cisco.aci.aci_interface_blacklist: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + fex_id: 123 + interface: 1/49 + state: present + delegate_to: localhost + +- name: Enable Interface on Fex + cisco.aci.aci_interface_blacklist: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + fex_id: 123 + interface: 1/49 + state: absent + delegate_to: localhost + +- name: Query Interface + cisco.aci.aci_interface_blacklist: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + interface: 1/49 + state: query + delegate_to: localhost + +- name: Query All Interfaces + cisco.aci.aci_interface_blacklist: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + pod_id=dict(type="int", aliases=["pod", "pod_number"]), + node_id=dict(type="int", aliases=["leaf", "spine", "node"]), + fex_id=dict(type="int"), + interface=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", ["pod_id", "node_id", "interface"]], + ["state", "present", ["pod_id", "node_id", "interface"]], + ], + ) + + aci = ACIModule(module) + + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + interface = module.params.get("interface") + fex_id = module.params.get("fex_id") + state = module.params.get("state") + + root_module_object = None + subclass_1_module_object = None + tdn = None + rn = None + + if pod_id and node_id and interface: + root_module_object = "fabric" + subclass_1_module_object = "outofsvc" + if fex_id: + tdn = "topology/pod-{0}/paths-{1}/extpaths-{2}/pathep-[eth{3}]".format(pod_id, node_id, fex_id, interface) + else: + tdn = "topology/pod-{0}/paths-{1}/pathep-[eth{2}]".format(pod_id, node_id, interface) + rn = "rsoosPath-[{0}]".format(tdn) + + aci.construct_url( + root_class=dict( + aci_class="fabricInst", + aci_rn="fabric", + module_object=root_module_object, + target_filter={"name": "fabric"}, + ), + subclass_1=dict( + aci_class="fabricOOServicePol", + aci_rn="outofsvc", + module_object=subclass_1_module_object, + target_filter={"name": "default"}, + ), + subclass_2=dict( + aci_class="fabricRsOosPath", + aci_rn=rn, + target_filter={"tDN": tdn}, + ), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricRsOosPath", + class_config=dict( + lc="blacklist", + ), + ) + + aci.get_diff(aci_class="fabricRsOosPath") + + 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_config.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py new file mode 100644 index 000000000..7e9b165bd --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_config.py @@ -0,0 +1,440 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, 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_interface_config +short_description: Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(5)+ +description: +- Manage Interface Configuration of Access (infra:PortConfig) and Fabric (fabric:PortConfig) Ports is only supported for ACI 5.2(5)+ +options: + policy_group: + description: + - The name of the Policy Group being associated with the Port. + - The I(policy_group) and I(breakout) cannot be configured simultaneously. + type: str + aliases: [ policy_group_name ] + breakout: + description: + - The Breakout Map of the interface. + - The I(policy_group) and I(breakout) cannot be configured simultaneously. + type: str + choices: [ 100g-2x, 100g-4x, 10g-4x, 25g-4x, 50g-8x ] + description: + description: + - The description of the Interface Configuration object. + type: str + aliases: [ descr ] + node: + description: + - The ID of the Node. + - The value must be between 101 to 4000. + type: int + aliases: [ node_id ] + pc_member: + description: + - The name of the Port Channel Member. + type: str + aliases: [ port_channel_member ] + port_type: + description: + - The type of the interface can be either access or fabric. + type: str + default: access + choices: [ access, fabric ] + role: + description: + - The role of the switch (node) can be either a leaf or a spine. + - The APIC defaults to leaf when unset during creation. + type: str + aliases: [ node_type ] + choices: [ leaf, spine ] + admin_state: + description: + - The Admin State of the Interface. + - The APIC defaults to up when unset during creation. + type: str + choices: [ up, down ] + interface_type: + description: + - The type of the interface. + type: str + default: switch_port + choices: [ switch_port, pc_or_vpc, fc, fc_port_channel, leaf_fabric, spine_access, spine_fabric ] + interface: + description: + - The address of the interface. + - The format of the interface value should be 1/1/1 (card/port_id/sub_port) or 1/1 (card/port_id). + - The Card ID must be in the range of 1 to 64. + - The Port ID must be in the range of 1 to 128. + - The Sub Port ID must be in the range of 0 to 16 + 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 + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:PortConfig). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" +EXAMPLES = r""" +- name: Add an interface with port channel(PC) policy group + cisco.aci.aci_interface_config: + host: apic + username: admin + password: SomeSecretPassword + role: leaf + port_type: access + interface_type: port_channel + policy_group: ans_test_port_channel + node: 502 + interface: "2/2/2" + state: present + delegate_to: localhost + +- name: Breakout the existing interface with "100g-4x" + cisco.aci.aci_interface_config: + host: apic + username: admin + password: SomeSecretPassword + role: leaf + port_type: access + node: 502 + interface: "2/2/2" + breakout: "100g-4x" + state: present + delegate_to: localhost + +- name: Query an access interface with node id + cisco.aci.aci_interface_config: + host: apic + username: admin + password: SomeSecretPassword + port_type: access + node: 201 + state: query + delegate_to: localhost + +- name: Query a fabric interface with node id + cisco.aci.aci_interface_config: + host: apic + username: admin + password: SomeSecretPassword + port_type: fabric + node: 202 + state: query + delegate_to: localhost + +- name: Query all access interfaces + cisco.aci.aci_interface_config: + host: apic + username: admin + password: SomeSecretPassword + port_type: access + state: query + delegate_to: localhost + +- name: Query all fabric interfaces + cisco.aci.aci_interface_config: + host: apic + username: admin + password: SomeSecretPassword + port_type: fabric + state: query + delegate_to: localhost + +- name: Remove a interface + cisco.aci.aci_interface_config: + host: apic + username: admin + password: SomeSecretPassword + port_type: access + node: 201 + interface: "1/1/1" + 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 + +PORT_TYPE_MAPPING = dict( + access=dict( + root_class="infraInfra", + root_class_rn="infra", + interface_class="infraPortConfig", + ), + fabric=dict( + root_class="fabricInst", + root_class_rn="fabric", + interface_class="fabricPortConfig", + ), +) + +ADMIN_STATE_MAPPING = {"up": "no", "down": "yes"} + + +POLICY_GROUP_MAPPING = dict( + switch_port="uni/infra/funcprof/accportgrp-{0}", + pc_or_vpc="uni/infra/funcprof/accbundle-{0}", + fc="uni/infra/funcprof/fcaccportgrp-{0}", + fc_port_channel="uni/infra/funcprof/fcaccbundle-{0}", + leaf_fabric="uni/fabric/funcprof/leportgrp-{0}", + spine_access="uni/infra/funcprof/spaccportgrp-{0}", + spine_fabric="uni/fabric/funcprof/spportgrp-{0}", +) + + +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"]), + breakout=dict(type="str", choices=["100g-2x", "100g-4x", "10g-4x", "25g-4x", "50g-8x"]), + description=dict(type="str", aliases=["descr"]), + node=dict(type="int", aliases=["node_id"]), + pc_member=dict(type="str", aliases=["port_channel_member"]), + port_type=dict(type="str", default="access", choices=["access", "fabric"]), + role=dict(type="str", choices=["leaf", "spine"], aliases=["node_type"]), + admin_state=dict(type="str", choices=["up", "down"]), + interface_type=dict( + type="str", + default="switch_port", + choices=["switch_port", "pc_or_vpc", "fc", "fc_port_channel", "leaf_fabric", "spine_access", "spine_fabric"], + ), + interface=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", ["node", "interface"]], + ["state", "present", ["node", "interface"]], + ], + mutually_exclusive=[("policy_group", "breakout")], + ) + + policy_group = module.params.get("policy_group") + breakout = module.params.get("breakout") + description = module.params.get("description") + node = module.params.get("node") + pc_member = module.params.get("pc_member") + port_type = module.params.get("port_type") + role = module.params.get("role") + admin_state = module.params.get("admin_state") + interface = module.params.get("interface") + interface_type = module.params.get("interface_type") + state = module.params.get("state") + + aci = ACIModule(module) + + try: + if node is not None and int(node) not in range(101, 4001): + aci.fail_json(msg="Node ID: {0} is invalid; it must be in the range of 101 to 4000.".format(node)) + + card, port_id, sub_port = (None, None, None) + if interface is not None: + interface_parts = interface.split("/") + if len(interface_parts) == 3: + card, port_id, sub_port = interface_parts + elif len(interface_parts) == 2: + card, port_id = interface_parts + sub_port = 0 + else: + aci.fail_json(msg="Interface: {0} is invalid; The format must be either card/port/sub_port(1/1/1) or card/port(1/1)".format(interface)) + + if int(card) not in range(1, 65): + aci.fail_json(msg="Card ID: {0} is invalid; it must be in the range of 1 to 64.".format(card)) + + if int(port_id) not in range(1, 129): + aci.fail_json(msg="Port ID: {0} is invalid; it must be in the range of 1 to 128.".format(port_id)) + + # Sub Port ID - 0 is default value + if int(sub_port) not in range(0, 17): + aci.fail_json(msg="Sub Port ID: {0} is invalid; it must be in the range of 0 to 16.".format(sub_port)) + except ValueError as error: + aci.fail_json(msg="Interface configuration failed due to: {0}".format(error)) + + root_class = PORT_TYPE_MAPPING.get(port_type)["root_class"] + root_class_rn = PORT_TYPE_MAPPING.get(port_type)["root_class_rn"] + interface_class = PORT_TYPE_MAPPING.get(port_type)["interface_class"] + + aci.construct_url( + root_class=dict( + aci_class=root_class, + aci_rn=root_class_rn, + ), + subclass_1=dict( + aci_class=interface_class, + aci_rn="portconfnode-{0}-card-{1}-port-{2}-sub-{3}".format(node, card, port_id, sub_port), + target_filter=dict(node=node), + ), + ) + + aci.get_existing() + + if breakout is None and policy_group: + policy_group_dn = POLICY_GROUP_MAPPING.get(interface_type).format(policy_group) + else: + # To handle the existing object property + policy_group_dn = "" + + if state == "present": + aci.payload( + aci_class=interface_class, + class_config=dict( + assocGrp=policy_group_dn, + brkoutMap=breakout, + card=card, + description=description, + node=node, + pcMember=pc_member, + port=port_id, + role=role, + shutdown=ADMIN_STATE_MAPPING.get(admin_state), + subPort=sub_port, + ), + ) + + aci.get_diff(aci_class=interface_class) + + 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_description.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_description.py new file mode 100644 index 000000000..c4a204c89 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_description.py @@ -0,0 +1,346 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_description +short_description: Setting and removing description on physical interfaces. +description: +- Setting and removing description on physical interfaces on Cisco ACI fabrics. +options: + pod_id: + description: + - The pod number. + - C(pod_id) is usually an integer below C(12) + type: int + aliases: [ pod, pod_number ] + node_id: + description: + - The switch ID that the C(interface) belongs to. + - The C(node_id) value is usually something like '101'. + type: int + aliases: [ leaf, spine, node ] + node_type: + description: + - The type of node the C(interface) is configured on. + type: str + choices: [ leaf, spine ] + interface: + description: + - The name of the C(interface) that is targeted. + - Usually an interface name with the following format C(1/7). + type: str + fex_id: + description: + - The fex ID that the C(interface) belongs to. + - The C(fex_id) value is usually something like '123'. + type: int + description: + description: + - The C(description) that should be attached to the C(interface). + 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 + +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Set Interface Description + cisco.aci.aci_interface_description: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + node_type: leaf + interface: 1/49 + description: foobar + state: present + delegate_to: localhost + +- name: Remove Interface Description + cisco.aci.aci_interface_description: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + node_type: leaf + interface: 1/49 + description: foobar + state: absent + delegate_to: localhost + +- name: Set Interface Description on Fex + cisco.aci.aci_interface_description: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + fex_id: 123 + interface: 1/49 + description: foobar + state: present + delegate_to: localhost + +- name: Remove Interface Description on Fex + cisco.aci.aci_interface_description: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + fex_id: 123 + interface: 1/49 + description: foobar + state: absent + delegate_to: localhost + +- name: Query Interface + cisco.aci.aci_interface_description: + host: "{{ inventory_hostname }}" + username: "{{ username }}" + password: "{{ password }}" + validate_certs: false + pod_id: 1 + node_id: 105 + node_type: leaf + interface: 1/49 + 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( + pod_id=dict(type="int", aliases=["pod", "pod_number"]), + node_id=dict(type="int", aliases=["leaf", "spine", "node"]), + fex_id=dict(type="int"), + node_type=dict(type="str", choices=["leaf", "spine"]), + interface=dict(type="str"), + description=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[ + ("node_type", "fex_id"), + ], + required_if=[ + ["state", "absent", ["pod_id", "node_id", "interface"]], + ["state", "present", ["pod_id", "node_id", "interface", "description"]], + ], + ) + + aci = ACIModule(module) + + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + interface = module.params.get("interface") + description = module.params.get("description") + fex_id = module.params.get("fex_id") + node_type = module.params.get("node_type") + state = module.params.get("state") + + class_name = "infraHPathS" + children = ["infraRsHPathAtt"] + if node_type == "spine": + class_name = "infraSHPathS" + children = ["infraRsSHPathAtt"] + rn = None + + if node_id and interface: + if fex_id: + rn = "hpaths-{0}_eth{1}_{2}".format(node_id, fex_id, interface.replace("/", "_")) + child_configs = [ + dict( + infraRsHPathAtt=dict( + attributes=dict(tDn="topology/pod-{0}/paths-{1}/extpaths-{2}/pathep-[eth{3}]".format(pod_id, node_id, fex_id, interface)) + ) + ), + ] + elif node_type == "spine": + rn = "shpaths-{0}_eth{1}".format(node_id, interface.replace("/", "_")) + child_configs = [ + dict(infraRsSHPathAtt=dict(attributes=dict(tDn="topology/pod-{0}/paths-{1}/pathep-[eth{2}]".format(pod_id, node_id, interface)))), + ] + elif node_type == "leaf": + rn = "hpaths-{0}_eth{1}".format(node_id, interface.replace("/", "_")) + child_configs = [ + dict(infraRsHPathAtt=dict(attributes=dict(tDn="topology/pod-{0}/paths-{1}/pathep-[eth{2}]".format(pod_id, node_id, interface)))), + ] + + dn = None + interface_name = None + infra_mo = None + if rn: + dn = "uni/infra/{0}".format(rn) + interface_name = rn.split("-")[1] + infra_mo = "infra" + + aci.construct_url( + root_class=dict(aci_class="infraInfra", aci_rn="infra", module_object=infra_mo, target_filter=dict(name="infra")), + subclass_1=dict(aci_class=class_name, aci_rn=rn, module_object=dn, target_filter=dict(name=interface_name)), + child_classes=children, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=class_name, + class_config=dict( + descr=description, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class=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_cdp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_cdp.py new file mode 100644 index 000000000..d017aeda1 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_cdp.py @@ -0,0 +1,267 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Tim Knipper <tim.knipper@gmail.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_cdp +short_description: Manage CDP interface policies (cdp:IfPol) +description: +- Manage CDP interface policies on Cisco ACI fabrics. +options: + cdp_policy: + description: + - The CDP interface policy name. + type: str + aliases: [ cdp_interface, name ] + description: + description: + - The description for the CDP interface policy name. + type: str + aliases: [ descr ] + admin_state: + description: + - Enable or Disable CDP state. + - The APIC defaults to C(true) when unset during creation. + type: bool + 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(cdp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Knipper (@tknipper11) +""" + +EXAMPLES = r""" +- name: Create CDP Interface Policy to enable CDP + cisco.aci.aci_interface_policy_cdp: + name: Ansible_CDP_Interface_Policy + host: apic.example.com + username: admin + password: adminpass + admin_state: true + state: present + +- name: Create CDP Interface Policy to disable CDP + cisco.aci.aci_interface_policy_cdp: + name: Ansible_CDP_Interface_Policy + host: apic.example.com + username: admin + password: adminpass + admin_state: false + state: present + +- name: Remove CDP Interface Policy + cisco.aci.aci_interface_policy_cdp: + name: Ansible_CDP_Interface_Policy + host: apic.example.com + username: admin + password: adminpass + output_level: debug + state: absent + +- name: Query CDP Policy + cisco.aci.aci_interface_policy_cdp: + host: apic.example.com + username: admin + password: adminpass + state: query +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "cdpIfPol": { + "attributes": { + "adminSt": "disabled", + "annotation": "", + "descr": "Ansible Created CDP Test Policy", + "dn": "uni/infra/cdpIfP-Ansible_CDP_Test_Policy", + "name": "Ansible_CDP_Test_Policy", + "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_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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + cdp_policy=dict(type="str", required=False, aliases=["cdp_interface", "name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + admin_state=dict(type="bool"), + 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", ["cdp_policy"]], + ["state", "present", ["cdp_policy"]], + ], + ) + + aci = ACIModule(module) + + cdp_policy = module.params.get("cdp_policy") + description = module.params.get("description") + admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="cdpIfPol", + aci_rn="infra/cdpIfP-{0}".format(cdp_policy), + module_object=cdp_policy, + target_filter={"name": cdp_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="cdpIfPol", + class_config=dict( + name=cdp_policy, + descr=description, + adminSt=admin_state, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="cdpIfPol") + + 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_fc.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py new file mode 100644 index 000000000..eaf8e1249 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_fc.py @@ -0,0 +1,268 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_fc +short_description: Manage Fibre Channel interface policies (fc:IfPol) +description: +- Manage ACI Fiber Channel interface policies on Cisco ACI fabrics. +options: + fc_policy: + description: + - The name of the Fiber Channel interface policy. + type: str + aliases: [ name ] + description: + description: + - The description of the Fiber Channel interface policy. + type: str + aliases: [ descr ] + port_mode: + description: + - The Port Mode to use. + - The APIC defaults to C(f) when unset during creation. + type: str + choices: [ f, np ] + 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(fc:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a Fibre Channel interface policy + cisco.aci.aci_interface_policy_fc: + host: apic + username: admin + password: SomeSecretPassword + fc_policy: fcpolicy1 + state: present + delegate_to: localhost + +- name: Delete a Fibre Channel interface policy + cisco.aci.aci_interface_policy_fc: + host: apic + username: admin + password: SomeSecretPassword + fc_policy: fcpolicy1 + state: absent + delegate_to: localhost + +- name: Query all Fibre Channel interface policies + cisco.aci.aci_interface_policy_fc: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Fibre Channel interface policy + cisco.aci.aci_interface_policy_fc: + host: apic + username: admin + password: SomeSecretPassword + fc_policy: fcpolicy1 + 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( + 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 + 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", ["fc_policy"]], + ["state", "present", ["fc_policy"]], + ], + ) + + fc_policy = module.params.get("fc_policy") + port_mode = module.params.get("port_mode") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fcIfPol", + aci_rn="infra/fcIfPol-{0}".format(fc_policy), + module_object=fc_policy, + target_filter={"name": fc_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fcIfPol", + class_config=dict( + name=fc_policy, + descr=description, + portMode=port_mode, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fcIfPol") + + 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_l2.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_l2.py new file mode 100644 index 000000000..fdf607bd5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_l2.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_l2 +short_description: Manage Layer 2 interface policies (l2:IfPol) +description: +- Manage Layer 2 interface policies on Cisco ACI fabrics. +options: + l2_policy: + description: + - The name of the Layer 2 interface policy. + type: str + aliases: [ name ] + description: + description: + - The description of the Layer 2 interface policy. + type: str + aliases: [ descr ] + qinq: + description: + - Determines if QinQ is disabled or if the port should be considered a core or edge port. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ core, disabled, edge ] + vepa: + description: + - Determines if Virtual Ethernet Port Aggregator is disabled or enabled. + - The APIC defaults to C(false) when unset during creation. + type: bool + vlan_scope: + description: + - The scope of the VLAN. + - The APIC defaults to C(global) when unset during creation. + type: str + choices: [ global, portlocal ] + 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(l2:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a Layer2 interface policy + cisco.aci.aci_interface_policy_l2: + host: apic + username: admin + password: SomeSecretPassword + l2_policy: PORT_LOCAL + vlan_scope: portlocal + state: present + delegate_to: localhost + +- name: Delete a Layer2 interface policy + cisco.aci.aci_interface_policy_l2: + host: apic + username: admin + password: SomeSecretPassword + l2_policy: PORT_LOCAL + state: absent + delegate_to: localhost + +- name: Query all Layer2 interface policies + cisco.aci.aci_interface_policy_l2: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Layer2 interface policy + cisco.aci.aci_interface_policy_l2: + host: apic + username: admin + password: SomeSecretPassword + l2_policy: PORT_LOCAL + 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 + +# Mapping dicts are used to normalize the proposed data to what the APIC expects, which will keep diffs accurate +QINQ_MAPPING = dict( + core="corePort", + disabled="disabled", + edge="edgePort", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + l2_policy=dict(type="str", aliases=["name"]), # Not required for querying all policies + description=dict(type="str", aliases=["descr"]), + vlan_scope=dict(type="str", choices=["global", "portlocal"]), # No default provided on purpose + qinq=dict(type="str", choices=["core", "disabled", "edge"]), + vepa=dict(type="bool"), + 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", ["l2_policy"]], + ["state", "present", ["l2_policy"]], + ], + ) + + aci = ACIModule(module) + + l2_policy = module.params.get("l2_policy") + vlan_scope = module.params.get("vlan_scope") + qinq = module.params.get("qinq") + if qinq is not None: + qinq = QINQ_MAPPING.get(qinq) + vepa = aci.boolean(module.params.get("vepa"), "enabled", "disabled") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="l2IfPol", + aci_rn="infra/l2IfP-{0}".format(l2_policy), + module_object=l2_policy, + target_filter={"name": l2_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="l2IfPol", + class_config=dict( + name=l2_policy, + descr=description, + vlanScope=vlan_scope, + qinq=qinq, + vepa=vepa, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="l2IfPol") + + 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_breakout_port_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_breakout_port_group.py new file mode 100644 index 000000000..e0f26e7e2 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_breakout_port_group.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Cindy Zhao <cizhao@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_breakout_port_group +short_description: Manage fabric interface policy leaf breakout port group (infra:BrkoutPortGrp) +description: +- Manage fabric interface policy leaf breakout port group on Cisco ACI fabrics. +options: + breakout_port_group: + description: + - Name of the leaf breakout port group to be added/deleted. + type: str + aliases: [ name ] + description: + description: + - Description for the leaf breakout port group to be created. + type: str + aliases: [ descr ] + breakout_map: + description: + - The mapping of breakout port. + 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 + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:BrkoutPortGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Cindy Zhao (@cizhao) +""" + +EXAMPLES = r""" +- name: Create a Leaf Breakout Port Group + cisco.aci.aci_interface_policy_leaf_breakout_port_group: + host: apic + username: admin + password: SomeSecretPassword + breakout_port_group: BreakoutPortName + breakout_map: 10g-4x + state: present + delegate_to: localhost + +- name: Query all Leaf Breakout Port Groups of type link + cisco.aci.aci_interface_policy_leaf_breakout_port_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Leaf Breakout Port Group + cisco.aci.aci_interface_policy_leaf_breakout_port_group: + host: apic + username: admin + password: SomeSecretPassword + breakout_port_group: BreakoutPortName + state: query + delegate_to: localhost + register: query_result + +- name: Delete an Leaf Breakout Port Group + cisco.aci.aci_interface_policy_leaf_breakout_port_group: + host: apic + username: admin + password: SomeSecretPassword + breakout_port_group: BreakoutPortName + 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( + breakout_port_group=dict(type="str", aliases=["name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + breakout_map=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", ["breakout_port_group"]], + ["state", "present", ["breakout_port_group"]], + ], + ) + + breakout_port_group = module.params.get("breakout_port_group") + description = module.params.get("description") + breakout_map = module.params.get("breakout_map") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="infraBrkoutPortGrp", + aci_rn="infra/funcprof/brkoutportgrp-{0}".format(breakout_port_group), + module_object=breakout_port_group, + target_filter={"name": breakout_port_group}, + ), + child_classes=[], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraBrkoutPortGrp", + class_config=dict( + name=breakout_port_group, + descr=description, + brkoutMap=breakout_map, + ), + ) + + aci.get_diff(aci_class="infraBrkoutPortGrp") + + 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 new file mode 100644 index 000000000..1a1ad21b2 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py @@ -0,0 +1,587 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.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_policy_group +short_description: Manage fabric interface policy leaf policy groups (infra:AccBndlGrp, infra:AccPortGrp) +description: +- Manage fabric interface policy leaf policy groups on Cisco ACI fabrics. +options: + policy_group: + description: + - Name of the leaf policy group to be added/deleted. + type: str + aliases: [ name, policy_group_name ] + description: + description: + - Description for the leaf policy group to be created. + type: str + aliases: [ descr ] + lag_type: + description: + - Selector for the type of leaf policy group we want to create. + - C(leaf) for Leaf Access Port Policy Group + - C(link) for Port Channel (PC) + - C(node) for Virtual Port Channel (VPC) + type: str + required: true + choices: [ leaf, link, node ] + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + type: str + aliases: [ port_security_policy_name ] + aep: + description: + - Choice of attached_entity_profile (AEP) to be used as part of the leaf policy group to be created. + type: str + aliases: [ aep_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: +- 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. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:AccBndlGrp) and B(infra:AccPortGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +""" + +EXAMPLES = r""" +- name: Create a Port Channel (PC) Interface Policy Group + cisco.aci.aci_interface_policy_leaf_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: link + policy_group: policygroupname + description: policygroupname description + link_level_policy: whateverlinklevelpolicy + cdp_policy: whatevercdppolicy + lldp_policy: whateverlldppolicy + port_channel_policy: whateverlacppolicy + state: present + delegate_to: localhost + +- name: Create a Virtual Port Channel (VPC) Interface Policy Group + cisco.aci.aci_interface_policy_leaf_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: node + policy_group: policygroupname + link_level_policy: whateverlinklevelpolicy + cdp_policy: whatevercdppolicy + lldp_policy: whateverlldppolicy + port_channel_policy: whateverlacppolicy + state: present + delegate_to: localhost + +- name: Create a Leaf Access Port Policy Group + cisco.aci.aci_interface_policy_leaf_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: leaf + policy_group: policygroupname + link_level_policy: whateverlinklevelpolicy + cdp_policy: whatevercdppolicy + lldp_policy: whateverlldppolicy + state: present + delegate_to: localhost + +- name: Query all Leaf Access Port Policy Groups of type link + cisco.aci.aci_interface_policy_leaf_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: link + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Lead Access Port Policy Group + cisco.aci.aci_interface_policy_leaf_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: leaf + 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_policy_group: + host: apic + username: admin + password: SomeSecretPassword + lag_type: leaf + 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: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)) + 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"]), + link_level_policy=dict(type="str", aliases=["link_level_policy_name"]), + cdp_policy=dict(type="str", aliases=["cdp_policy_name"]), + mcp_policy=dict(type="str", aliases=["mcp_policy_name"]), + lldp_policy=dict(type="str", aliases=["lldp_policy_name"]), + stp_interface_policy=dict(type="str", aliases=["stp_interface_policy_name"]), + egress_data_plane_policing_policy=dict(type="str", aliases=["egress_data_plane_policing_policy_name"]), + ingress_data_plane_policing_policy=dict(type="str", aliases=["ingress_data_plane_policing_policy_name"]), + priority_flow_control_policy=dict(type="str", aliases=["priority_flow_control_policy_name"]), + fibre_channel_interface_policy=dict(type="str", aliases=["fibre_channel_interface_policy_name"]), + slow_drain_policy=dict(type="str", aliases=["slow_drain_policy_name"]), + port_channel_policy=dict(type="str", aliases=["port_channel_policy_name"]), + monitoring_policy=dict(type="str", aliases=["monitoring_policy_name"]), + 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"]), + aep=dict(type="str", aliases=["aep_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", ["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") + link_level_policy = module.params.get("link_level_policy") + cdp_policy = module.params.get("cdp_policy") + mcp_policy = module.params.get("mcp_policy") + lldp_policy = module.params.get("lldp_policy") + stp_interface_policy = module.params.get("stp_interface_policy") + egress_data_plane_policing_policy = module.params.get("egress_data_plane_policing_policy") + ingress_data_plane_policing_policy = module.params.get("ingress_data_plane_policing_policy") + priority_flow_control_policy = module.params.get("priority_flow_control_policy") + fibre_channel_interface_policy = module.params.get("fibre_channel_interface_policy") + slow_drain_policy = module.params.get("slow_drain_policy") + port_channel_policy = module.params.get("port_channel_policy") + monitoring_policy = module.params.get("monitoring_policy") + 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") + aep = module.params.get("aep") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + if lag_type == "leaf" and port_channel_policy is not None: + aci.fail_json( + "port_channel_policy is not a valid parameter for leaf\ + (leaf access port policy group), if used\ + assign null to it (port_channel_policy: null)." + ) + + if lag_type == "leaf": + aci_class_name = "infraAccPortGrp" + dn_name = "accportgrp" + class_config_dict = dict( + name=policy_group, + descr=description, + nameAlias=name_alias, + ) + # Reset for target_filter + lag_type = None + elif lag_type in ("link", "node"): + aci_class_name = "infraAccBndlGrp" + dn_name = "accbundle" + class_config_dict = dict( + name=policy_group, + descr=description, + lagT=lag_type, + nameAlias=name_alias, + ) + + child_configs = [ + dict( + infraRsCdpIfPol=dict( + attributes=dict( + tnCdpIfPolName=cdp_policy, + ), + ), + ), + dict( + infraRsFcIfPol=dict( + attributes=dict( + tnFcIfPolName=fibre_channel_interface_policy, + ), + ), + ), + dict( + infraRsHIfPol=dict( + attributes=dict( + tnFabricHIfPolName=link_level_policy, + ), + ), + ), + dict( + infraRsL2IfPol=dict( + attributes=dict( + tnL2IfPolName=l2_interface_policy, + ), + ), + ), + dict( + infraRsL2PortSecurityPol=dict( + attributes=dict( + tnL2PortSecurityPolName=port_security_policy, + ), + ), + ), + dict( + infraRsLacpPol=dict( + attributes=dict( + tnLacpLagPolName=port_channel_policy, + ), + ), + ), + dict( + infraRsLldpIfPol=dict( + attributes=dict( + tnLldpIfPolName=lldp_policy, + ), + ), + ), + dict( + infraRsMcpIfPol=dict( + attributes=dict( + tnMcpIfPolName=mcp_policy, + ), + ), + ), + dict( + infraRsMonIfInfraPol=dict( + attributes=dict( + tnMonInfraPolName=monitoring_policy, + ), + ), + ), + dict( + infraRsQosEgressDppIfPol=dict( + attributes=dict( + tnQosDppPolName=egress_data_plane_policing_policy, + ), + ), + ), + dict( + infraRsQosIngressDppIfPol=dict( + attributes=dict( + tnQosDppPolName=ingress_data_plane_policing_policy, + ), + ), + ), + dict( + infraRsQosPfcIfPol=dict( + attributes=dict( + tnQosPfcIfPolName=priority_flow_control_policy, + ), + ), + ), + dict( + infraRsQosSdIfPol=dict( + attributes=dict( + tnQosSdIfPolName=slow_drain_policy, + ), + ), + ), + dict( + infraRsStormctrlIfPol=dict( + attributes=dict( + tnStormctrlIfPolName=storm_control_interface_policy, + ), + ), + ), + dict( + infraRsStpIfPol=dict( + attributes=dict( + tnStpIfPolName=stp_interface_policy, + ), + ), + ), + ] + + # Add infraRsattEntP binding only when aep was defined + if aep is not None: + child_configs.append( + dict( + infraRsAttEntP=dict( + attributes=dict( + tDn="uni/infra/attentp-{0}".format(aep), + ), + ), + ) + ) + + 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, "lagT": lag_type}, + ), + child_classes=[ + "infraRsAttEntP", + "infraRsCdpIfPol", + "infraRsFcIfPol", + "infraRsHIfPol", + "infraRsL2IfPol", + "infraRsL2PortSecurityPol", + "infraRsLacpPol", + "infraRsLldpIfPol", + "infraRsMcpIfPol", + "infraRsMonIfInfraPol", + "infraRsQosEgressDppIfPol", + "infraRsQosIngressDppIfPol", + "infraRsQosPfcIfPol", + "infraRsQosSdIfPol", + "infraRsStormctrlIfPol", + "infraRsStpIfPol", + ], + ) + + 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_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_profile.py new file mode 100644 index 000000000..39c3e1ecd --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_profile.py @@ -0,0 +1,316 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> +# Copyright: (c) 2020, Shreyas Srish <ssrish@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_profile +short_description: Manage fabric interface policy leaf profiles (infra:AccPortP) +description: +- Manage fabric interface policy leaf profiles on Cisco ACI fabrics. +options: + interface_profile: + description: + - The name of the Fabric access policy leaf interface profile. + type: str + aliases: [ name, leaf_interface_profile_name, leaf_interface_profile, interface_profile_name ] + description: + description: + - Description for the Fabric access policy leaf interface 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 + type: + description: + - The type of profile to be created. + type: str + choices: [ fex, leaf ] + default: leaf +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:AccPortP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new leaf_interface_profile + cisco.aci.aci_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + description: leafintprfname description + state: present + delegate_to: localhost + +- name: Add a new leaf_interface_profile of type fex + cisco.aci.aci_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname_fex + type: fex + description: leafintprfname description + state: present + delegate_to: localhost + +- name: Remove a leaf_interface_profile + cisco.aci.aci_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + state: absent + delegate_to: localhost + +- name: Remove a leaf_interface_profile of type fex + cisco.aci.aci_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname_fex + type: fex + state: absent + delegate_to: localhost + +- name: Query a leaf_interface_profile + cisco.aci.aci_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname + state: query + delegate_to: localhost + register: query_result + +- name: Query a leaf_interface_profile of type fex + cisco.aci.aci_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: leafintprfname_fex + type: fex + state: query + delegate_to: localhost + register: query_result + +- name: Query all leaf_interface_profiles + cisco.aci.aci_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Query all leaf_interface_profiles of type fex + cisco.aci.aci_interface_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + type: fex + 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( + interface_profile=dict(type="str", aliases=["name", "leaf_interface_profile_name", "leaf_interface_profile", "interface_profile_name"]), + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + type=dict(type="str", default="leaf", choices=["fex", "leaf"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["interface_profile"]], + ["state", "present", ["interface_profile"]], + ], + ) + + interface_profile = module.params.get("interface_profile") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + type_profile = module.params.get("type") + + aci = ACIModule(module) + aci_class = "infraAccPortP" + aci_rn = "accportprof" + if type_profile == "fex": + aci_class = "infraFexP" + aci_rn = "fexprof" + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn="infra/" + aci_rn + "-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_class, + class_config=dict( + name=interface_profile, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class=aci_class) + + 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_profile_fex_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_profile_fex_policy_group.py new file mode 100644 index 000000000..8b0208546 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_profile_fex_policy_group.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, 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_interface_policy_leaf_profile_fex_policy_group +short_description: Manage leaf interface profiles fex policy group (infra:FexBndlGrp) +description: +- Manage leaf interface profiles fex policy group on Cisco ACI fabrics. +options: + name: + description: + - The name of the Fex Profile Policy Group. + type: str + aliases: [ policy_group ] + fex_profile: + description: + - The name of the Fex Profile. + 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 + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:FexBndlGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sabari Jaganathan (@sajagana) +""" + +EXAMPLES = r""" +- name: Add a new fex policy group + cisco.aci.aci_interface_policy_leaf_profile_fex_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: fex_policy_group + fex_profile: anstest_fex_profile + state: present + delegate_to: localhost + +- name: Add list of fex policy groups + cisco.aci.aci_interface_policy_leaf_profile_fex_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: "{{ item.name }}" + fex_profile: "{{ item.fex_profile }}" + state: present + delegate_to: localhost + with_items: + - name: fex_policy_group_1 + fex_profile: anstest_fex_profile + - name: fex_policy_group_2 + fex_profile: anstest_fex_profile + +- name: Query a fex policy group under fex profile + cisco.aci.aci_interface_policy_leaf_profile_fex_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: fex_policy_group + fex_profile: anstest_fex_profile + state: query + delegate_to: localhost + +- name: Query all fex policy groups under fex profile + cisco.aci.aci_interface_policy_leaf_profile_fex_policy_group: + host: apic + username: admin + password: SomeSecretPassword + fex_profile: anstest_fex_profile + state: query + delegate_to: localhost + +- name: Query all fex policy groups with name + cisco.aci.aci_interface_policy_leaf_profile_fex_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: fex_policy_group + state: query + delegate_to: localhost + +- name: Query all fex policy groups + cisco.aci.aci_interface_policy_leaf_profile_fex_policy_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove fex policy group + cisco.aci.aci_interface_policy_leaf_profile_fex_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: fex_policy_group + fex_profile: anstest_fex_profile + 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"]), + fex_profile=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", "fex_profile"]], + ["state", "present", ["name", "fex_profile"]], + ], + ) + + name = module.params.get("name") + fex_profile = module.params.get("fex_profile") + state = module.params.get("state") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="infraFexP", + aci_rn="infra/fexprof-{0}".format(fex_profile), + module_object=fex_profile, + target_filter={"name": fex_profile}, + ), + subclass_1=dict( + aci_class="infraFexBndlGrp", + aci_rn="fexbundle-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraFexBndlGrp", + class_config=dict( + name=name, + ), + ) + + aci.get_diff(aci_class="infraFexBndlGrp") + + 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_link_level.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_link_level.py new file mode 100644 index 000000000..5c0120d4d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_link_level.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Vasily Prokopov (@vasilyprokopov) +# 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_link_level +short_description: Manage Link Level interface policies (fabric:HIfPol) +description: +- The link level interface policy specifies the layer 1 parameters of switch interfaces. +options: + link_level_policy: + description: + - The name of the Link Level interface policy. + type: str + aliases: [ name ] + description: + description: + - The description of the Link Level interface policy. + type: str + aliases: [ descr ] + auto_negotiation: + description: + - Auto-negotiation enables devices to automatically exchange information over a link about speed and duplex abilities. + - The APIC defaults to C(on) when unset during creation. + type: bool + default: true + speed: + description: + - Determines the interface policy administrative port speed. + - The APIC defaults to C(inherit) when unset during creation. + type: str + choices: [ 100M, 1G, 10G, 25G, 40G, 50G, 100G, 200G, 400G, inherit ] + default: inherit + link_debounce_interval: + description: + - Enables the debounce timer for physical interface ports and sets it for a specified amount of time in milliseconds. + - The APIC defaults to C(100) when unset during creation. + type: int + default: 100 + forwarding_error_correction: + description: + - Determines the forwarding error correction (FEC) mode. + - The APIC defaults to C(inherit) when unset during creation. + type: str + choices: [ inherit, kp-fec, cl91-rs-fec, cl74-fc-fec, disable-fec, ieee-rs-fec, cons16-rs-fec ] + default: inherit + 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:HIfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Vasily Prokopov (@vasilyprokopov) +""" + +EXAMPLES = r""" +- name: Add a Link Level Policy + cisco.aci.aci_interface_policy_link_level: + host: apic + username: admin + password: SomeSecretPassword + link_level_policy: link_level_policy_test + description: via Ansible + auto_negotiation: on + speed: 100M + link_debounce_interval: 100 + forwarding_error_correction: cl91-rs-fec + state: present + delegate_to: localhost + +- name: Remove a Link Level Policy + cisco.aci.aci_interface_policy_link_level: + host: apic + username: admin + password: SomeSecretPassword + link_level_policy: ansible_test + state: absent + +- name: Query a Link Level Policy + cisco.aci.aci_interface_policy_link_level: + host: apic + username: admin + password: SomeSecretPassword + link_level_policy: link_level_policy_test + state: query + delegate_to: localhost + +- name: Query all Link Level Policies + cisco.aci.aci_interface_policy_link_level: + 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( + link_level_policy=dict(type="str", aliases=["name"]), + description=dict(type="str", aliases=["descr"]), + auto_negotiation=dict(type="bool", default="true"), + speed=dict(type="str", default="inherit", choices=["100M", "1G", "10G", "25G", "40G", "50G", "100G", "200G", "400G", "inherit"]), + link_debounce_interval=dict(type="int", default="100"), + forwarding_error_correction=dict( + type="str", default="inherit", choices=["inherit", "kp-fec", "cl91-rs-fec", "cl74-fc-fec", "disable-fec", "ieee-rs-fec", "cons16-rs-fec"] + ), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["link_level_policy"]], + ["state", "present", ["link_level_policy"]], + ], + ) + + aci = ACIModule(module) + + link_level_policy = module.params["link_level_policy"] + description = module.params["description"] + auto_negotiation = aci.boolean(module.params["auto_negotiation"], "on", "off") + speed = module.params["speed"] + link_debounce_interval = module.params["link_debounce_interval"] + if link_debounce_interval is not None and link_debounce_interval not in range(0, 5001): + module.fail_json(msg='The "link_debounce_interval" must be a value between 0 and 5000') + forwarding_error_correction = module.params["forwarding_error_correction"] + state = module.params["state"] + + aci.construct_url( + root_class=dict( + aci_class="fabricHIfPol", + aci_rn="infra/hintfpol-{0}".format(link_level_policy), + module_object=link_level_policy, + target_filter={"name": link_level_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricHIfPol", + class_config=dict( + name=link_level_policy, + descr=description, + autoNeg=auto_negotiation, + speed=speed, + linkDebounce=link_debounce_interval, + fecMode=forwarding_error_correction, + ), + ) + + aci.get_diff(aci_class="fabricHIfPol") + + 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_lldp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py new file mode 100644 index 000000000..8c1299374 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_lldp.py @@ -0,0 +1,278 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_lldp +short_description: Manage LLDP interface policies (lldp:IfPol) +description: +- Manage LLDP interface policies on Cisco ACI fabrics. +options: + lldp_policy: + description: + - The LLDP interface policy name. + type: str + aliases: [ name ] + description: + description: + - The description for the LLDP interface policy name. + type: str + aliases: [ descr ] + receive_state: + description: + - Enable or disable Receive state. + - The APIC defaults to C(true) when unset during creation. + type: bool + transmit_state: + description: + - Enable or Disable Transmit state. + - The APIC defaults to C(true) when unset during creation. + type: bool + 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(lldp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a LLDP interface policy + cisco.aci.aci_interface_policy_lldp: + host: apic + username: admin + password: SomeSecretPassword + lldp_policy: LLDP_OFF + receive_state: false + transmit_state: false + state: present + delegate_to: localhost + +- name: Delete a LLDP interface policy + cisco.aci.aci_interface_policy_lldp: + host: apic + username: admin + password: SomeSecretPassword + lldp_policy: LLDP_OFF + state: absent + delegate_to: localhost + +- name: Query all LLDP interface policies + cisco.aci.aci_interface_policy_lldp: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific LLDP interface policy + cisco.aci.aci_interface_policy_lldp: + host: apic + username: admin + password: SomeSecretPassword + lldp_policy: LLDP_OFF + 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( + lldp_policy=dict(type="str", aliases=["name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + receive_state=dict(type="bool"), + transmit_state=dict(type="bool"), + 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", ["lldp_policy"]], + ["state", "present", ["lldp_policy"]], + ], + ) + + aci = ACIModule(module) + + lldp_policy = module.params.get("lldp_policy") + description = module.params.get("description") + receive_state = aci.boolean(module.params.get("receive_state"), "enabled", "disabled") + transmit_state = aci.boolean(module.params.get("transmit_state"), "enabled", "disabled") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="lldpIfPol", + aci_rn="infra/lldpIfP-{0}".format(lldp_policy), + module_object=lldp_policy, + target_filter={"name": lldp_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="lldpIfPol", + class_config=dict( + name=lldp_policy, + descr=description, + adminRxSt=receive_state, + adminTxSt=transmit_state, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="lldpIfPol") + + 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_mcp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py new file mode 100644 index 000000000..1a25eced5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_mcp.py @@ -0,0 +1,269 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_mcp +short_description: Manage MCP interface policies (mcp:IfPol) +description: +- Manage MCP interface policies on Cisco ACI fabrics. +options: + mcp: + description: + - The name of the MCP interface. + type: str + aliases: [ mcp_interface, name ] + description: + description: + - The description for the MCP interface. + type: str + aliases: [ descr ] + admin_state: + description: + - Enable or disable admin state. + - The APIC defaults to C(true) when unset during creation. + type: bool + 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(mcp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a MCP interface policy + cisco.aci.aci_interface_policy_mcp: + host: apic + username: admin + password: SomeSecretPassword + mcp: MCP_OFF + admin_state: false + state: present + delegate_to: localhost + +- name: Delete a MCP interface policy + cisco.aci.aci_interface_policy_mcp: + host: apic + username: admin + password: SomeSecretPassword + mcp: MCP_OFF + state: absent + delegate_to: localhost + +- name: Query all MCP interface policies + cisco.aci.aci_interface_policy_mcp: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific MCP interface policy + cisco.aci.aci_interface_policy_mcp: + host: apic + username: admin + password: SomeSecretPassword + mcp: MCP_OFF + 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( + 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"), + 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", ["mcp"]], + ["state", "present", ["mcp"]], + ], + ) + + aci = ACIModule(module) + + mcp = module.params.get("mcp") + description = module.params.get("description") + admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="mcpIfPol", + aci_rn="infra/mcpIfP-{0}".format(mcp), + module_object=mcp, + target_filter={"name": mcp}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="mcpIfPol", + class_config=dict( + name=mcp, + descr=description, + adminSt=admin_state, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="mcpIfPol") + + 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_ospf.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py new file mode 100644 index 000000000..040e13963 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_ospf.py @@ -0,0 +1,408 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (dagwieers) <dag@wieers.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_ospf +short_description: Manage OSPF interface policies (ospf:IfPol) +description: +- Manage OSPF interface policies on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the Tenant the OSPF interface policy should belong to. + type: str + aliases: [ tenant_name ] + ospf: + description: + - The OSPF interface policy name. + - This name can be between 1 and 64 alphanumeric characters. + - Note that you cannot change this name after the object has been saved. + type: str + aliases: [ ospf_interface, name ] + description: + description: + - The description for the OSPF interface. + type: str + aliases: [ descr ] + network_type: + description: + - The OSPF interface policy network type. + - OSPF supports broadcast and point-to-point. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ bcast, p2p ] + cost: + description: + - The OSPF cost of the interface. + - The cost (also called metric) of an interface in OSPF is an indication of + the overhead required to send packets across a certain interface. The + cost of an interface is inversely proportional to the bandwidth of that + interface. A higher bandwidth indicates a lower cost. There is more + overhead (higher cost) and time delays involved in crossing a 56k serial + line than crossing a 10M ethernet line. The formula used to calculate the + cost is C(cost= 10000 0000/bandwith in bps) For example, it will cost + 10 EXP8/10 EXP7 = 10 to cross a 10M Ethernet line and will cost + 10 EXP8/1544000 = 64 to cross a T1 line. + - By default, the cost of an interface is calculated based on the bandwidth; + you can force the cost of an interface with the ip ospf cost value + interface subconfiguration mode command. + - Accepted values range between C(1) and C(450). + - The APIC defaults to C(0) when unset during creation. + type: int + controls: + description: + - The interface policy controls. + - 'This is a list of one or more of the following controls:' + - C(advert-subnet) -- Advertise IP subnet instead of a host mask in the router LSA. + - C(bfd) -- Bidirectional Forwarding Detection + - C(mtu-ignore) -- Disables MTU mismatch detection on an interface. + - C(passive) -- The interface does not participate in the OSPF protocol and + will not establish adjacencies or send routing updates. However the + interface is announced as part of the routing network. + type: list + elements: str + choices: [ advert-subnet, bfd, mtu-ignore, passive ] + dead_interval: + description: + - The interval between hello packets from a neighbor before the router + declares the neighbor as down. + - This value must be the same for all networking devices on a specific network. + - Specifying a smaller dead interval (seconds) will give faster detection + of a neighbor being down and improve convergence, but might cause more + routing instability. + - Accepted values range between C(1) and C(65535). + - The APIC defaults to C(40) when unset during creation. + type: int + hello_interval: + description: + - The interval between hello packets that OSPF sends on the interface. + - Note that the smaller the hello interval, the faster topological changes will be detected, but more routing traffic will ensue. + - This value must be the same for all routers and access servers on a specific network. + - Accepted values range between C(1) and C(65535). + - The APIC defaults to C(10) when unset during creation. + type: int + prefix_suppression: + description: + - Whether prefix suppressions is enabled or disabled. + - The APIC defaults to C(inherit) when unset during creation. + type: bool + priority: + description: + - The priority for the OSPF interface profile. + - Accepted values ranges between C(0) and C(255). + - The APIC defaults to C(1) when unset during creation. + type: int + retransmit_interval: + description: + - The interval between LSA retransmissions. + - The retransmit interval occurs while the router is waiting for an acknowledgement from the neighbor router that it received the LSA. + - If no acknowledgment is received at the end of the interval, then the LSA is resent. + - Accepted values range between C(1) and C(65535). + - The APIC defaults to C(5) when unset during creation. + type: int + transmit_delay: + description: + - The delay time needed to send an LSA update packet. + - OSPF increments the LSA age time by the transmit delay amount before transmitting the LSA update. + - You should take into account the transmission and propagation delays for the interface when you set this value. + - Accepted values range between C(1) and C(450). + - The APIC defaults to C(1) when unset during creation. + type: int + 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(ospf:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Ensure ospf interface policy exists + cisco.aci.aci_interface_policy_ospf: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ospf: ospf1 + state: present + delegate_to: localhost + +- name: Ensure ospf interface policy does not exist + cisco.aci.aci_interface_policy_ospf: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ospf: ospf1 + state: present + delegate_to: localhost + +- name: Query an ospf interface policy + cisco.aci.aci_interface_policy_ospf: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ospf: ospf1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all ospf interface policies in tenant production + cisco.aci.aci_interface_policy_ospf: + host: apic + username: admin + password: SomeSecretPassword + 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 + + +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 + 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"]), + cost=dict(type="int"), + controls=dict(type="list", elements="str", choices=["advert-subnet", "bfd", "mtu-ignore", "passive"]), + dead_interval=dict(type="int"), + hello_interval=dict(type="int"), + prefix_suppression=dict(type="bool"), + priority=dict(type="int"), + retransmit_interval=dict(type="int"), + transmit_delay=dict(type="int"), + 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", ["ospf", "tenant"]], + ["state", "present", ["ospf", "tenant"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + 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")) + + cost = module.params.get("cost") + if cost is not None and cost not in range(1, 451): + module.fail_json(msg="Parameter 'cost' is only valid in range between 1 and 450.") + + dead_interval = module.params.get("dead_interval") + if dead_interval is not None and dead_interval not in range(1, 65536): + module.fail_json(msg="Parameter 'dead_interval' is only valid in range between 1 and 65536.") + + hello_interval = module.params.get("hello_interval") + 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.") + + retransmit_interval = module.params.get("retransmit_interval") + if retransmit_interval is not None and retransmit_interval not in range(1, 65536): + module.fail_json(msg="Parameter 'retransmit_interval' is only valid in range between 1 and 65536.") + + transmit_delay = module.params.get("transmit_delay") + if transmit_delay is not None and transmit_delay not in range(1, 451): + module.fail_json(msg="Parameter 'transmit_delay' is only valid in range between 1 and 450.") + + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="ospfIfPol", + aci_rn="tn-{0}/ospfIfPol-{1}".format(tenant, ospf), + module_object=ospf, + target_filter={"name": ospf}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="ospfIfPol", + class_config=dict( + name=ospf, + descr=description, + cost=cost, + ctrl=controls, + deadIntvl=dead_interval, + helloIntvl=hello_interval, + nwT=network_type, + pfxSuppress=prefix_suppression, + prio=priority, + rexmitIntvl=retransmit_interval, + xmitDelay=transmit_delay, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="ospfIfPol") + + 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_port_channel.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py new file mode 100644 index 000000000..028bd849b --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_channel.py @@ -0,0 +1,352 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_port_channel +short_description: Manage port channel interface policies (lacp:LagPol) +description: +- Manage port channel interface policies on Cisco ACI fabrics. +options: + port_channel: + description: + - Name of the port channel. + type: str + aliases: [ name ] + description: + description: + - The description for the port channel. + type: str + aliases: [ descr ] + max_links: + description: + - Maximum links. + - Accepted values range between 1 and 16. + - The APIC defaults to C(16) when unset during creation. + type: int + min_links: + description: + - Minimum links. + - Accepted values range between 1 and 16. + - The APIC defaults to C(1) when unset during creation. + type: int + mode: + description: + - Port channel interface policy mode. + - Determines the LACP method to use for forming port-channels. + - The APIC defaults to C(off) when unset during creation. + type: str + choices: [ active, mac-pin, mac-pin-nicload, 'off', passive ] + fast_select: + description: + - Determines if Fast Select is enabled for Hot Standby Ports. + - This makes up the LACP Policy Control Policy; if one setting is defined, then all other Control Properties + left undefined or set to false will not exist after the task is ran. + - The APIC defaults to C(true) when unset during creation. + type: bool + graceful_convergence: + description: + - Determines if Graceful Convergence is enabled. + - This makes up the LACP Policy Control Policy; if one setting is defined, then all other Control Properties + left undefined or set to false will not exist after the task is ran. + - The APIC defaults to C(true) when unset during creation. + type: bool + load_defer: + description: + - Determines if Load Defer is enabled. + - This makes up the LACP Policy Control Policy; if one setting is defined, then all other Control Properties + left undefined or set to false will not exist after the task is ran. + - The APIC defaults to C(false) when unset during creation. + type: bool + suspend_individual: + description: + - Determines if Suspend Individual is enabled. + - This makes up the LACP Policy Control Policy; if one setting is defined, then all other Control Properties + left undefined or set to false will not exist after the task is ran. + - The APIC defaults to C(true) when unset during creation. + type: bool + symmetric_hash: + description: + - Determines if Symmetric Hashing is enabled. + - This makes up the LACP Policy Control Policy; if one setting is defined, then all other Control Properties + left undefined or set to false will not exist after the task is ran. + - The APIC defaults to C(false) when unset during creation. + type: bool + 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(lacp:LagPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a Port Channel interface policy + cisco.aci.aci_interface_policy_port_channel: + host: apic + username: admin + password: SomeSecretPassword + port_channel: LACP_ACTIVE + mode: active + min_links: 1 + max_links: 16 + state: present + delegate_to: localhost + +- name: Delete a Port Channel interface policy + cisco.aci.aci_interface_policy_port_channel: + host: apic + username: admin + password: SomeSecretPassword + port_channel: LACP_ACTIVE + state: absent + delegate_to: localhost + +- name: Query all Port Channel interface policies + cisco.aci.aci_interface_policy_port_channel: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Port Channel interface policy + cisco.aci.aci_interface_policy_port_channel: + host: apic + username: admin + password: SomeSecretPassword + port_channel: LACP_ACTIVE + 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( + port_channel=dict(type="str", aliases=["name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + min_links=dict(type="int"), + max_links=dict(type="int"), + mode=dict(type="str", choices=["active", "mac-pin", "mac-pin-nicload", "off", "passive"]), + fast_select=dict(type="bool"), + graceful_convergence=dict(type="bool"), + load_defer=dict(type="bool"), + suspend_individual=dict(type="bool"), + symmetric_hash=dict(type="bool"), + 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", ["port_channel"]], + ["state", "present", ["port_channel"]], + ], + ) + + port_channel = module.params.get("port_channel") + description = module.params.get("description") + min_links = module.params.get("min_links") + if min_links is not None and min_links not in range(1, 17): + module.fail_json(msg='The "min_links" must be a value between 1 and 16') + max_links = module.params.get("max_links") + if max_links is not None and max_links not in range(1, 17): + module.fail_json(msg='The "max_links" must be a value between 1 and 16') + mode = module.params.get("mode") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + # Build ctrl value for request + ctrl = [] + if module.params.get("fast_select") is True: + ctrl.append("fast-sel-hot-stdby") + if module.params.get("graceful_convergence") is True: + ctrl.append("graceful-conv") + if module.params.get("load_defer") is True: + ctrl.append("load-defer") + if module.params.get("suspend_individual") is True: + ctrl.append("susp-individual") + if module.params.get("symmetric_hash") is True: + ctrl.append("symmetric-hash") + if not ctrl: + ctrl = None + else: + ctrl = ",".join(ctrl) + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="lacpLagPol", + aci_rn="infra/lacplagp-{0}".format(port_channel), + module_object=port_channel, + target_filter={"name": port_channel}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="lacpLagPol", + class_config=dict( + name=port_channel, + ctrl=ctrl, + descr=description, + minLinks=min_links, + maxLinks=max_links, + mode=mode, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="lacpLagPol") + + 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_port_security.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_security.py new file mode 100644 index 000000000..b1df41916 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_port_security.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_port_security +short_description: Manage port security (l2:PortSecurityPol) +description: +- Manage port security on Cisco ACI fabrics. +options: + port_security: + description: + - The name of the port security. + type: str + aliases: [ name ] + description: + description: + - The description for the contract. + type: str + aliases: [ descr ] + max_end_points: + description: + - Maximum number of end points. + - Accepted values range between C(0) and C(12000). + - The APIC defaults to C(0) when unset during creation. + type: int + port_security_timeout: + description: + - The delay time in seconds before MAC learning is re-enabled + - Accepted values range between C(60) and C(3600) + - The APIC defaults to C(60) when unset during creation + type: int + 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(l2:PortSecurityPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a Port Security interface policy + cisco.aci.aci_interface_policy_port_security: + host: apic + username: admin + password: SomeSecretPassword + port_security: PS_1 + max_end_points: 1 + port_security_timeout: 60 + state: present + delegate_to: localhost + +- name: Delete a Port Security interface policy + cisco.aci.aci_interface_policy_port_security: + host: apic + username: admin + password: SomeSecretPassword + port_security: PS_1 + state: absent + delegate_to: localhost + +- name: Query all Port Security interface policies + cisco.aci.aci_interface_policy_port_security: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific Port Security interface policy + cisco.aci.aci_interface_policy_port_security: + host: apic + username: admin + password: SomeSecretPassword + port_security: PS_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, 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( + port_security=dict(type="str", aliases=["name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + max_end_points=dict(type="int"), + port_security_timeout=dict(type="int"), + 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", ["port_security"]], + ["state", "present", ["port_security"]], + ], + ) + + port_security = module.params.get("port_security") + description = module.params.get("description") + max_end_points = module.params.get("max_end_points") + port_security_timeout = module.params.get("port_security_timeout") + name_alias = module.params.get("name_alias") + if max_end_points is not None and max_end_points not in range(12001): + module.fail_json(msg="The max_end_points must be between 0 and 12000") + if port_security_timeout is not None and port_security_timeout not in range(60, 3601): + module.fail_json(msg="The port_security_timeout must be between 60 and 3600") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="l2PortSecurityPol", + aci_rn="infra/portsecurityP-{0}".format(port_security), + module_object=port_security, + target_filter={"name": port_security}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="l2PortSecurityPol", + class_config=dict( + name=port_security, + descr=description, + maximum=max_end_points, + nameAlias=name_alias, + timeout=port_security_timeout, + ), + ) + + aci.get_diff(aci_class="l2PortSecurityPol") + + 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_spanning_tree.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spanning_tree.py new file mode 100644 index 000000000..4a9b30f6e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_spanning_tree.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Eric Girard <@netgirard> +# 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_spanning_tree +short_description: Manage spanning tree interface policies (stp:IfPol) +description: +- Manage spanning tree interface policies on Cisco ACI fabrics. +options: + stp_policy: + description: + - The name of the STP policy. + type: str + aliases: [ name ] + description: + description: + - The description for the policy. + type: str + aliases: [ descr ] + bpdu_guard: + description: + - The BPDU-Guard state. + type: bool + default: false + bpdu_filter: + description: + - The BPDU-Filter state. + type: bool + default: false + 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(stp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Eric Girard (@netgirard) +""" + +EXAMPLES = r""" +- name: Add a spanning interface policy + cisco.aci.aci_interface_policy_spanning_tree: + host: '{{ inventory_hostname }}' + username: '{{ username }}' + password: '{{ password }}' + stp_policy: 'my_policy' + description: 'my_description' + bpdu_guard: true + bpdu_filter: false + state: present + delegate_to: localhost + +- name: Query a specific spanning interface policy + cisco.aci.aci_interface_policy_spanning_tree: + host: '{{ inventory_hostname }}' + username: '{{ username }}' + password: '{{ password }}' + stp_policy: 'my_policy' + state: query + delegate_to: localhost + +- name: Query all spanning interface policies + cisco.aci.aci_interface_policy_spanning_tree: + host: '{{ inventory_hostname }}' + username: '{{ username }}' + password: '{{ password }}' + state: query + delegate_to: localhost + +- name: Remove a specific spanning interface policy + cisco.aci.aci_interface_policy_spanning_tree: + host: '{{ inventory_hostname }}' + username: '{{ username }}' + password: '{{ password }}' + stp_policy: 'my_policy' + 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( + stp_policy=dict(type="str", aliases=["name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + bpdu_guard=dict(type="bool", default=False), + bpdu_filter=dict(type="bool", default=False), + 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", ["stp_policy"]], + ["state", "present", ["stp_policy"]], + ], + ) + + stp_policy = module.params.get("stp_policy") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + # Build ctrl value for request + ctrl = [] + if module.params.get("bpdu_filter") is True: + ctrl.append("bpdu-filter") + if module.params.get("bpdu_guard") is True: + ctrl.append("bpdu-guard") + + # Order of control string must match ACI return value for idempotency + ctrl = ",".join(sorted(ctrl)) if ctrl else None + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="stpIfPol", + aci_rn="infra/ifPol-{0}".format(stp_policy), + module_object=stp_policy, + target_filter={"name": stp_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="stpIfPol", + class_config=dict( + name=stp_policy, + ctrl=ctrl, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="stpIfPol") + + 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_selector_to_switch_policy_leaf_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_selector_to_switch_policy_leaf_profile.py new file mode 100644 index 000000000..2adfb1912 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_selector_to_switch_policy_leaf_profile.py @@ -0,0 +1,252 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.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_selector_to_switch_policy_leaf_profile +short_description: Bind interface selector profiles to switch policy leaf profiles (infra:RsAccPortP) +description: +- Bind interface selector profiles to switch policy leaf profiles on Cisco ACI fabrics. +options: + leaf_profile: + description: + - Name of the Leaf Profile to which we add a Selector. + type: str + aliases: [ leaf_profile_name ] + interface_selector: + description: + - Name of Interface Profile Selector to be added and associated with the Leaf Profile. + type: str + aliases: [ name, interface_selector_name, interface_profile_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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- This module requires an existing leaf profile, the module M(cisco.aci.aci_switch_policy_leaf_profile) can be used for this. +seealso: +- module: cisco.aci.aci_switch_policy_leaf_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:RsAccPortP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +""" + +EXAMPLES = r""" +- name: Associating an interface selector profile to a switch policy leaf profile + cisco.aci.aci_interface_selector_to_switch_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + interface_selector: interface_profile_name + state: present + delegate_to: localhost + +- name: Remove an interface selector profile associated with a switch policy leaf profile + cisco.aci.aci_interface_selector_to_switch_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + interface_selector: interface_profile_name + state: absent + delegate_to: localhost + +- name: Query an interface selector profile associated with a switch policy leaf profile + cisco.aci.aci_interface_selector_to_switch_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + interface_selector: interface_profile_name + 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( + leaf_profile=dict(type="str", aliases=["leaf_profile_name"]), # Not required for querying all objects + interface_selector=dict(type="str", aliases=["interface_profile_name", "interface_selector_name", "name"]), # 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", ["leaf_profile", "interface_selector"]], ["state", "present", ["leaf_profile", "interface_selector"]]], + ) + + leaf_profile = module.params.get("leaf_profile") + # WARNING: interface_selector accepts non existing interface_profile names and they appear on APIC gui with a state of "missing-target" + interface_selector = module.params.get("interface_selector") + state = module.params.get("state") + + # Defining the interface profile tDn for clarity + interface_selector_tDn = "uni/infra/accportprof-{0}".format(interface_selector) + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="infraNodeP", + aci_rn="infra/nprof-{0}".format(leaf_profile), + module_object=leaf_profile, + target_filter={"name": leaf_profile}, + ), + subclass_1=dict( + aci_class="infraRsAccPortP", + aci_rn="rsaccPortP-[{0}]".format(interface_selector_tDn), + module_object=interface_selector, + target_filter={"tDn": interface_selector_tDn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraRsAccPortP", + class_config=dict(tDn=interface_selector_tDn), + ) + + aci.get_diff(aci_class="infraRsAccPortP") + + 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_l2out.py b/ansible_collections/cisco/aci/plugins/modules/aci_l2out.py new file mode 100644 index 000000000..974c3c8e9 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l2out.py @@ -0,0 +1,306 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Sudhakar Shet Kudtarkar (@kudtarkar1) +# Copyright: (c) 2020, Shreyas Srish <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l2out +short_description: Manage Layer2 Out (L2Out) objects. +description: +- Manage Layer2 Out configuration on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + l2out: + description: + - The name of outer layer2. + type: str + aliases: [ 'name' ] + description: + description: + - Description for the L2Out. + type: str + bd: + description: + - Name of the Bridge domain which is associted with the L2Out. + type: str + domain: + description: + - Name of the external L2 Domain that is being associated with L2Out. + type: str + vlan: + description: + - The VLAN which is being associated with the L2Out. + type: int + 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) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fvTenant). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sudhakar Shet Kudtarkar (@kudtarkar1) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new L2Out + cisco.aci.aci_l2out: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l2out: l2out + description: via Ansible + bd: bd1 + domain: l2Dom + vlan: 3200 + state: present + delegate_to: localhost + +- name: Remove an L2Out + cisco.aci.aci_l2out: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l2out: l2out + state: absent + delegate_to: localhost + +- name: Query an L2Out + cisco.aci.aci_l2out: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l2out: l2out + state: query + delegate_to: localhost + register: query_result + +- name: Query all L2Outs in a specific tenant + cisco.aci.aci_l2out: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + 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 "/></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( + bd=dict(type="str"), + l2out=dict(type="str", aliases=["name"]), + domain=dict(type="str"), + vlan=dict(type="int"), + description=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str"), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["l2out", "tenant"]], + ["state", "present", ["bd", "l2out", "tenant", "domain", "vlan"]], + ], + ) + + bd = module.params.get("bd") + l2out = module.params.get("l2out") + description = module.params.get("description") + domain = module.params.get("domain") + vlan = module.params.get("vlan") + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + child_classes = ["l2extRsEBd", "l2extRsL2DomAtt", "l2extLNodeP"] + + 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="l2extOut", + aci_rn="l2out-{0}".format(l2out), + module_object=l2out, + target_filter={"name": l2out}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [ + dict(l2extRsL2DomAtt=dict(attributes=dict(tDn="uni/l2dom-{0}".format(domain)))), + dict(l2extRsEBd=dict(attributes=dict(tnFvBDName=bd, encap="vlan-{0}".format(vlan)))), + ] + + aci.payload( + aci_class="l2extOut", + class_config=dict(name=l2out, descr=description, dn="uni/tn-{0}/l2out-{1}".format(tenant, l2out), nameAlias=name_alias), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="l2extOut") + + 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_l2out_extepg.py b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_extepg.py new file mode 100644 index 000000000..3df19e9da --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_extepg.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Sudhakar Shet Kudtarkar (@kudtarkar1) +# Copyright: (c) 2020, Shreyas Srish <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + + +DOCUMENTATION = r""" +--- +module: aci_l2out_extepg +short_description: Manage External Network Instance (L2Out External EPG) objects (l2extInstP). +description: +- Manage External Network Instance (L2Out External EPG) objects (l2extInstP) on ACI fabrics. +options: + tenant: + description: + - Name of existing tenant. + type: str + l2out: + description: + - Name of the l2out. + type: str + extepg: + description: + - Name of the external end point group. + type: str + aliases: [ external_epg, extepg_name, name ] + description: + description: + - Description for the l2out. + type: str + preferred_group: + description: + - This depicts whether this External EPG is part of the Preferred Group and can communicate without contracts. + - This is convenient for migration scenarios, or when ACI is used for network automation but not for policy. + - The APIC defaults to C(false) when unset during creation. + type: bool + qos_class: + description: + - The bandwidth level for Quality of service. + type: str + choices: [ level1, level2, level3, level4, level5, level6, 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant) and C(l2out) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l2out) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fvtenant) and B(l2extOut). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sudhakar Shet Kudtarkar (@kudtarkar1) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new L2 external end point group + cisco.aci.aci_l2out_extepg: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l2out: l2out + extepg: NewExt + description: external epg + preferred_group: False + state: present + delegate_to: localhost + +- name: Remove an L2 external end point group + cisco.aci.aci_l2out_extepg: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l2out: l2out + extepg: NewExt + state: absent + delegate_to: localhost + +- name: Query the L2 external end point group + cisco.aci.aci_l2out_extepg: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l2out: l2out + extepg: NewExt + state: query + delegate_to: localhost + register: query_result + +- name: Query all L2 external end point groups in a tenant + cisco.aci.aci_l2out_extepg: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + 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 "/></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( + l2out=dict(type="str"), + description=dict(type="str"), + extepg=dict(type="str", aliases=["external_epg", "extepg_name", "name"]), + preferred_group=dict(type="bool"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str"), + qos_class=dict(type="str", choices=["level1", "level2", "level3", "level4", "level5", "level6", "Unspecified"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["l2out", "tenant", "extepg"]], + ["state", "present", ["l2out", "tenant", "extepg"]], + ], + ) + + aci = ACIModule(module) + + l2out = module.params.get("l2out") + description = module.params.get("description") + preferred_group = aci.boolean(module.params.get("preferred_group"), "include", "exclude") + state = module.params.get("state") + tenant = module.params.get("tenant") + extepg = module.params.get("extepg") + qos_class = module.params.get("qos_class") + + 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="l2extOut", + aci_rn="l2out-{0}".format(l2out), + module_object=l2out, + target_filter={"name": l2out}, + ), + subclass_2=dict( + aci_class="l2extInstP", + aci_rn="instP-{0}".format(extepg), + module_object=extepg, + target_filter={"name": extepg}, + ), + ) + + aci.get_existing() + + if state == "present": + config = dict(name=extepg, descr=description, dn="uni/tn-{0}/l2out-{1}/instP-{2}".format(tenant, l2out, extepg), prefGrMemb=preferred_group) + if qos_class: + config.update(prio=qos_class) + aci.payload( + class_config=config, + aci_class="l2extInstP", + ) + + aci.get_diff(aci_class="l2extInstP") + + 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_l2out_extepg_to_contract.py b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_extepg_to_contract.py new file mode 100644 index 000000000..205be3795 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_extepg_to_contract.py @@ -0,0 +1,349 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Sudhakar Shet Kudtarkar (@kudtarkar1) +# Copyright: (c) 2020, Shreyas Srish <ssrish@cisco.com> +# Copyright: (c) 2021, Oleksandr Kreshchenko (@alexkross) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l2out_extepg_to_contract +short_description: Bind Contracts to L2 External End Point Groups (EPGs) +description: +- Bind Contracts to L2 External End Point Groups (EPGs) on ACI fabrics. +options: + tenant: + description: + - Name of existing tenant. + type: str + l2out: + description: + - Name of the l2out. + type: str + aliases: ['l2out_name'] + extepg: + description: + - Name of the external end point group. + type: str + aliases: ['extepg_name', 'external_epg'] + contract: + description: + - Name of the contract. + type: str + contract_type: + description: + - The type of contract. + type: str + required: true + choices: ['consumer', 'provider'] + priority: + description: + - This has four levels of priority. + type: str + choices: ['level1', 'level2', 'level3', 'unspecified'] + provider_match: + description: + - This is configurable for provided contracts. + type: str + choices: ['all', 'at_least_one', 'at_most_one', 'none'] + 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 C(tenant), C(l2out) and C(extepg) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l2out) and M(cisco.aci.aci_l2out_extepg) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fvtenant), B(l2extInstP) and B(l2extOut). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sudhakar Shet Kudtarkar (@kudtarkar1) +- Shreyas Srish (@shrsr) +- Oleksandr Kreshchenko (@alexkross) +""" + +EXAMPLES = r""" +- name: Bind a contract to an L2 external EPG + cisco.aci.aci_l2out_extepg_to_contract: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l2out: l2out + extepg : testEpg + contract: contract1 + contract_type: provider + state: present + delegate_to: localhost + +- name: Remove existing contract from an L2 external EPG + cisco.aco.aci_l2out_extepg_to_contract: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l2out: l2out + extepg : testEpg + contract: contract1 + contract_type: provider + state: absent + delegate_to: localhost + +- name: Query a contract bound to an L2 external EPG + cisco.aci.aci_l2out_extepg_to_contract: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + l2out: ansible_l2out + extepg: ansible_extEpg + contract: ansible_contract + contract_type: provider + state: query + delegate_to: localhost + register: query_result + +- name: Query all contracts relationships + cisco.aci.aci_l2out_extepg_to_contract: + host: apic + username: admin + password: SomeSecretePassword + contract_type: provider + 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 "/></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_CLASS_MAPPING = dict( + consumer={ + "class": "fvRsCons", + "rn": "rscons-", + }, + provider={ + "class": "fvRsProv", + "rn": "rsprov-", + }, +) + +PROVIDER_MATCH_MAPPING = dict( + all="All", + at_least_one="AtleastOne", + at_most_one="tmostOne", + none="None", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + contract_type=dict(type="str", required=True, choices=["consumer", "provider"]), + l2out=dict(type="str", aliases=["l2out_name"]), + contract=dict(type="str"), + priority=dict(type="str", choices=["level1", "level2", "level3", "unspecified"]), + provider_match=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str"), + extepg=dict(type="str", aliases=["extepg_name", "external_epg"]), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["extepg", "contract", "l2out", "tenant"]], + ["state", "present", ["extepg", "contract", "l2out", "tenant"]], + ], + ) + + l2out = module.params.get("l2out") + contract = module.params.get("contract") + contract_type = module.params.get("contract_type") + extepg = module.params.get("extepg") + priority = module.params.get("priority") + provider_match = module.params.get("provider_match") + if provider_match is not None: + provider_match = PROVIDER_MATCH_MAPPING.get(provider_match) + state = module.params.get("state") + tenant = module.params.get("tenant") + + aci_class = ACI_CLASS_MAPPING.get(contract_type)["class"] + aci_rn = ACI_CLASS_MAPPING.get(contract_type)["rn"] + + if contract_type == "consumer" and provider_match is not None: + module.fail_json(msg="the 'provider_match' is only configurable for Provided Contracts") + + 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="l2extOut", + aci_rn="l2out-{0}".format(l2out), + module_object=l2out, + target_filter={"name": l2out}, + ), + subclass_2=dict( + aci_class="l2extInstP", + aci_rn="instP-{0}".format(extepg), + module_object=extepg, + target_filter={"name": extepg}, + ), + subclass_3=dict( + aci_class=aci_class, + aci_rn="{0}{1}".format(aci_rn, contract), + module_object=contract, + target_filter={"tnVzBrCPName": contract}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_class, + class_config=dict( + matchT=provider_match, + prio=priority, + tnVzBrCPName=contract, + ), + ) + + aci.get_diff(aci_class=aci_class) + + 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_l2out_logical_interface_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_logical_interface_path.py new file mode 100644 index 000000000..6be588bcd --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_logical_interface_path.py @@ -0,0 +1,372 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l2out_logical_interface_path +short_description: Manage Layer 2 Outside (L2Out) logical interface path (l2extRsPathL2OutAtt) +description: +- Manage interface path entry of L2 outside node (BD extension) on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l2out: + description: + - Name of an existing L2Out. + type: str + aliases: [ l2out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - Name of the interface profile. + type: str + aliases: [ name, interface_profile_name, logical_interface ] + interface_type: + description: + - The type of interface for the static EPG deployment. + type: str + choices: [ switch_port, port_channel, vpc ] + default: switch_port + pod_id: + description: + - The pod number part of the tDn. + - C(pod_id) is usually an integer below C(10). + type: int + aliases: [ pod, pod_number ] + leaves: + description: + - The switch ID(s) that the C(interface) belongs to. + - When C(interface_type) is C(switch_port) or C(port_channel), then C(leaves) is a string of the leaf ID. + - When C(interface_type) is C(vpc), then C(leaves) is a list with both leaf IDs. + - The C(leaves) value is usually something like '101' or '101-102' depending on C(connection_type). + type: list + elements: str + aliases: [ leafs, nodes, paths, switches ] + interface: + description: + - The C(interface) string value part of the tDn. + - Usually a policy group like C(test-IntPolGrp) or an interface of the following format C(1/7) depending on C(interface_type). + 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: +- module: aci_l2out +- module: aci_l2out_logical_node_profile +- module: aci_l2out_logical_interface_profile +- module: aci_l2out_extepg +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Oleksandr Kreshchenko (@alexkross) +""" + +EXAMPLES = r""" +- name: Add new path to interface profile + cisco.aci.aci_l2out_logical_interface_path: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + leaves: 101-102 + interface: L2o1 + state: present + delegate_to: localhost + +- name: Delete path to interface profile + cisco.aci.aci_l2out_logical_interface_path: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + leaves: 101-102 + interface: L2o1 + state: absent + delegate_to: localhost + +- name: Query a path to interface profile + cisco.aci.aci_l2out_logical_interface_path: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + leaves: 101-102 + interface: L2o1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all paths to interface profiles + cisco.aci.aci_l2out_logical_interface_path: + 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 + +INTERFACE_TYPE_MAPPING = dict( + switch_port="topology/pod-{pod_id}/paths-{leaves}/pathep-[eth{interface}]", + port_channel="topology/pod-{pod_id}/paths-{leaves}/pathep-[{interface}]", + vpc="topology/pod-{pod_id}/protpaths-{leaves}/pathep-[{interface}]", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( # See comments in aci_static_binding_to_epg module. + tenant=dict(type="str", aliases=["tenant_name"]), + l2out=dict(type="str", aliases=["l2out_name"]), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), + interface_profile=dict(type="str", aliases=["name", "interface_profile_name", "logical_interface"]), + interface_type=dict(type="str", default="switch_port", choices=["switch_port", "port_channel", "vpc"]), + pod_id=dict(type="int", aliases=["pod", "pod_number"]), + leaves=dict(type="list", elements="str", aliases=["leafs", "nodes", "paths", "switches"]), + interface=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", ["tenant", "l2out", "node_profile", "interface_profile", "pod_id", "leaves", "interface"]], + ["state", "present", ["tenant", "l2out", "node_profile", "interface_profile", "pod_id", "leaves", "interface"]], + ], + ) + + tenant = module.params.get("tenant") + l2out = module.params.get("l2out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + interface_type = module.params.get("interface_type") + pod_id = module.params.get("pod_id") + leaves = module.params.get("leaves") + if leaves is not None: # Process leaves, and support dash-delimited leaves + leaves = [] + for leaf in module.params.get("leaves"): # Users are likely to use integers for leaf IDs, which would raise an exception when using the join method + leaves.extend(str(leaf).split("-")) + if len(leaves) == 1: + if interface_type == "vpc": + module.fail_json(msg='A interface_type of "vpc" requires 2 leaves') + leaves = leaves[0] + elif len(leaves) == 2: + if interface_type != "vpc": + module.fail_json(msg='The interface_types "switch_port" and "port_channel" do not support using multiple leaves for a single binding') + leaves = "-".join(leaves) + else: + module.fail_json(msg='The "leaves" parameter must not have more than 2 entries') + interface = module.params.get("interface") + state = module.params.get("state") + + path = INTERFACE_TYPE_MAPPING[interface_type].format(pod_id=pod_id, leaves=leaves, interface=interface) + if not pod_id or not leaves or not interface: + path = None + + path_target_filter = {} + if any((pod_id, leaves, interface)): + path_target_filter = {"tDn": path} + + 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="l2extOut", + aci_rn="l2out-{0}".format(l2out), + module_object=l2out, + target_filter={"name": l2out}, + ), + subclass_2=dict( + aci_class="l2extLNodeP", + aci_rn="lnodep-{0}".format(node_profile), + module_object=node_profile, + target_filter={"name": node_profile}, + ), + subclass_3=dict( + aci_class="l2extLIfP", + aci_rn="lifp-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_4=dict( + aci_class="l2extRsPathL2OutAtt", + aci_rn="rspathL2OutAtt-[{0}]".format(path), + # rspathL2OutAtt-[topology/pod-1/protpaths-101-102/pathep-[L2o2_n7]] + module_object=path, + target_filter=path_target_filter, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="l2extRsPathL2OutAtt", + class_config=dict(tDn=path), + ) + + aci.get_diff(aci_class="l2extRsPathL2OutAtt") + + 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_l2out_logical_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_logical_interface_profile.py new file mode 100644 index 000000000..93e8fd195 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_logical_interface_profile.py @@ -0,0 +1,299 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l2out_logical_interface_profile +short_description: Manage Layer 2 Outside (L2Out) interface profiles (l2ext:LIfP) +description: +- Manage interface profiles of L2 outside (BD extension) on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l2out: + description: + - Name of an existing L2Out. + type: str + aliases: [ l2out_name ] + node_profile: + description: + - Name of the node profile. + type: str + default: default + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - Name of the interface profile. + type: str + aliases: [ name, interface_profile_name, logical_interface ] + 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: +- module: aci_l2out +- module: aci_l2out_logical_node_profile +- module: aci_l2out_logical_interface_path +- module: aci_l2out_extepg +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Oleksandr Kreshchenko (@alexkross) +""" + +EXAMPLES = r""" +- name: Add new interface profile + cisco.aci.aci_l2out_logical_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + interface_profile: my_interface_profile + state: present + delegate_to: localhost + +- name: Delete interface profile + cisco.aci.aci_l2out_logical_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + interface_profile: my_interface_profile + state: absent + delegate_to: localhost + +- name: Query an interface profile + cisco.aci.aci_l2out_logical_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + interface_profile: my_interface_profile + state: query + delegate_to: localhost + register: query_result + +- name: Query all interface profiles + cisco.aci.aci_l2out_logical_interface_profile: + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( # See comments in aci_static_binding_to_epg module. + tenant=dict(type="str", aliases=["tenant_name"]), + l2out=dict(type="str", aliases=["l2out_name"]), + node_profile=dict(type="str", default="default", aliases=["node_profile_name", "logical_node"]), + interface_profile=dict(type="str", aliases=["name", "interface_profile_name", "logical_interface"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "l2out", "node_profile", "interface_profile"]], + ["state", "present", ["tenant", "l2out", "node_profile", "interface_profile"]], + ], + ) + + tenant = module.params.get("tenant") + l2out = module.params.get("l2out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + state = module.params.get("state") + + 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="l2extOut", + aci_rn="l2out-{0}".format(l2out), + module_object=l2out, + target_filter={"name": l2out}, + ), + subclass_2=dict( + aci_class="l2extLNodeP", + aci_rn="lnodep-{0}".format(node_profile), + module_object=node_profile, + target_filter={"name": node_profile}, + ), + subclass_3=dict( + aci_class="l2extLIfP", + aci_rn="lifp-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + ) + + aci.get_existing() + + if state == "present": + # child_configs = [] + aci.payload( + aci_class="l2extLIfP", + class_config=dict(name=interface_profile), + # child_configs=child_configs + ) + + aci.get_diff(aci_class="l2extLIfP") + + 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_l2out_logical_node_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_logical_node_profile.py new file mode 100644 index 000000000..12a1aa6f5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_logical_node_profile.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l2out_logical_node_profile +short_description: Manage Layer 2 Outside (L2Out) logical node profiles (l2ext:LNodeP) +description: +- Manage node profiles of L2 outside (BD extension) on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l2out: + description: + - Name of an existing L2Out. + type: str + aliases: [ l2out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + 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: +- module: aci_l2out +- module: aci_l2out_logical_interface_profile +- module: aci_l2out_logical_interface_path +- module: aci_l2out_extepg +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Oleksandr Kreshchenko (@alexkross) +""" + +EXAMPLES = r""" +- name: Add new node profile + cisco.aci.aci_l2out_logical_node_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + state: present + delegate_to: localhost + +- name: Delete node profile + cisco.aci.aci_l2out_logical_node_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + state: absent + delegate_to: localhost + +- name: Query an node profile + cisco.aci.aci_l2out_logical_node_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l2out: my_l2out + node_profile: my_node_profile + state: query + delegate_to: localhost + register: query_result + +- name: Query all node profiles + cisco.aci.aci_l2out_logical_node_profile: + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( # See comments in aci_static_binding_to_epg module. + tenant=dict(type="str", aliases=["tenant_name"]), + l2out=dict(type="str", aliases=["l2out_name"]), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[["state", "absent", ["tenant", "l2out", "node_profile"]], ["state", "present", ["tenant", "l2out", "node_profile"]]], + ) + + tenant = module.params.get("tenant") + l2out = module.params.get("l2out") + node_profile = module.params.get("node_profile") + state = module.params.get("state") + + 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="l2extOut", + aci_rn="l2out-{0}".format(l2out), + module_object=l2out, + target_filter={"name": l2out}, + ), + subclass_2=dict( + aci_class="l2extLNodeP", + aci_rn="lnodep-{0}".format(node_profile), + module_object=node_profile, + target_filter={"name": node_profile}, + ), + ) + + aci.get_existing() + + if state == "present": + # child_configs = [] + aci.payload( + aci_class="l2extLNodeP", + class_config=dict(name=node_profile), + # child_configs=child_configs + ) + + aci.get_diff(aci_class="l2extLNodeP") + + 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 new file mode 100644 index 000000000..70cd11f8c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py @@ -0,0 +1,389 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 +short_description: Manage Layer 3 Outside (L3Out) objects (l3ext:Out) +description: +- Manage Layer 3 Outside (L3Out) on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of L3Out being created. + type: str + aliases: [ l3out_name, name ] + vrf: + description: + - Name of the VRF being associated with the L3Out. + type: str + aliases: [ vrf_name ] + domain: + description: + - Name of the external L3 domain being associated with the L3Out. + type: str + aliases: [ ext_routed_domain_name, routed_domain ] + dscp: + description: + - The target Differentiated Service (DSCP) value. + - The APIC defaults to C(unspecified) when unset during creation. + 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 ] + route_control: + description: + - Route Control enforcement direction. The only allowed values are export or import,export. + type: list + elements: str + choices: [ export, import ] + aliases: [ route_control_enforcement ] + l3protocol: + description: + - Routing protocol for the L3Out + type: list + elements: str + choices: [ bgp, eigrp, ospf, pim, static ] + asn: + description: + - The AS number for the L3Out. + - Only applicable when using 'eigrp' as the l3protocol + type: int + aliases: [ as_number ] + description: + description: + - Description for the L3Out. + 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 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. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_vrf +- 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: +- Rostyslav Davydenko (@rost-d) +""" + +EXAMPLES = r""" +- name: Add a new L3Out + cisco.aci.aci_l3out: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + name: prod_l3out + description: L3Out for Production tenant + domain: l3dom_prod + vrf: prod + l3protocol: ospf + state: present + delegate_to: localhost + +- name: Delete L3Out + cisco.aci.aci_l3out: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + name: prod_l3out + state: absent + delegate_to: localhost + +- name: Query L3Out information + cisco.aci.aci_l3out: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + name: 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": "", + "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", "name"]), # Not required for querying all objects + 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"]), + dscp=dict( + 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"], + ), + l3protocol=dict(type="list", elements="str", choices=["bgp", "eigrp", "ospf", "pim", "static"]), + asn=dict(type="int", aliases=["as_number"]), + 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", ["l3out", "tenant"]], + ["state", "present", ["l3out", "tenant", "domain", "vrf"]], + ], + ) + + aci = ACIModule(module) + + l3out = module.params.get("l3out") + domain = module.params.get("domain") + dscp = module.params.get("dscp") + description = module.params.get("description") + enforceRtctrl = module.params.get("route_control") + vrf = module.params.get("vrf") + l3protocol = module.params.get("l3protocol") + asn = module.params.get("asn") + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + + if l3protocol: + if "eigrp" in l3protocol and asn is None: + 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") + + enforce_ctrl = "" + if enforceRtctrl is not None: + if len(enforceRtctrl) == 1 and enforceRtctrl[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"] + + 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}, + ), + child_classes=child_classes, + ) + + 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, + targetDscp=dscp, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="l3extOut") + + 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_bgp_peer.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py new file mode 100644 index 000000000..83bb9ce14 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py @@ -0,0 +1,612 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = r""" +--- +module: aci_l3out_bgp_peer +short_description: Manage Layer 3 Outside (L3Out) BGP Peers (bgp:PeerP) +description: +- Manage L3Out BGP Peers on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - Name of the interface profile. + type: str + aliases: [ interface_profile_name, logical_interface ] + pod_id: + description: + - Pod to build the interface on. + type: str + node_id: + description: + - Node to build the interface on for Port-channels and single ports. + - Hyphen separated pair of nodes (e.g. "201-202") for vPCs. + type: str + path_ep: + description: + - Path to interface + - Interface Port Group name for Port-channels and vPCs + - Port number for single ports (e.g. "eth1/12") + type: str + peer_ip: + description: + - IP address of the BGP peer. + type: str + remote_asn: + description: + - Autonomous System Number of the BGP peer. + type: int + bgp_controls: + description: + - BGP Controls + type: list + elements: str + choices: [ send-com, send-ext-com, allow-self-as, as-override, dis-peer-as-check, nh-self ] + peer_controls: + description: + - Peer Controls + type: list + elements: str + choices: [ bfd, dis-conn-check ] + address_type_controls: + description: + - Address Type Controls + type: list + elements: str + choices: [ af-ucast, af-mcast ] + private_asn_controls: + description: + - Private AS Controls + type: list + elements: str + choices: [ remove-exclusive, remove-all, replace-as ] + ttl: + description: + - eBGP Multihop Time To Live + type: int + weight: + description: + - Weight for BGP routes from this neighbor + type: int + admin_state: + description: + - Admin state for the BGP session + type: str + choices: [ enabled, disabled ] + allow_self_as_count: + description: + - Number of allowed self AS. + - Only used if C(allow-self-as) is enabled under C(bgp_controls). + type: int + route_control_profiles: + description: + - List of dictionaries objects, which is used to bind the BGP Peer Connectivity Profile to Route Control Profile. + type: list + elements: dict + suboptions: + tenant: + description: + - Name of the tenant. + type: str + required: true + profile: + description: + - Name of the Route Control Profile. + type: str + required: true + l3out: + description: + - Name of the L3 Out. + type: str + direction: + description: + - Name of the Route Control Profile direction. + type: str + required: true + 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: +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(bgp:peerP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new BGP peer on a physical interface + cisco.aci.aci_l3out_bgp_peer: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + peer_ip: 192.168.10.2 + remote_asn: 65456 + bgp_controls: + - nh-self + - send-com + - send-ext-com + peer_controls: + - bfd + route_control_profiles: + - tenant: "ansible_tenant" + profile: "anstest_import" + direction: "import" + - tenant: "ansible_tenant" + profile: "anstest_export" + direction: "export" + l3out: "anstest_l3out" + state: present + delegate_to: localhost + +- name: Add a new BGP peer on a vPC + cisco.aci.aci_l3out_bgp_peer: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201-202 + path_ep: my_vpc_ipg + peer_ip: 192.168.20.2 + remote_asn: 65457 + ttl: 4 + weight: 50 + state: present + delegate_to: localhost + +- name: Shutdown a BGP peer + cisco.aci.aci_l3out_bgp_peer: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + peer_ip: 192.168.10.2 + admin_state: disabled + state: present + delegate_to: localhost + +- name: Delete a BGP peer + cisco.aci.aci_l3out_bgp_peer: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + peer_ip: 192.168.10.2 + state: absent + delegate_to: localhost + +- name: Add BGP Peer to the Node Profile level + cisco.aci.aci_l3out_bgp_peer: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + l3out: ansible_l3out + node_profile: ansible_node_profile + peer_ip: 192.168.50.3 + route_control_profiles: + - tenant: "ansible_tenant" + profile: "anstest_import" + direction: "import" + - tenant: "ansible_tenant" + profile: "anstest_export" + direction: "export" + l3out: "anstest_l3out" + state: present + +- name: Query a BGP peer + cisco.aci.aci_l3out_bgp_peer: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + peer_ip: 192.168.10.2 + state: query + delegate_to: localhost + register: query_result + +- name: Query all BGP peer + cisco.aci.aci_l3out_bgp_peer: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_all + +""" + +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, + route_control_profile_spec, +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + l3out=dict(type="str", aliases=["l3out_name"]), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), + interface_profile=dict(type="str", aliases=["interface_profile_name", "logical_interface"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + pod_id=dict(type="str"), + node_id=dict(type="str"), + path_ep=dict(type="str"), + peer_ip=dict(type="str"), + remote_asn=dict(type="int"), + bgp_controls=dict( + type="list", + elements="str", + choices=[ + "send-com", + "send-ext-com", + "allow-self-as", + "as-override", + "dis-peer-as-check", + "nh-self", + ], + ), + peer_controls=dict(type="list", elements="str", choices=["bfd", "dis-conn-check"]), + address_type_controls=dict(type="list", elements="str", choices=["af-ucast", "af-mcast"]), + private_asn_controls=dict( + type="list", + elements="str", + choices=["remove-exclusive", "remove-all", "replace-as"], + ), + ttl=dict(type="int"), + weight=dict(type="int"), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + allow_self_as_count=dict(type="int"), + route_control_profiles=dict( + type="list", + elements="dict", + options=route_control_profile_spec(), + ), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "l3out", "node_profile", "peer_ip"]], + ["state", "present", ["tenant", "l3out", "node_profile", "peer_ip"]], + ], + required_together=[["interface_profile", "pod_id", "node_id", "path_ep"]], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + state = module.params.get("state") + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + path_ep = module.params.get("path_ep") + peer_ip = module.params.get("peer_ip") + remote_asn = module.params.get("remote_asn") + bgp_controls = module.params.get("bgp_controls") + peer_controls = module.params.get("peer_controls") + address_type_controls = sorted(module.params.get("address_type_controls") or []) + private_asn_controls = module.params.get("private_asn_controls") + ttl = module.params.get("ttl") + weight = module.params.get("weight") + 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") + + aci = ACIModule(module) + if node_id: + if "-" in node_id: + path_type = "protpaths" + else: + path_type = "paths" + + path_dn = "topology/pod-{0}/{1}-{2}/pathep-[{3}]".format(pod_id, path_type, node_id, path_ep) + + child_configs = [] + child_classes = ["bgpRsPeerPfxPol", "bgpAsP", "bgpLocalAsnP"] + + if remote_asn: + child_configs.append( + dict( + bgpAsP=dict( + attributes=dict(asn=remote_asn), + ), + ) + ) + + if route_control_profiles: + child_classes.append("bgpRsPeerToProfile") + for profile in route_control_profiles: + if profile.get("l3out"): + route_control_profile_dn = "uni/tn-{0}/out-{1}/prof-{2}".format( + profile.get("tenant"), + profile.get("l3out"), + profile.get("profile"), + ) + else: + route_control_profile_dn = "uni/tn-{0}/prof-{1}".format(profile.get("tenant"), profile.get("profile")) + child_configs.append( + dict( + bgpRsPeerToProfile=dict( + attributes=dict( + direction=profile.get("direction"), + tDn=route_control_profile_dn, + ) + ) + ) + ) + + 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}, + ) + + if interface_profile is None: + subclass_3 = bgp_peer_profile_dict + else: + subclass_3 = dict( + aci_class="l3extLIfP", + aci_rn="lifp-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ) + subclass_4 = dict( + aci_class="l3extRsPathL3OutAtt", + aci_rn="rspathL3OutAtt-[{0}]".format(path_dn), + module_object=path_dn, + target_filter={"tDn": path_dn}, + ) + subclass_5 = bgp_peer_profile_dict + + 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=subclass_3, + subclass_4=subclass_4, + subclass_5=subclass_5, + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + ctrl, peerCtrl, addrTCtrl, privateASctrl = None, None, None, None + if bgp_controls: + ctrl = ",".join(bgp_controls) + if peer_controls: + peerCtrl = ",".join(peer_controls) + if address_type_controls: + addrTCtrl = ",".join(address_type_controls) + if private_asn_controls: + privateASctrl = ",".join(private_asn_controls) + aci.payload( + aci_class="bgpPeerP", + class_config=dict( + addr=peer_ip, + ctrl=ctrl, + peerCtrl=peerCtrl, + addrTCtrl=addrTCtrl, + privateASctrl=privateASctrl, + ttl=ttl, + weight=weight, + adminSt=admin_state, + allowedSelfAsCnt=allow_self_as_count, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="bgpPeerP") + + 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 new file mode 100644 index 000000000..514708bd7 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py @@ -0,0 +1,335 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_extepg +short_description: Manage External Network Instance Profile (ExtEpg) objects (l3extInstP:instP) +description: +- Manage External Network Instance Profile (ExtEpg) objects (l3extInstP:instP) +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + extepg: + description: + - Name of ExtEpg being created. + type: str + aliases: [ extepg_name, name ] + description: + description: + - Description for the ExtEpg. + type: str + aliases: [ descr ] + preferred_group: + description: + - Whether ot not the EPG is part of the Preferred Group and can communicate without contracts. + - This is very convenient for migration scenarios, or when ACI is used for network automation but not for policy. + - The APIC defaults to C(false) when unset during creation. + type: bool + dscp: + description: + - The target Differentiated Service (DSCP) value. + - The APIC defaults to C(unspecified) when unset during creation. + 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 ] + 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(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. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_vrf +- 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: +- Rostyslav Davydenko (@rost-d) +""" + +EXAMPLES = r""" +- name: Add a new ExtEpg + cisco.aci.aci_l3out_extepg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + name: prod_extepg + description: ExtEpg for Production L3Out + state: present + delegate_to: localhost + +- name: Delete ExtEpg + cisco.aci.aci_l3out_extepg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + name: prod_extepg + state: absent + delegate_to: localhost + +- name: Query ExtEpg information + cisco.aci.aci_l3out_extepg: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + name: prod_extepg + 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( + 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 + extepg=dict(type="str", aliases=["extepg_name", "name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + preferred_group=dict(type="bool"), + dscp=dict( + 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"], + ), + 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", "present", ["extepg", "l3out", "tenant"]], + ["state", "absent", ["extepg", "l3out", "tenant"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + extepg = module.params.get("extepg") + description = module.params.get("description") + preferred_group = aci.boolean(module.params.get("preferred_group"), "include", "exclude") + dscp = module.params.get("dscp") + state = module.params.get("state") + 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="l3extOut", + aci_rn="out-{0}".format(l3out), + module_object=l3out, + target_filter={"name": l3out}, + ), + subclass_2=dict( + aci_class="l3extInstP", + aci_rn="instP-{0}".format(extepg), + module_object=extepg, + target_filter={"name": extepg}, + ), + ) + + 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, + ), + ) + + aci.get_diff(aci_class="l3extInstP") + + 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_to_contract.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg_to_contract.py new file mode 100644 index 000000000..ba5df29c5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg_to_contract.py @@ -0,0 +1,347 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Sudhakar Shet Kudtarkar (@kudtarkar1) +# Copyright: (c) 2020, Shreyas Srish <ssrish@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_extepg_to_contract +short_description: Bind Contracts to External End Point Groups (EPGs) +description: +- Bind Contracts to External End Point Groups (EPGs) on ACI fabrics. +options: + tenant: + description: + - Name of existing tenant. + type: str + l3out: + description: + - Name of the l3out. + type: str + aliases: ['l3out_name'] + extepg: + description: + - Name of the external end point group. + type: str + aliases: ['extepg_name', 'external_epg'] + contract: + description: + - Name of the contract. + type: str + contract_type: + description: + - The type of contract. + type: str + required: true + choices: ['consumer', 'provider'] + priority: + description: + - This has four levels of priority. + type: str + choices: ['level1', 'level2', 'level3', 'unspecified'] + provider_match: + description: + - This is configurable for provided contracts. + type: str + choices: ['all', 'at_least_one', 'at_most_one', 'none'] + 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 C(tenant), C(l3out) and C(extepg) 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_extepg) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fvtenant), B(l3extInstP) and B(l3extOut). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Sudhakar Shet Kudtarkar (@kudtarkar1) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Bind a contract to an external EPG + cisco.aci.aci_l3out_extepg_to_contract: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l3out: l3out + extepg : testEpg + contract: contract1 + contract_type: provider + state: present + delegate_to: localhost + +- name: Remove existing contract from an external EPG + cisco.aci.aci_l3out_extepg_to_contract: + host: apic + username: admin + password: SomeSecretePassword + tenant: Auto-Demo + l3out: l3out + extepg : testEpg + contract: contract1 + contract_type: provider + state: absent + delegate_to: localhost + +- name: Query a contract bound to an external EPG + cisco.aci.aci_l3out_extepg_to_contract: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + l3out: ansible_l3out + extepg: ansible_extEpg + contract: ansible_contract + contract_type: provider + state: query + delegate_to: localhost + register: query_result + +- name: Query all contracts relationships + cisco.aci.aci_l3out_extepg_to_contract: + host: apic + username: admin + password: SomeSecretePassword + contract_type: provider + 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 "/></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_CLASS_MAPPING = dict( + consumer={ + "class": "fvRsCons", + "rn": "rscons-", + }, + provider={ + "class": "fvRsProv", + "rn": "rsprov-", + }, +) + +PROVIDER_MATCH_MAPPING = dict( + all="All", + at_least_one="AtleastOne", + at_most_one="tmostOne", + none="None", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + contract_type=dict(type="str", required=True, choices=["consumer", "provider"]), + l3out=dict(type="str", aliases=["l3out_name"]), + contract=dict(type="str"), + priority=dict(type="str", choices=["level1", "level2", "level3", "unspecified"]), + provider_match=dict(type="str", choices=["all", "at_least_one", "at_most_one", "none"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str"), + extepg=dict(type="str", aliases=["extepg_name", "external_epg"]), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["extepg", "contract", "l3out", "tenant"]], + ["state", "present", ["extepg", "contract", "l3out", "tenant"]], + ], + ) + + l3out = module.params.get("l3out") + contract = module.params.get("contract") + contract_type = module.params.get("contract_type") + extepg = module.params.get("extepg") + priority = module.params.get("priority") + provider_match = module.params.get("provider_match") + if provider_match is not None: + provider_match = PROVIDER_MATCH_MAPPING.get(provider_match) + state = module.params.get("state") + tenant = module.params.get("tenant") + + aci_class = ACI_CLASS_MAPPING.get(contract_type)["class"] + aci_rn = ACI_CLASS_MAPPING.get(contract_type)["rn"] + + if contract_type == "consumer" and provider_match is not None: + module.fail_json(msg="the 'provider_match' is only configurable for Provided Contracts") + + 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="l3extOut", + aci_rn="out-{0}".format(l3out), + module_object=l3out, + target_filter={"name": l3out}, + ), + subclass_2=dict( + aci_class="l3extInstP", + aci_rn="instP-{0}".format(extepg), + module_object=extepg, + target_filter={"name": extepg}, + ), + subclass_3=dict( + aci_class=aci_class, + aci_rn="{0}{1}".format(aci_rn, contract), + module_object=contract, + target_filter={"tnVzBrCPName": contract}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_class, + class_config=dict( + matchT=provider_match, + prio=priority, + tnVzBrCPName=contract, + ), + ) + + aci.get_diff(aci_class=aci_class) + + 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_extsubnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extsubnet.py new file mode 100644 index 000000000..6ecb3b27c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extsubnet.py @@ -0,0 +1,352 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_extsubnet +short_description: Manage External Subnet objects (l3extSubnet:extsubnet) +description: +- Manage External Subnet objects (l3extSubnet:extsubnet) +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + required: true + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + required: true + extepg: + description: + - Name of an existing ExtEpg. + type: str + aliases: [ extepg_name ] + required: true + network: + description: + - The network address for the Subnet. + type: str + aliases: [ address, ip ] + subnet_name: + description: + - Name of External Subnet being created. + type: str + aliases: [ name ] + description: + description: + - Description for the External Subnet. + type: str + aliases: [ descr ] + scope: + description: + - Determines the scope of the Subnet. + - The C(export-rtctrl) option controls which external networks are advertised out of the fabric using route-maps and IP prefix-lists. + - The C(import-rtctrl) option controls which external networks are advertised in to the fabric using route-maps and IP prefix-lists. + - The C(import-security) option classifies for the external EPG. + The rules and contracts defined in this external EPG apply to networks matching this subnet. + - The C(shared-rtctrl) option controls which external prefixes are advertised to other tenants for shared services. + - The C(shared-security) option configures the classifier for the subnets in the VRF where the routes are leaked. + - The APIC defaults to C(import-security) when unset during creation. + - The C(import-rtctrl) is only supported for BGP and OSPF. + type: list + elements: str + choices: [ export-rtctrl, import-rtctrl, import-security, shared-rtctrl, shared-security ] + aggregate: + description: + - Determines the Aggregate Routes for the Subnet. + - The C(export-rtctrl) option to export all transit routes of a VRF (0/0 subnets). + - The C(import-rtctrl) option to import all incoming routes of given L3 peers (0/0 subnets). + - The C(shared-rtctrl) option to share routes learned in one VRF which needs to be advertised to another VRF. + 0/0 can be used to share all subnet routes across multiple VRFs. + - The C(import-rtctrl) is only supported for BGP and OSPF. + - Aggregate import route control is only available if the L3Out has 'Import Route Control Enforcement' enabled. + Default this is disabled, M(cisco.aci.aci_l3out) with C(route_control) can be used to enable. + type: list + elements: str + choices: [ export-rtctrl, import-rtctrl, shared-rtctrl] + 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(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. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_vrf +- 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: +- Rostyslav Davydenko (@rost-d) +- Cindy Zhao (@cizhao) +""" + +EXAMPLES = r""" +- name: Add a new External Subnet + cisco.aci.aci_l3out_extsubnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + extepg: prod_extepg + description: External Subnet for Production ExtEpg + network: 192.0.2.0/24 + scope: export-rtctrl + aggregate: export-rtctrl + state: present + delegate_to: localhost + +- name: Delete External Subnet + cisco.aci.aci_l3out_extsubnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + extepg: prod_extepg + network: 192.0.2.0/24 + state: absent + delegate_to: localhost + +- name: Query ExtEpg Subnet information + cisco.aci.aci_l3out_extsubnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + extepg: prod_extepg + network: 192.0.2.0/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": "", + "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( + tenant=dict(type="str", required=True, aliases=["tenant_name"]), + l3out=dict(type="str", required=True, aliases=["l3out_name"]), + extepg=dict(type="str", required=True, aliases=["extepg_name", "name"]), + network=dict(type="str", aliases=["address", "ip"]), + description=dict(type="str", aliases=["descr"]), + subnet_name=dict(type="str", aliases=["name"]), + scope=dict(type="list", elements="str", choices=["import-security", "export-rtctrl", "import-rtctrl", "shared-rtctrl", "shared-security"]), + aggregate=dict(type="list", elements="str", choices=["export-rtctrl", "import-rtctrl", "shared-rtctrl"]), + 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", "present", ["network"]], + ["state", "absent", ["network"]], + ], + required_by={"aggregate": "scope"}, + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + extepg = module.params.get("extepg") + network = module.params.get("network") + description = module.params.get("description") + subnet_name = module.params.get("subnet_name") + scope = module.params.get("scope") + aggregate = module.params.get("aggregate") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + # Validation rule to only allow aggregate choice when there is a match in scope choice + if aggregate and not set(aggregate).issubset(scope): + aci.module.fail_json(msg="All aggregate values {0} need to be defined in scope {1}.".format(aggregate, scope)) + + class_config = dict(ip=network, descr=description, name=subnet_name, nameAlias=name_alias) + if scope: + class_config["scope"] = ",".join(sorted(scope)) + if aggregate: + class_config["aggregate"] = ",".join(sorted(aggregate)) + + 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="l3extInstP", + aci_rn="instP-{0}".format(extepg), + module_object=extepg, + target_filter={"name": extepg}, + ), + subclass_3=dict( + aci_class="l3extSubnet", + aci_rn="extsubnet-[{0}]".format(network), + module_object=network, + target_filter={"name": network}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="l3extSubnet", class_config=class_config) + + aci.get_diff(aci_class="l3extSubnet") + + 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_interface.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_interface.py new file mode 100644 index 000000000..f9a43b012 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_interface.py @@ -0,0 +1,392 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_interface +short_description: Manage Layer 3 Outside (L3Out) interfaces (l3ext:RsPathL3OutAtt) +description: +- Manage L3Out interfaces on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + required: true + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + required: true + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + required: true + interface_profile: + description: + - Name of the interface profile. + type: str + aliases: [ interface_profile_name, logical_interface ] + required: true + pod_id: + description: + - Pod to build the interface on. + type: str + node_id: + description: + - Node to build the interface on for Port-channels and single ports. + - Hyphen separated pair of nodes (e.g. "201-202") for vPCs. + type: str + path_ep: + description: + - Path to interface + - Interface Policy Group name for Port-channels and vPCs + - Port number for single ports (e.g. "eth1/12") + type: str + encap: + description: + - encapsulation on the interface (e.g. "vlan-500") + type: str + address: + description: + - IP address. + type: str + aliases: [ addr, ip_address] + mtu: + description: + - Interface MTU. + type: str + ipv6_dad: + description: + - IPv6 DAD feature. + type: str + choices: [ enabled, disabled] + interface_type: + description: + - Type of interface to build. + type: str + choices: [ l3-port, sub-interface, ext-svi ] + mode: + description: + - Interface mode, only used if instance_type is ext-svi + type: str + choices: [ regular, native, untagged ] + 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 + auto_state: + description: + - SVI auto state. + type: str + choices: [ enabled, disabled ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(l3ext:RsPathL3OutAtt) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Add a new routed interface + cisco.aci.aci_l3out_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + interface_type: l3-port + address: 192.168.10.1/27 + state: present + delegate_to: localhost + +- name: Add a new SVI vPC + cisco.aci.aci_l3out_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201-202 + path_ep: my_vpc_ipg + interface_type: ext-svi + encap: vlan-800 + mode: regular + state: present + delegate_to: localhost + +- name: Delete an interface + cisco.aci.aci_l3out_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + state: absent + delegate_to: localhost + +- name: Query an interface + cisco.aci.aci_l3out_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + 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( + tenant=dict(type="str", aliases=["tenant_name"], required=True), + l3out=dict(type="str", aliases=["l3out_name"], required=True), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"], required=True), + interface_profile=dict(type="str", aliases=["interface_profile_name", "logical_interface"], required=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + pod_id=dict(type="str"), + node_id=dict(type="str"), + path_ep=dict(type="str"), + address=dict(type="str", aliases=["addr", "ip_address"]), + mtu=dict(type="str"), + ipv6_dad=dict(type="str", choices=["enabled", "disabled"]), + interface_type=dict(type="str", choices=["l3-port", "sub-interface", "ext-svi"]), + mode=dict(type="str", choices=["regular", "native", "untagged"]), + encap=dict(type="str"), + auto_state=dict(type="str", choices=["enabled", "disabled"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[["state", "present", ["interface_type", "pod_id", "node_id", "path_ep"]], ["state", "absent", ["pod_id", "node_id", "path_ep"]]], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + state = module.params.get("state") + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + path_ep = module.params.get("path_ep") + address = module.params.get("address") + mtu = module.params.get("mtu") + ipv6_dad = module.params.get("ipv6_dad") + interface_type = module.params.get("interface_type") + mode = module.params.get("mode") + encap = module.params.get("encap") + auto_state = module.params.get("auto_state") + + aci = ACIModule(module) + if node_id and "-" in node_id: + path_type = "protpaths" + else: + path_type = "paths" + + path_dn = None + if pod_id and node_id and path_ep: + path_dn = "topology/pod-{0}/{1}-{2}/pathep-[{3}]".format(pod_id, path_type, node_id, path_ep) + + 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="l3extLIfP", + aci_rn="lifp-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_4=dict(aci_class="l3extRsPathL3OutAtt", aci_rn="rspathL3OutAtt-[{0}]".format(path_dn), module_object=path_dn, target_filter={"tDn": path_dn}), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="l3extRsPathL3OutAtt", + class_config=dict(tDn=path_dn, addr=address, ipv6Dad=ipv6_dad, mtu=mtu, ifInstT=interface_type, mode=mode, encap=encap, autostate=auto_state), + ) + + aci.get_diff(aci_class="l3extRsPathL3OutAtt") + + 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_interface_secondary_ip.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_interface_secondary_ip.py new file mode 100644 index 000000000..d8311dad5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_interface_secondary_ip.py @@ -0,0 +1,405 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = r""" +--- +module: aci_l3out_interface_secondary_ip +short_description: Manage Layer 3 Outside (L3Out) interface secondary IP addresses (l3ext:Ip). +description: +- Manage Layer 3 Outside (L3Out) interface secondary IP addresses (l3ext:Ip). +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - Name of the interface profile. + type: str + aliases: [ interface_profile_name, logical_interface ] + pod_id: + description: + - Pod to build the interface on. + type: str + node_id: + description: + - Node to build the interface on for Port-channels and single ports. + - Hyphen separated pair of nodes (e.g. "201-202") for vPCs. + type: str + path_ep: + description: + - Path to interface + - Interface Policy Group name for Port-channels and vPCs + - Port number for single ports (e.g. "eth1/12") + type: str + side: + description: + - Provides the side for vPC member interfaces. + type: str + choices: [ A, B ] + address: + description: + - Secondary IP address. + type: str + aliases: [ addr, ip_address] + ipv6_dad: + description: + - IPv6 DAD feature. + type: str + choices: [ enabled, disabled] + 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: +- This is a test +seealso: +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- module: aci_l3out_logical_interface_profile +- module: aci_l3out_logical_interface +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(l3ext:RsPathL3OutAtt) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Add a new secondary IP to a routed interface + cisco.aci.aci_l3out_interface_secondary_ip: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + address: 192.168.10.2/27 + state: present + delegate_to: localhost + +- name: Add a new secondary IP to a vPC member + cisco.aci.aci_l3out_interface_secondary_ip: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201-202 + path_ep: my_vpc_ipg + side: A + address: 192.168.10.2/27 + state: present + delegate_to: localhost + +- name: Delete a secondary IP + cisco.aci.aci_l3out_interface_secondary_ip: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + address: 192.168.10.2/27 + state: absent + delegate_to: localhost + +- name: Query a secondary IP + cisco.aci.aci_l3out_interface_secondary_ip: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + pod_id: 1 + node_id: 201 + path_ep: eth1/12 + address: 192.168.10.2/27 + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + l3out=dict(type="str", aliases=["l3out_name"]), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), + interface_profile=dict(type="str", aliases=["interface_profile_name", "logical_interface"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + pod_id=dict(type="str"), + node_id=dict(type="str"), + path_ep=dict(type="str"), + side=dict(type="str", choices=["A", "B"]), + address=dict(type="str", aliases=["addr", "ip_address"]), + ipv6_dad=dict(type="str", choices=["enabled", "disabled"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + [ + "state", + "absent", + [ + "tenant", + "l3out", + "node_profile", + "interface_profile", + "pod_id", + "node_id", + "path_ep", + ], + ], + [ + "state", + "present", + [ + "tenant", + "l3out", + "node_profile", + "interface_profile", + "pod_id", + "node_id", + "path_ep", + ], + ], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + path_ep = module.params.get("path_ep") + side = module.params.get("side") + address = module.params.get("address") + ipv6_dad = module.params.get("ipv6_dad") + state = module.params.get("state") + + aci = ACIModule(module) + + path_type = "paths" + rn_prefix = "" + + if node_id: + if "-" in node_id: + path_type = "protpaths" + rn_prefix = "mem-{0}/".format(side) + + path_dn = None + if pod_id and node_id and path_ep: + path_dn = "topology/pod-{0}/{1}-{2}/pathep-[{3}]".format(pod_id, path_type, node_id, path_ep) + + 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="l3extLIfP", + aci_rn="lifp-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_4=dict( + aci_class="l3extRsPathL3OutAtt", + aci_rn="rspathL3OutAtt-[{0}]".format(path_dn), + module_object=path_dn, + target_filter={"tDn": path_dn}, + ), + subclass_5=dict( + aci_class="l3extIp", + aci_rn="{0}addr-[{1}]".format(rn_prefix, address), + module_object=address, + target_filter={"addr": address}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="l3extIp", class_config=dict(addr=address, ipv6Dad=ipv6_dad)) + + aci.get_diff(aci_class="l3extIp") + 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_logical_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py new file mode 100644 index 000000000..a87cb664c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py @@ -0,0 +1,315 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_logical_interface_profile +short_description: Manage Layer 3 Outside (L3Out) logical interface profiles (l3ext:LIfP) +description: +- Manage L3Out interface profiles on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - Name of the interface profile. + type: str + aliases: [ name, interface_profile_name, logical_interface ] + nd_policy: + description: + - Name of the neighbor discovery interface policy. + type: str + egress_dpp_policy: + description: + - Name of the egress data plane policing policy. + type: str + ingress_dpp_policy: + description: + - Name of the ingress data plane policing policy. + 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 + +seealso: +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Add a new interface profile + cisco.aci.aci_l3out_logical_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + state: present + delegate_to: localhost + +- name: Delete an interface profile + cisco.aci.aci_l3out_logical_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + state: absent + delegate_to: localhost + +- name: Query an interface profile + cisco.aci.aci_l3out_logical_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + state: query + delegate_to: localhost + register: query_result + +- name: Query all interface profiles + cisco.aci.aci_l3out_logical_interface_profile: + 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 + + +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"]), + l3out=dict(type="str", aliases=["l3out_name"]), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), + interface_profile=dict(type="str", aliases=["name", "interface_profile_name", "logical_interface"]), + nd_policy=dict(type="str", default=""), + egress_dpp_policy=dict(type="str", default=""), + ingress_dpp_policy=dict(type="str", default=""), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "l3out", "node_profile", "interface_profile"]], + ["state", "present", ["tenant", "l3out", "node_profile", "interface_profile"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + 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") + state = module.params.get("state") + + 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="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="l3extLIfP", + aci_rn="lifp-[{0}]".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + child_classes=["l3extRsNdIfPol", "l3extRsIngressQosDppPol", "l3extRsEgressQosDppPol"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [ + dict(l3extRsNdIfPol=dict(attributes=dict(tnNdIfPolName=nd_policy))), + 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.get_diff(aci_class="l3extLIfP") + + 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_logical_interface_profile_ospf_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py new file mode 100644 index 000000000..92361230f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Jason Juenger (@jasonjuenger) <jasonjuenger@gmail.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_logical_interface_profile_ospf_policy +short_description: Manage Layer 3 Outside (L3Out) logical interface profile (l3ext:LIfP) OSPF policy (ospfIfP) +description: +- Manage L3Out interface profile OSPF policies on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - Name of an existing interface profile. + type: str + aliases: [ name, interface_profile_name, logical_interface ] + ospf_policy: + description: + - Name of an existing OSPF interface policy. + type: str + aliases: [ name, ospf_policy_name ] + ospf_auth_type: + description: + - OSPF authentication type. The value C(default) represents the "No Authentication" setting. + type: str + choices: [ default, simple, md5 ] + ospf_auth_key: + description: + - 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. + - 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: +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jason Juenger (@jasonjuenger) +""" + +EXAMPLES = r""" +- name: Add a new interface profile OSPF policy + cisco.aci.aci_l3out_logical_interface_profile_ospf_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + ospf_policy: my_ospf_interface_policy + state: present + delegate_to: localhost + +- name: Add a new interface profile OSPF policy with authentication + cisco.aci.aci_l3out_logical_interface_profile_ospf_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + ospf_policy: my_ospf_interface_policy + ospf_auth_type: simple + ospf_auth_key: my_auth_key + state: present + delegate_to: localhost + +- name: Delete an interface profile OSPF policy + cisco.aci.aci_l3out_logical_interface_profile_ospf_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + ospf_policy: my_ospf_interface_policy + state: absent + delegate_to: localhost + +- name: Query an interface profile OSPF policy + cisco.aci.aci_l3out_logical_interface_profile_ospf_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + ospf_policy: my_ospf_interface_policy + 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"]), + l3out=dict(type="str", aliases=["l3out_name"]), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), + interface_profile=dict(type="str", aliases=["interface_profile_name", "logical_interface"]), + ospf_policy=dict(type="str", aliases=["name", "ospf_policy_name"]), + ospf_auth_type=dict(type="str", choices=["default", "simple", "md5"]), + ospf_auth_key=dict(type="str", no_log=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "l3out", "node_profile", "interface_profile"]], + ["state", "present", ["tenant", "l3out", "node_profile", "interface_profile", "ospf_policy"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + ospf_policy = module.params.get("ospf_policy") + ospf_auth_type = module.params.get("ospf_auth_type") + ospf_auth_key = module.params.get("ospf_auth_key") + state = module.params.get("state") + + 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="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="l3extLIfP", + aci_rn="lifp-[{0}]".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_4=dict( + aci_class="ospfIfP", + aci_rn="ospfIfP", + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + child_classes=["ospfRsIfPol"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [dict(ospfRsIfPol=dict(attributes=dict(tnOspfIfPolName=ospf_policy)))] + + config = dict(authType=ospf_auth_type) + if ospf_auth_key is not None: + config.update(authKey=ospf_auth_key) + + aci.payload(aci_class="ospfIfP", class_config=config, child_configs=child_configs) + + aci.get_diff(aci_class="ospfIfP") + + 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_logical_interface_vpc_member.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_vpc_member.py new file mode 100644 index 000000000..4ddd32161 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_vpc_member.py @@ -0,0 +1,409 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Anvitha Jain(@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_logical_interface_vpc_member +short_description: Manage Member Node objects (l3ext:Member) +description: +- Manage Member Node objects (l3ext:Member) +options: + description: + description: + - The description for the logical interface VPC member. + type: str + aliases: [ descr ] + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - Name of the interface profile. + type: str + aliases: [ interface_profile_name, logical_interface ] + pod_id: + description: + - Pod to of the interface. + type: str + node_id: + description: + - Hyphen separated pair of nodes (e.g. "201-202") + type: str + path_ep: + description: + - vPC Interface Policy Group name + type: str + path_dn: + description: + - DN of existing path endpoint (fabricPathEp). + type: str + side: + description: + - Provides the side of member. + type: str + choices: [ A, B ] + address: + description: + - IP address. + type: str + aliases: [ addr, ip_address] + ipv6_dad: + description: + - IPv6 DAD feature. + type: str + choices: [ enabled, disabled] + 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 L3Out vPC inteface used must exist before using this module in your playbook. + The M(cisco.aci.aci_l3out_logical_interface_profile) module can be used for this. +seealso: +- module: cisco.aci.aci_l3out_logical_interface_profile +- 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: +- Anvitha Jain (@anvitha-jain) +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Create a VPC member based on the path_dn + cisco.aci.aci_l3out_logical_interface_vpc_member: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + l3out: l3out + node_profile: nodeName + interface_profile: interfaceName + path_dn: topology/pod-1/protpaths-101-102/pathep-[policy_group_name] + side: A + state: present + delegate_to: localhost + +- name: Create a VPC member based pod, node and path + cisco.aci.aci_l3out_logical_interface_vpc_member: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + l3out: l3out + node_profile: nodeName + interface_profile: interfaceName + pod_id: 1 + node_id: 101-102 + path_ep: policy_group_name + side: A + address: 192.168.1.252/24 + state: present + delegate_to: localhost + +- name: Delete a VPC member + cisco.aci.aci_l3out_logical_interface_vpc_member: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + l3out: l3out + node_profile: nodeName + interface_profile: interfaceName + path_dn: topology/pod-1/protpaths-101-102/pathep-[policy_group_name] + side: A + state: absent + delegate_to: localhost + +- name: Query all VPC members + cisco.aci.aci_l3out_logical_interface_vpc_member: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific VPC member under l3out + cisco.aci.aci_l3out_logical_interface_vpc_member: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + l3out: l3out + node_profile: nodeName + interface_profile: interfaceName + path_dn: topology/pod-1/protpaths-101-102/pathep-[policy_group_name] + side: A + 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( + 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 + interface_profile=dict(type="str", aliases=["interface_profile_name", "logical_interface"]), + path_dn=dict(type="str"), + pod_id=dict(type="str"), + node_id=dict(type="str"), + path_ep=dict(type="str"), + side=dict(type="str", choices=["A", "B"]), + address=dict(type="str", aliases=["addr", "ip_address"]), + ipv6_dad=dict(type="str", choices=["enabled", "disabled"]), + 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", "present", ["side", "interface_profile", "node_profile", "l3out", "tenant"]], + ["state", "absent", ["side", "interface_profile", "node_profile", "l3out", "tenant"]], + ], + mutually_exclusive=[ + ["path_dn", "pod_id"], + ["path_dn", "node_id"], + ["path_dn", "path_ep"], + ], + required_together=[ + ["pod_id", "node_id", "path_ep"], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + path_ep = module.params.get("path_ep") + path_dn = module.params.get("path_dn") + side = module.params.get("side") + address = module.params.get("address") + ipv6_dad = module.params.get("ipv6_dad") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + if not path_dn: + if pod_id and node_id and path_ep: + path_dn = "topology/pod-{0}/protpaths-{1}/pathep-[{2}]".format(pod_id, node_id, path_ep) + else: + path_dn = None + + 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="l3extLIfP", + aci_rn="lifp-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_4=dict( + aci_class="l3extRsPathL3OutAtt", + aci_rn="rspathL3OutAtt-[{0}]".format(path_dn), + module_object=path_dn, + target_filter={"name": path_dn}, + ), + subclass_5=dict( + aci_class="l3extMember", + aci_rn="mem-{0}".format(side), + module_object=side, + target_filter={"name": side}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="l3extMember", + class_config=dict( + name=side, + addr=address, + ipv6Dad=ipv6_dad, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="l3extMember") + + 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_logical_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py new file mode 100644 index 000000000..2bfc876ad --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py @@ -0,0 +1,316 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_logical_node +short_description: Manage Layer 3 Outside (L3Out) logical node profile nodes (l3ext:RsNodeL3OutAtt) +description: +- Bind nodes to node profiles on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + pod_id: + description: + - Existing podId. + type: int + node_id: + description: + - Existing nodeId. + type: int + router_id: + description: + - Router ID in dotted decimal notation. + type: str + router_id_as_loopback: + description: + - Configure the router ID as a loopback IP. + type: str + choices: [ 'yes', 'no' ] + default: 'yes' + 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: +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(vmm:DomP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Add a new node to a 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 + router_id: 111.111.111.111 + state: present + delegate_to: localhost + +- name: Delete a node from a 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 + state: absent + delegate_to: localhost + +- name: Query a node + 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 + state: query + delegate_to: localhost + register: query_result + +- name: Query all nodes + cisco.aci.aci_l3out_logical_node: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + l3out=dict(type="str", aliases=["l3out_name"]), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), + pod_id=dict(type="int"), + node_id=dict(type="int"), + router_id=dict(type="str"), + router_id_as_loopback=dict(type="str", default="yes", choices=["yes", "no"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "l3out", "node_profile", "pod_id", "node_id"]], + ["state", "present", ["tenant", "l3out", "node_profile", "pod_id", "node_id"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + pod_id = module.params.get("pod_id") + 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") + state = module.params.get("state") + + tdn = None + if pod_id is not None and node_id is not None: + tdn = "topology/pod-{0}/node-{1}".format(pod_id, node_id) + + 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="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="l3extRsNodeL3OutAtt", + aci_rn="rsnodeL3OutAtt-[{0}]".format(tdn), + module_object=tdn, + target_filter={"name": tdn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="l3extRsNodeL3OutAtt", class_config=dict(rtrId=router_id, rtrIdLoopBack=router_id_as_loopback, tDn=tdn)) + + aci.get_diff(aci_class="l3extRsNodeL3OutAtt") + + 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_logical_node_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py new file mode 100644 index 000000000..d3aa5e8f7 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py @@ -0,0 +1,335 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_logical_node_profile +short_description: Manage Layer 3 Outside (L3Out) logical node profiles (l3extLNodeP:lnodep) +description: +- Manage Layer 3 Outside (L3Out) logical node profiles on Cisco ACI fabrics. +options: + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, name, logical_node ] + description: + description: + - Description for the node profile. + type: str + aliases: [ descr ] + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + dscp: + description: + - The target Differentiated Service (DSCP) value. + - The APIC defaults to C(unspecified) when unset during creation. + 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_dscp ] + 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: +- module: aci_l3out +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(vmm:DomP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jason Juenger (@jasonjuenger) +""" + +EXAMPLES = r""" +- name: Add a new node profile + cisco.aci.aci_l3out_logical_node_profile: + host: apic + username: admin + password: SomeSecretPassword + node_profile: my_node_profile + description: node profile for my_l3out + l3out: my_l3out + tenant: my_tenant + dscp: CS0 + state: present + delegate_to: localhost + +- name: Delete a node profile + cisco.aci.aci_l3out_logical_node_profile: + host: apic + username: admin + password: SomeSecretPassword + node_profile: my_node_profile + l3out: my_l3out + tenant: my_tenant + state: absent + delegate_to: localhost + +- name: Query a node profile + cisco.aci.aci_l3out_logical_node_profile: + host: apic + username: admin + password: SomeSecretPassword + node_profile: my_node_profile + l3out: my_l3out + tenant: my_tenant + state: query + delegate_to: localhost + register: query_result + +- name: Query all node profile for L3out + cisco.aci.aci_l3out_logical_node_profile: + host: apic + username: admin + password: SomeSecretPassword + l3out: my_l3out + tenant: my_tenant + 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( + node_profile=dict(type="str", aliases=["name", "node_profile_name", "logical_node"]), + tenant=dict(type="str", aliases=["tenant_name"]), + l3out=dict(type="str", aliases=["l3out_name"]), + description=dict(type="str", aliases=["descr"]), + dscp=dict( + 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_dscp"], + ), + 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"]], + ], + ) + + node_profile = module.params.get("node_profile") + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + description = module.params.get("description") + dscp = module.params.get("dscp") + state = module.params.get("state") + 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="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}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="l3extLNodeP", + class_config=dict( + descr=description, + name=node_profile, + targetDscp=dscp, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="l3extLNodeP") + + 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_route_tag_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_route_tag_policy.py new file mode 100644 index 000000000..d975ddcf3 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_route_tag_policy.py @@ -0,0 +1,289 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_route_tag_policy +short_description: Manage route tag policies (l3ext:RouteTagPol) +description: +- Manage route tag policies on Cisco ACI fabrics. +options: + rtp: + description: + - The name of the route tag policy. + type: str + aliases: [ name, rtp_name ] + description: + description: + - The description for the route tag policy. + type: str + aliases: [ descr ] + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + tag: + description: + - The value of the route tag. + - Accepted values range between C(0) and C(4294967295). + - The APIC defaults to C(4294967295) when unset during creation. + type: int + 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(l3ext:RouteTagPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a l3out route tag policy + cisco.aci.aci_l3out_route_tag_policy: + host: apic + username: admin + password: SomeSecretPassword + tag: 1000 + rtp: my_route_tag_policy + tenant: production + state: present + delegate_to: localhost + +- name: Delete a l3out route tag policy + cisco.aci.aci_l3out_route_tag_policy: + host: apic + username: admin + password: SomeSecretPassword + rtp: my_route_tag_policy + tenant: production + state: absent + delegate_to: localhost + +- name: Query all l3out route tag policies + cisco.aci.aci_l3out_route_tag_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific l3out route tag policy + cisco.aci.aci_l3out_route_tag_policy: + host: apic + username: admin + password: SomeSecretPassword + rtp: my_route_tag_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 + + +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 + rtp=dict(type="str", aliases=["name", "rtp_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + tag=dict(type="int"), + 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", ["rtp", "tenant"]], + ["state", "present", ["rtp", "tenant"]], + ], + ) + + rtp = module.params.get("rtp") + description = module.params.get("description") + tag = module.params.get("tag") + 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="l3extRouteTagPol", + aci_rn="rttag-{0}".format(rtp), + module_object=rtp, + target_filter={"name": rtp}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="l3extRouteTagPol", + class_config=dict( + name=rtp, + descr=description, + tag=tag, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="l3extRouteTagPol") + + 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_static_routes.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py new file mode 100644 index 000000000..09a67b8ee --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py @@ -0,0 +1,366 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Anvitha Jain(@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_static_routes +short_description: Manage Static routes object (l3ext:ipRouteP) +description: +- Manage External Subnet objects (l3ext:ipRouteP) +options: + description: + description: + - The description for the static routes. + type: str + aliases: [ descr ] + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + logical_node: + description: + - Name of an existing logical node profile. + type: str + aliases: [ node_profile, node_profile_name ] + pod_id: + description: + - Existing podId. + type: int + node_id: + description: + - Existing nodeId. + type: int + prefix: + description: + - Configure IP and next hop IP for the routed outside network. + type: str + aliases: [ route ] + track_policy: + description: + - Relation definition for static route to TrackList. + type: str + preference: + description: + - Administrative preference value for the route. + type: int + bfd: + description: + - Determines if bfd is required for route control. + - The APIC defaults to C(None) when unset during creation. + type: str + choices: [ bfd, None ] + 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(tenant), C(l3out), C(logical_node), C(fabric_node) and C(prefix) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_l3out) modules 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(l3ext:Out). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Anvitha Jain(@anvitha-jain) +""" + +EXAMPLES = r""" +- name: Create static routes + cisco.aci.aci_l3out_static_routes: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + l3out: l3out + logical_node: nodeName + node_id: 101 + pod_id: 1 + prefix: 10.10.0.0/16 + delegate_to: localhost + +- name: Delete static routes + cisco.aci.aci_l3out_static_routes: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + l3out: l3out + logical_node: nodeName + node_id: 101 + pod_id: 1 + prefix: 10.10.0.0/16 + delegate_to: localhost + +- name: Query for a specific MO under l3out + cisco.aci.aci_l3out_static_routes: + host: apic + username: admin + password: SomeSecretPassword + tenant: tenantName + l3out: l3out + logical_node: nodeName + node_id: 101 + pod_id: 1 + prefix: 10.10.0.0/16 + delegate_to: localhost + +- name: Query for all static routes + cisco.aci.aci_l3out_static_routes: + host: apic + username: admin + password: SomeSecretPassword + 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 + + +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 + l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects + logical_node=dict(type="str", aliases=["node_profile", "node_profile_name"]), # Not required for querying all objects + pod_id=dict(type="int"), + node_id=dict(type="int"), + prefix=dict(type="str", aliases=["route"]), + track_policy=dict(type="str"), + preference=dict(type="int"), + bfd=dict(type="str", choices=["bfd", None]), + 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", "present", ["prefix", "node_id", "pod_id", "logical_node", "l3out", "tenant"]], + ["state", "absent", ["prefix", "node_id", "pod_id", "logical_node", "l3out", "tenant"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + logical_node = module.params.get("logical_node") + node_id = module.params.get("node_id") + pod_id = module.params.get("pod_id") + prefix = module.params.get("prefix") + track_policy = module.params.get("track_policy") + preference = module.params.get("preference") + bfd = module.params.get("bfd") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + fabric_node = "topology/pod-{0}/node-{1}".format(pod_id, node_id) + child_classes = ["ipNexthopP"] + if track_policy is not None: + child_classes.append("ipRsRouteTrack") + + 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(logical_node), + module_object=logical_node, + target_filter={"name": logical_node}, + ), + subclass_3=dict( + aci_class="l3extRsNodeL3OutAtt", + aci_rn="rsnodeL3OutAtt-[{0}]".format(fabric_node), + module_object=fabric_node, + target_filter={"name": fabric_node}, + ), + subclass_4=dict( + aci_class="ipRouteP", + aci_rn="rt-[{0}]".format(prefix), + module_object=prefix, + target_filter={"name": prefix}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + class_config = dict( + descr=description, + ip=prefix, + pref=preference, + nameAlias=name_alias, + ) + if bfd is not None: + class_config["rtCtrl"] = bfd + + if track_policy is not None: + 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.get_diff(aci_class="ipRouteP") + + 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_static_routes_nexthop.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes_nexthop.py new file mode 100644 index 000000000..36c3afa36 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes_nexthop.py @@ -0,0 +1,306 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_static_routes_nexthop +short_description: Manage nexthops for static routes (ip:NexthopP) +description: +- Manage nexthops for static routes. +options: + tenant: + description: + - Name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - Name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - Name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + pod_id: + description: + - Existing podId. + type: int + node_id: + description: + - Existing nodeId. + type: int + prefix: + description: + - The IP prefix + type: str + aliases: [ route ] + nexthop: + description: + - The nexthop for the prefix + 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: +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- module: aci_l3out_logical_node_profile_to_node +- module: aci_l3out_static_routes +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(vmm:DomP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Add a new nexthop to a prefix + cisco.aci.aci_l3out_static_routes_nexthop: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + pod_id: 1 + node_id: 111 + prefix: 10.84.90.0/24 + nexthop: 10.1.1.1 + state: present + delegate_to: localhost + +- name: Delete a nexthop from a prefix + cisco.aci.aci_l3out_static_routes_nexthop: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + pod_id: 1 + node_id: 111 + prefix: 10.84.90.0/24 + nexthop: 10.1.1.1 + state: absent + delegate_to: localhost + +- name: Query a nexthop + cisco.aci.aci_l3out_static_routes_nexthop: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + pod_id: 1 + node_id: 111 + prefix: 10.84.90.0/24 + nexthop: 10.1.1.1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all nexthops + cisco.aci.aci_l3out_static_routes_nexthop: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + l3out=dict(type="str", aliases=["l3out_name"]), + node_profile=dict(type="str", aliases=["node_profile_name", "logical_node"]), + pod_id=dict(type="int"), + node_id=dict(type="int"), + prefix=dict(type="str", aliases=["route"]), + nexthop=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", ["tenant", "l3out", "node_profile", "pod_id", "node_id", "prefix", "nexthop"]], + ["state", "present", ["tenant", "l3out", "node_profile", "pod_id", "node_id", "prefix", "nexthop"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + prefix = module.params.get("prefix") + nexthop = module.params.get("nexthop") + state = module.params.get("state") + + node_tdn = None + if pod_id is not None and node_id is not None: + node_tdn = "topology/pod-{0}/node-{1}".format(pod_id, node_id) + + 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="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="l3extRsNodeL3OutAtt", aci_rn="rsnodeL3OutAtt-[{0}]".format(node_tdn), module_object=node_tdn, target_filter={"name": node_tdn} + ), + subclass_4=dict(aci_class="ipRouteP", aci_rn="rt-[{0}]".format(prefix), module_object=prefix, target_filter={"name": prefix}), + subclass_5=dict(aci_class="ipNexthopP", aci_rn="nh-[{0}]".format(nexthop), module_object=nexthop, target_filter={"name": nexthop}), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="ipNexthopP", class_config=dict(nhAddr=nexthop)) + + aci.get_diff(aci_class="ipNexthopP") + + 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_maintenance_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py new file mode 100644 index 000000000..98b0df9f2 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 = """ +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 +description: + - This modules creates an ACI maintenance group +options: + group: + description: + - This is the name of the group + type: str + policy: + description: + - This is the name of the policy that was created using aci_maintenance_policy + 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 + +author: + - Steven Gerhart (@sgerhart) +""" + +EXAMPLES = r""" +- name: Create a maintenance group + cisco.aci.aci_maintenance_group: + host: apic + username: admin + password: SomeSecretPassword + group: my_maintenance_group + policy: my_maintenance_policy + state: present + delegate_to: localhost + +- name: Delete a maintenance group + cisco.aci.aci_maintenance_group: + host: apic + username: admin + password: SomeSecretPassword + group: my_maintenance_group + state: absent + delegate_to: localhost + +- name: Query all maintenance groups + cisco.aci.aci_maintenance_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific maintenance group + cisco.aci.aci_maintenance_group: + host: apic + username: admin + password: SomeSecretPassword + group: my_maintenance_group + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = """ +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_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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + 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 + 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", ["group"]], + ["state", "present", ["group"]], + ], + ) + + state = module.params.get("state") + group = module.params.get("group") + policy = module.params.get("policy") + name_alias = module.params.get("name_alias") + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="maintMaintGrp", + aci_rn="fabric/maintgrp-{0}".format(group), + target_filter={"name": group}, + module_object=group, + ), + child_classes=["maintRsMgrpp"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="maintMaintGrp", + class_config=dict( + name=group, + nameAlias=name_alias, + ), + child_configs=[ + dict( + maintRsMgrpp=dict( + attributes=dict( + tnMaintMaintPName=policy, + ), + ), + ), + ], + ) + + aci.get_diff(aci_class="maintMaintGrp") + + 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_maintenance_group_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py new file mode 100644 index 000000000..272143410 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py @@ -0,0 +1,261 @@ +#!/usr/bin/python + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_maintenance_group_node +short_description: Manage maintenance group nodes +description: +- Manage maintenance group nodes +options: + group: + description: + - The maintenance group name that you want to add the node to. + type: str + node: + description: + - The node to be added to the maintenance 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. + 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 + +author: +- Steven Gerhart (@sgerhart) +""" + +EXAMPLES = r""" +- name: Create a maintenance group node + cisco.aci.aci_maintenance_group_node: + host: apic + username: admin + password: SomeSecretPassword + group: my_maintenance_group + node: 1001 + state: present + delegate_to: localhost + +- name: Delete a maintenance group node + cisco.aci.aci_maintenance_group_node: + host: apic + username: admin + password: SomeSecretPassword + group: my_maintenance_group + node: 1001 + state: absent + delegate_to: localhost + +- name: Query all maintenance group nodes + cisco.aci.aci_maintenance_group_node: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a maintenance group node + cisco.aci.aci_maintenance_group_node: + host: apic + username: admin + password: SomeSecretPassword + group: my_maintenance_group + node: 1001 + 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( + group=dict(type="str"), # Not required for querying all objects + node=dict(type="str"), + 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", ["node", "group"]], + ["state", "present", ["node", "group"]], + ], + ) + + state = module.params.get("state") + group = module.params.get("group") + node = module.params.get("node") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="maintMaintGrp", + aci_rn="fabric/maintgrp-{0}".format(group), + target_filter={"name": group}, + module_object=group, + ), + subclass_1=dict( + aci_class="fabricNodeBlk", + aci_rn="nodeblk-blk{0}-{0}".format(node), + target_filter={"name": "blk{0}-{0}".format(node)}, + module_object=node, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricNodeBlk", + class_config=dict( + from_=node, + to_=node, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fabricNodeBlk") + + 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_maintenance_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py new file mode 100644 index 000000000..ddc62df09 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_policy.py @@ -0,0 +1,300 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_maintenance_policy +short_description: Manage firmware maintenance policies +description: +- Manage maintenance policies that defines behavior during an ACI upgrade. +options: + name: + description: + - The name for the maintenance policy. + type: str + aliases: [ maintenance_policy ] + runmode: + description: + - Whether the system pauses on error or just continues through it. + type: str + choices: [ pauseOnlyOnFailures, pauseNever ] + default: pauseOnlyOnFailures + graceful: + description: + - Whether the system will bring down the nodes gracefully during an upgrade, which reduces traffic lost. + - The APIC defaults to C(false) when unset during creation. + type: bool + scheduler: + description: + - The name of scheduler that is applied to the policy. + type: str + adminst: + description: + - Will trigger an immediate upgrade for nodes if adminst is set to triggered. + type: str + choices: [ triggered, untriggered ] + default: untriggered + ignoreCompat: + description: + - To check whether compatibility checks should be ignored + - The APIC defaults to C(false) when unset during creation. + type: bool + 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: +- 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. +author: +- Steven Gerhart (@sgerhart) +""" + +EXAMPLES = r""" +- name: Create a maintenance policy + cisco.aci.aci_maintenance_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_maintenance_policy + scheduler: simpleScheduler + state: present + delegate_to: localhost + +- name: Delete a maintenance policy + cisco.aci.aci_maintenance_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_maintenance_policy + state: absent + delegate_to: localhost + +- name: Query all maintenance policies + cisco.aci.aci_maintenance_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific maintenance policy + cisco.aci.aci_maintenance_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_maintenance_policy + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + 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"]), + graceful=dict(type="bool"), + scheduler=dict(type="str"), + ignoreCompat=dict(type="bool"), + adminst=dict(type="str", default="untriggered", choices=["triggered", "untriggered"]), + 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", ["name"]], + ["state", "present", ["name", "scheduler"]], + ], + ) + + aci = ACIModule(module) + + state = module.params.get("state") + name = module.params.get("name") + runmode = module.params.get("runmode") + scheduler = module.params.get("scheduler") + adminst = module.params.get("adminst") + graceful = aci.boolean(module.params.get("graceful")) + ignoreCompat = aci.boolean(module.params.get("ignoreCompat")) + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="maintMaintP", + aci_rn="fabric/maintpol-{0}".format(name), + target_filter={"name": name}, + module_object=name, + ), + child_classes=["maintRsPolScheduler"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="maintMaintP", + class_config=dict( + name=name, + runMode=runmode, + graceful=graceful, + adminSt=adminst, + ignoreCompat=ignoreCompat, + nameAlias=name_alias, + ), + child_configs=[ + dict( + maintRsPolScheduler=dict( + attributes=dict( + tnTrigSchedPName=scheduler, + ), + ), + ), + ], + ) + + aci.get_diff(aci_class="maintMaintP") + + 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_node_mgmt_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_node_mgmt_epg.py new file mode 100644 index 000000000..be8fe1838 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_node_mgmt_epg.py @@ -0,0 +1,317 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Shreyas Srish <ssrish@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 + +DOCUMENTATION = r""" +--- +module: aci_node_mgmt_epg +short_description: In band or Out of band management EPGs +description: +- Cisco ACI Fabric Node EPGs +options: + epg: + description: + - The name of the end point group + type: str + aliases: [ name ] + type: + description: + - type of management interface + type: str + choices: [ in_band, out_of_band ] + required: true + bd: + description: + - The in-band bridge domain which is used when type is in_band + type: str + encap: + description: + - The in-band access encapsulation which is used when type is in_band + 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 + +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add in band mgmt epg + cisco.aci.aci_node_mgmt_epg: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + type: in_band + encap: vlan-1 + bd: bd1 + state: present + delegate_to: localhost + +- name: Add out of band mgmt epg + cisco.aci.aci_node_mgmt_epg: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + type: out_of_band + state: present + delegate_to: localhost + +- name: Query in band mgmt epg + cisco.aci.aci_node_mgmt_epg: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + type: in_band + encap: vlan-1 + bd: bd1 + state: query + delegate_to: localhost + +- name: Query all in band mgmt epg + cisco.aci.aci_node_mgmt_epg: + host: "Host IP" + username: admin + password: SomeSecretePassword + type: in_band + state: query + delegate_to: localhost + +- name: Query all out of band mgmt epg + cisco.aci.aci_node_mgmt_epg: + host: "Host IP" + username: admin + password: SomeSecretePassword + type: out_of_band + state: query + delegate_to: localhost + +- name: Remove in band mgmt epg + cisco.aci.aci_node_mgmt_epg: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + type: in_band + 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 "/></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: class_map (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( + type=dict(type="str", choices=["in_band", "out_of_band"], required=True), + epg=dict(type="str", aliases=["name"]), + bd=dict(type="str"), + encap=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", ["epg"]], ["state", "present", ["epg"]]]) + + type = module.params.get("type") + epg = module.params.get("epg") + bd = module.params.get("bd") + encap = module.params.get("encap") + state = module.params.get("state") + + child_configs = [] + child_class = [] + if type == "in_band": + child_configs = [ + dict( + mgmtRsMgmtBD=dict( + attributes=dict( + tnFvBDName=bd, + ), + ), + ) + ] + + child_class = ["mgmtRsMgmtBD"] + + class_map = dict( + in_band=list( + [ + dict(aci_class="mgmtInB", aci_rn="inb-{0}"), + ] + ), + out_of_band=list( + [ + dict(aci_class="mgmtOoB", aci_rn="oob-{0}"), + ] + ), + ) + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-mgmt", + module_object="mgmt", + target_filter={"name": "mgmt"}, + ), + subclass_1=dict( + aci_class="mgmtMgmtP", + aci_rn="mgmtp-default", + module_object="default", + target_filter={"name": "default"}, + ), + subclass_2=dict( + aci_class=class_map.get(type)[0]["aci_class"], + aci_rn=class_map.get(type)[0]["aci_rn"].format(epg), + module_object=epg, + target_filter={"name": epg}, + ), + child_classes=child_class, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=class_map.get(type)[0]["aci_class"], + class_config=dict( + name=epg, + encap=encap, + ), + child_configs=child_configs, + ) + aci.get_diff(aci_class=class_map.get(type)[0]["aci_class"]) + + 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_ntp_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_ntp_policy.py new file mode 100644 index 000000000..7fc8abde3 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_ntp_policy.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = r""" +--- +module: aci_ntp_policy +short_description: Manage NTP policies. +description: +- Manage NTP policy (datetimePol) configuration on Cisco ACI fabrics. +options: + name: + description: + - Name of the NTP policy + type: str + aliases: [ ntp_policy ] + description: + description: + - Description of the NTP policy + type: str + admin_state: + description: + - Admin state of the policy + type: str + choices: [ disabled, enabled ] + server_state: + description: + - Allow switches to act as NTP servers + type: str + choices: [ disabled, enabled ] + auth_state: + description: + - Enable authentication + type: str + choices: [ disabled, enabled ] + master_mode: + description: + - Enable master mode. Only applicable if server_state is enabled + type: str + choices: [ disabled, enabled ] + 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(datetimePol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new NTP policy + cisco.aci.aci_ntp_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_ntp_policy + description: via Ansible + admin_state: enabled + state: present + delegate_to: localhost + +- name: Remove a NTP policy + cisco.aci.aci_ntp_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_ntp_policy + state: absent + delegate_to: localhost + +- name: Query a NTP policy + cisco.aci.aci_ntp_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_ntp_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query all NTP policies + cisco.aci.aci_ntp_policy: + 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 "/></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=["ntp_policy"]), + description=dict(type="str"), + admin_state=dict(type="str", choices=["disabled", "enabled"]), + server_state=dict(type="str", choices=["disabled", "enabled"]), + auth_state=dict(type="str", choices=["disabled", "enabled"]), + master_mode=dict(type="str", choices=["disabled", "enabled"]), + 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"]], + ], + ) + + name = module.params.get("name") + description = module.params.get("description") + admin_state = module.params.get("admin_state") + server_state = module.params.get("server_state") + auth_state = module.params.get("auth_state") + master_mode = module.params.get("master_mode") + state = module.params.get("state") + child_classes = ["datetimeNtpProv"] + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="datetimePol", + aci_rn="fabric/time-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="datetimePol", + class_config=dict( + name=name, + descr=description, + adminSt=admin_state, + serverState=server_state, + authSt=auth_state, + masterMode=master_mode, + ), + ) + + aci.get_diff(aci_class="datetimePol") + + 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_ntp_server.py b/ansible_collections/cisco/aci/plugins/modules/aci_ntp_server.py new file mode 100644 index 000000000..3e40b652a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_ntp_server.py @@ -0,0 +1,324 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "community", +} + +DOCUMENTATION = r""" +--- +module: aci_ntp_server +short_description: Manage NTP servers. +description: +- Manage NTP server (datetimeNtpProv) configuration on Cisco ACI fabrics. +options: + ntp_policy: + description: + - Name of an existing NTP policy + type: str + required: true + aliases: [ policy_name ] + ntp_server: + description: + - Name of the NTP server + type: str + aliases: [ server_name ] + description: + description: + - Description of the NTP server + type: str + min_poll: + description: + - Minimum polling interval + type: int + max_poll: + description: + - Maximum polling interval + type: int + preferred: + description: + - Is this the preferred NTP server + type: bool + epg_type: + description: + - Type of management EPG to use to reach the NTP server, inb or oob + type: str + choices: [ inb, oob ] + epg_name: + description: + - Name of the management EPG to reach the NTP server + 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 used C(ntp_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_ntp_policy) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(datetimeNtpProv). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new NTP server + cisco.aci.aci_ntp_server: + host: apic + username: admin + password: SomeSecretPassword + ntp_policy: my_ntp_policy + ntp_server: 10.20.30.40 + min_poll: 3 + max_poll: 8 + preferred: true + state: present + delegate_to: localhost + +- name: Remove a NTP server + cisco.aci.aci_ntp_server: + host: apic + username: admin + password: SomeSecretPassword + ntp_policy: my_ntp_policy + ntp_server: 10.20.30.40 + state: absent + delegate_to: localhost + +- name: Query a NTP server + cisco.aci.aci_ntp_server: + host: apic + username: admin + password: SomeSecretPassword + ntp_policy: my_ntp_policy + ntp_server: 10.20.30.40 + state: query + delegate_to: localhost + register: query_result + +- name: Query all NTP servers within a policy + cisco.aci.aci_ntp_server: + host: apic + username: admin + password: SomeSecretPassword + ntp_policy: my_ntp_policy + 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 "/></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( + ntp_policy=dict(type="str", aliases=["policy_name"], required=True), + ntp_server=dict(type="str", aliases=["server_name"]), + description=dict(type="str"), + min_poll=dict(type="int"), + max_poll=dict(type="int"), + preferred=dict(type="bool"), + epg_type=dict(type="str", choices=["inb", "oob"]), + epg_name=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", ["ntp_server"]], + ["state", "present", ["ntp_server"]], + ], + required_together=[ + ["epg_type", "epg_name"], + ], + ) + aci = ACIModule(module) + + ntp_policy = module.params.get("ntp_policy") + ntp_server = module.params.get("ntp_server") + description = module.params.get("description") + min_poll = module.params.get("min_poll") + max_poll = module.params.get("max_poll") + preferred = aci.boolean(module.params.get("preferred")) + epg_type = module.params.get("epg_type") + epg_name = module.params.get("epg_name") + state = module.params.get("state") + child_classes = ["datetimeRsNtpProvToEpg"] + + aci.construct_url( + root_class=dict( + aci_class="datetimePol", + aci_rn="fabric/time-{0}".format(ntp_policy), + module_object=ntp_policy, + target_filter={"name": ntp_policy}, + ), + subclass_1=dict( + aci_class="datetimeNtpProv", + aci_rn="ntpprov-{0}".format(ntp_server), + module_object=ntp_server, + target_filter={"name": ntp_server}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if epg_type is not None: + tdn = "uni/tn-mgmt/mgmtp-default/{0}-{1}".format(epg_type, epg_name) + child_configs.append(dict(datetimeRsNtpProvToEpg=dict(attributes=dict(tDn=tdn)))) + aci.payload( + aci_class="datetimeNtpProv", + class_config=dict( + name=ntp_server, + descr=description, + maxPoll=max_poll, + minPoll=min_poll, + preferred=preferred, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="datetimeNtpProv") + + 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 new file mode 100644 index 000000000..67428e4f9 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_rest.py @@ -0,0 +1,451 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com> +# Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@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_rest +short_description: Direct access to the Cisco APIC REST API +description: +- Enables the management of the Cisco ACI fabric through direct access to the Cisco APIC REST API. +- Thanks to the idempotent nature of the APIC, this module is idempotent and reports changes. +requirements: +- lxml (when using XML payload) +- xmljson >= 0.1.8 (when using XML payload) +- python 2.7+ (when using xmljson) +options: + method: + description: + - The HTTP method of the request. + - Using C(delete) is typically used for deleting objects. + - Using C(get) is typically used for querying objects. + - Using C(post) is typically used for modifying objects. + type: str + choices: [ delete, get, post ] + default: get + aliases: [ action ] + path: + description: + - URI being used to execute API calls. + - Must end in C(.xml) or C(.json). + type: str + required: true + aliases: [ uri ] + content: + description: + - When used instead of C(src), sets the payload of the API request directly. + - This may be convenient to template simple requests. + - For anything complex use the C(template) lookup plugin (see examples) + or the C(template) module with parameter C(src). + type: raw + src: + description: + - Name of the absolute path of the filename that includes the body + of the HTTP request being sent to the ACI fabric. + - If you require a templated payload, use the C(content) parameter + together with the C(template) lookup plugin, or use C(template). + type: path + aliases: [ config_file ] +extends_documentation_fragment: +- cisco.aci.aci + +notes: +- Certain payloads are known not to be idempotent, so be careful when constructing payloads, + e.g. using C(status="created") will cause idempotency issues, use C(status="modified") instead. + More information in :ref:`the ACI documentation <aci_guide_known_issues>`. +- Certain payloads (and used paths) are known to report no changes happened when changes did happen. + This is a known APIC problem and has been reported to the vendor. A workaround for this issue exists. + More information in :ref:`the ACI documentation <aci_guide_known_issues>`. +- XML payloads require the C(lxml) and C(xmljson) python libraries. For JSON payloads nothing special is needed. +- If you do not have any attributes, it may be necessary to add the "attributes" key with an empty dictionnary "{}" for value + as the APIC does expect the entry to precede any children. +seealso: +- module: cisco.aci.aci_tenant +- name: Cisco APIC REST API Configuration Guide + description: More information about the APIC REST API. + link: http://www.cisco.com/c/en/us/td/docs/switches/datacenter/aci/apic/sw/2-x/rest_cfg/2_1_x/b_Cisco_APIC_REST_API_Configuration_Guide.html +author: +- Dag Wieers (@dagwieers) +- Cindy Zhao (@cizhao) +""" + +EXAMPLES = r""" +- name: Add a tenant using certificate authentication + cisco.aci.aci_rest: + host: apic + username: admin + private_key: pki/admin.key + method: post + path: /api/mo/uni.xml + src: /home/cisco/ansible/aci/configs/aci_config.xml + delegate_to: localhost + +- name: Add a tenant from a templated payload file from templates/ + cisco.aci.aci_rest: + host: apic + username: admin + private_key: pki/admin.key + method: post + path: /api/mo/uni.xml + content: "{{ lookup('template', 'aci/tenant.xml.j2') }}" + delegate_to: localhost + +- name: Add a tenant using inline YAML + cisco.aci.aci_rest: + host: apic + username: admin + private_key: pki/admin.key + validate_certs: false + path: /api/mo/uni.json + method: post + content: + fvTenant: + attributes: + name: Sales + descr: Sales department + delegate_to: localhost + +- name: Add a tenant using a JSON string + cisco.aci.aci_rest: + host: apic + username: admin + private_key: pki/admin.key + validate_certs: false + path: /api/mo/uni.json + method: post + content: + { + "fvTenant": { + "attributes": { + "name": "Sales", + "descr": "Sales department" + } + } + } + delegate_to: localhost + +- name: Add a tenant using an XML string + cisco.aci.aci_rest: + host: apic + username: admin + private_key: pki/{{ aci_username }}.key + validate_certs: false + path: /api/mo/uni.xml + method: post + content: '<fvTenant name="Sales" descr="Sales departement"/>' + delegate_to: localhost + +- name: Get tenants using password authentication + cisco.aci.aci_rest: + host: apic + username: admin + password: SomeSecretPassword + method: get + path: /api/node/class/fvTenant.json + delegate_to: localhost + register: query_result + +- name: Configure contracts + cisco.aci.aci_rest: + host: apic + username: admin + private_key: pki/admin.key + method: post + path: /api/mo/uni.xml + src: /home/cisco/ansible/aci/configs/contract_config.xml + delegate_to: localhost + +- name: Register leaves and spines + cisco.aci.aci_rest: + host: apic + username: admin + private_key: pki/admin.key + validate_certs: false + method: post + path: /api/mo/uni/controller/nodeidentpol.xml + content: + <fabricNodeIdentPol> + <fabricNodeIdentP name="{{ item.name }}" nodeId="{{ item.nodeid }}" status="{{ item.status }}" serial="{{ item.serial }}"/> + </fabricNodeIdentPol> + with_items: + - '{{ apic_leavesspines }}' + delegate_to: localhost + +- name: Wait for all controllers to become ready + cisco.aci.aci_rest: + host: apic + username: admin + private_key: pki/admin.key + validate_certs: false + path: /api/node/class/topSystem.json?query-target-filter=eq(topSystem.role,"controller") + register: apics + until: "'totalCount' in apics and apics.totalCount|int >= groups['apic']|count" + retries: 120 + delay: 30 + delegate_to: localhost + run_once: true +""" + +RETURN = r""" +error_code: + description: The REST ACI return code, useful for troubleshooting on failure + returned: always + type: int + sample: 122 +error_text: + description: The REST ACI descriptive text, useful for troubleshooting on failure + returned: always + type: str + sample: unknown managed object class foo +imdata: + description: Converted output returned by the APIC REST (register this for post-processing) + returned: always + type: str + sample: [{"error": {"attributes": {"code": "122", "text": "unknown managed object class foo"}}}] +payload: + description: The (templated) payload send to the APIC REST API (xml or json) + returned: always + type: str + sample: '<foo bar="boo"/>' +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>' +response: + description: HTTP response string + returned: always + type: str + sample: 'HTTP Error 400: Bad Request' +status: + description: HTTP status code + returned: always + type: int + sample: 400 +totalCount: + description: Number of items in the imdata array + returned: always + type: str + sample: '0' +url: + description: URL used for APIC REST call + returned: success + type: str + sample: https://1.2.3.4/api/mo/uni/tn-[Dag].json?rsp-subtree=modified +""" + +import json +import os + +try: + from ansible.module_utils.six.moves.urllib.parse import parse_qsl, urlencode, urlparse, urlunparse + + HAS_URLPARSE = True +except Exception: + HAS_URLPARSE = False + +# Optional, only used for XML payload +try: + from lxml import etree # noqa + + HAS_LXML_ETREE = True +except ImportError: + HAS_LXML_ETREE = False + +# Optional, only used for XML payload +try: + from xmljson import cobra # noqa + + HAS_XMLJSON_COBRA = True +except ImportError: + HAS_XMLJSON_COBRA = False + +# Optional, only used for YAML validation +try: + import yaml + + HAS_YAML = True +except Exception: + HAS_YAML = False + +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 + + +def update_qsl(url, params): + """Add or update a URL query string""" + + if HAS_URLPARSE: + url_parts = list(urlparse(url)) + query = dict(parse_qsl(url_parts[4])) + query.update(params) + url_parts[4] = urlencode(query) + return urlunparse(url_parts) + elif "?" in url: + return url + "&" + "&".join(["%s=%s" % (k, v) for k, v in params.items()]) + else: + return url + "?" + "&".join(["%s=%s" % (k, v) for k, v in params.items()]) + + +class ACIRESTModule(ACIModule): + def changed(self, d): + """Check ACI response for changes""" + + if isinstance(d, dict): + for k, v in d.items(): + if k == "status" and v in ("created", "modified", "deleted"): + return True + elif self.changed(v) is True: + return True + elif isinstance(d, list): + for i in d: + if self.changed(i) is True: + return True + + return False + + def response_type(self, rawoutput, rest_type="xml"): + """Handle APIC response output""" + + if rest_type == "json": + self.response_json(rawoutput) + else: + self.response_xml(rawoutput) + + # Use APICs built-in idempotency + if HAS_URLPARSE: + self.result["changed"] = self.changed(self.imdata) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update( + path=dict(type="str", required=True, aliases=["uri"]), + method=dict(type="str", default="get", choices=["delete", "get", "post"], aliases=["action"]), + src=dict(type="path", aliases=["config_file"]), + content=dict(type="raw"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + mutually_exclusive=[["content", "src"]], + ) + + content = module.params.get("content") + path = module.params.get("path") + src = module.params.get("src") + + # Report missing file + file_exists = False + if src: + if os.path.isfile(src): + file_exists = True + else: + module.fail_json(msg="Cannot find/access src '%s'" % src) + + # Find request type + if path.find(".xml") != -1: + rest_type = "xml" + if not HAS_LXML_ETREE: + module.fail_json(msg="The lxml python library is missing, or lacks etree support.") + if not HAS_XMLJSON_COBRA: + module.fail_json(msg="The xmljson python library is missing, or lacks cobra support.") + elif path.find(".json") != -1: + rest_type = "json" + else: + module.fail_json(msg="Failed to find REST API payload type (neither .xml nor .json).") + + aci = ACIRESTModule(module) + aci.result["status"] = -1 # Ensure we always return a status + + # We include the payload as it may be templated + payload = content + if file_exists: + with open(src, "r") as config_object: + # TODO: Would be nice to template this, requires action-plugin + payload = config_object.read() + + # Validate payload + if rest_type == "json": + if content and isinstance(content, dict): + # Validate inline YAML/JSON + payload = json.dumps(payload) + elif payload and isinstance(payload, str) and HAS_YAML: + try: + # 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) + elif rest_type == "xml" and HAS_LXML_ETREE: + if content and isinstance(content, dict) and HAS_XMLJSON_COBRA: + # Validate inline YAML/JSON + payload = etree.tostring(cobra.etree(payload)[0], encoding="unicode") + elif payload and isinstance(payload, str): + try: + # 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) + + # 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" + 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() + + # 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") + + # Report failure + if info.get("status") != 200: + try: + # APIC error + 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 + + 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)) + + # Report success + aci.exit_json(**aci.result) + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client.py new file mode 100644 index 000000000..a7c01dc8e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_snmp_client +short_description: Manage SNMP clients (snmp:ClientP). +description: +- Manage SNMP clients +options: + address: + description: + - IP subnet to accept SNMP requests from + type: str + client_group: + description: + - Name of an existing SNMP client group + type: str + aliases: [ client_group_name, client_group_profile ] + client_name: + description: + - Name of the SNMP client + type: str + policy: + description: + - Name of an existing SNMP policy + type: str + aliases: [ snmp_policy, snmp_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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(snmp:ClientP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create an SNMP client + cisco.aci.aci_snmp_client: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + client_group: my_snmp_client_group + address: 10.20.30.0/24 + client_name: my_client_name + state: present + delegate_to: localhost + +- name: Remove an SNMP client + cisco.aci.aci_snmp_client_group: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + client_group: my_snmp_client_group + address: 10.20.30.0/24 + state: absent + delegate_to: localhost + +- name: Query an SNMP client + cisco.aci.aci_snmp_client: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + client_group: my_snmp_client_group + address: 10.20.30.0/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all SNMP clients + cisco.aci.aci_snmp_client: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + address=dict(type="str"), + client_group=dict(type="str", aliases=["client_group_name", "client_group_profile"]), + policy=dict(type="str", aliases=["snmp_policy", "snmp_policy_name"]), + client_name=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", ["policy", "client_group", "address"]], + ["state", "present", ["policy", "client_group", "address"]], + ], + ) + + aci = ACIModule(module) + + client_group = module.params.get("client_group") + policy = module.params.get("policy") + client_name = module.params.get("client_name") + address = module.params.get("address") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="snmpPol", + aci_rn="fabric/snmppol-{0}".format(policy), + module_object=policy, + target_filter={"name": policy}, + ), + subclass_1=dict( + aci_class="snmpClientGrpP", + aci_rn="clgrp-{0}".format(client_group), + module_object=client_group, + target_filter={"name": client_group}, + ), + subclass_2=dict( + aci_class="snmpClientP", + aci_rn="client-[{0}]".format(address), + module_object=address, + target_filter={"addr": address}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="snmpClientP", + class_config=dict(addr=address, name=client_name), + ) + + aci.get_diff(aci_class="snmpClientP") + + 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_client_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client_group.py new file mode 100644 index 000000000..7f7b12504 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client_group.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_snmp_client_group +short_description: Manage SNMP client groups (snmp:ClientGrpP). +description: +- Manage SNMP client groups +options: + client_group: + description: + - Name of the SNMP client group + type: str + aliases: [ client_group_name, client_group_profile ] + description: + description: + - Description of the SNMP policy + type: str + mgmt_epg: + description: + - Associated management EPG + type: str + aliases: [ management_epg_name, management_epg ] + policy: + description: + - Name of an existing SNMP policy + type: str + aliases: [ snmp_policy, snmp_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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(snmp:ClientGrpP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create an SNMP client group + cisco.aci.aci_snmp_client_group: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + client_group: my_snmp_client_group + mgmt_epg: oob-default + state: present + delegate_to: localhost + +- name: Remove an SNMP client group + cisco.aci.aci_snmp_client_group: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + client_group: my_snmp_client_group + state: absent + delegate_to: localhost + +- name: Query an SNMP client group + cisco.aci.aci_snmp_client_group: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + client_group: my_snmp_client_group + state: query + delegate_to: localhost + register: query_result + +- name: Query all SNMP client group + cisco.aci.aci_snmp_community_policy: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + client_group=dict(type="str", aliases=["client_group_name", "client_group_profile"]), + mgmt_epg=dict(type="str", aliases=["management_epg_name", "management_epg"]), + policy=dict(type="str", aliases=["snmp_policy", "snmp_policy_name"]), + description=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", ["policy", "client_group"]], + ["state", "present", ["policy", "client_group"]], + ], + ) + + aci = ACIModule(module) + + client_group = module.params.get("client_group") + policy = module.params.get("policy") + mgmt_epg = module.params.get("mgmt_epg") + description = module.params.get("description") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="snmpPol", + aci_rn="fabric/snmppol-{0}".format(policy), + module_object=policy, + target_filter={"name": policy}, + ), + subclass_1=dict( + aci_class="snmpClientGrpP", + aci_rn="clgrp-{0}".format(client_group), + module_object=client_group, + target_filter={"name": client_group}, + ), + child_classes=["snmpRsEpg"], + ) + + aci.get_existing() + + if state == "present": + if mgmt_epg: + tdn = "uni/tn-mgmt/mgmtp-default/{0}".format(mgmt_epg) + else: + tdn = None + aci.payload( + aci_class="snmpClientGrpP", + class_config=dict(name=client_group, descr=description), + child_configs=[ + dict( + snmpRsEpg=dict( + attributes=dict(tDn=tdn), + ), + ), + ], + ) + + aci.get_diff(aci_class="snmpClientGrpP") + + 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_community_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_community_policy.py new file mode 100644 index 000000000..446962165 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_community_policy.py @@ -0,0 +1,263 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_snmp_community_policy +short_description: Manage SNMP community policies (snmp:CommunityP). +description: +- Manage SNMP community policies +options: + community: + description: + - Name of the SNMP community policy + type: str + description: + description: + - Description of the SNMP policy + type: str + policy: + description: + - Name of an existing SNMP policy + type: str + aliases: [ snmp_policy, snmp_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 +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(snmp:CommunityP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create an SNMP community policy + cisco.aci.aci_snmp_community_policy: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + community: my_snmp_community + state: present + delegate_to: localhost + +- name: Remove an SNMP community policy + cisco.aci.aci_snmp_community_policy: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + community: my_snmp_community + state: absent + delegate_to: localhost + +- name: Query an SNMP community policy + cisco.aci.aci_snmp_community_policy: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + community: my_snmp_community + state: query + delegate_to: localhost + register: query_result + +- name: Query all SNMP community policies + cisco.aci.aci_snmp_community_policy: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + community=dict(type="str"), + policy=dict(type="str", aliases=["snmp_policy", "snmp_policy_name"]), + description=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", ["policy", "community"]], + ["state", "present", ["policy", "community"]], + ], + ) + + aci = ACIModule(module) + + community = module.params.get("community") + policy = module.params.get("policy") + description = module.params.get("description") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="snmpPol", + aci_rn="fabric/snmppol-{0}".format(policy), + module_object=policy, + target_filter={"name": policy}, + ), + subclass_1=dict( + aci_class="snmpCommunityP", + aci_rn="community-{0}".format(community), + module_object=community, + target_filter={"name": community}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="snmpCommunityP", + class_config=dict(name=community, descr=description), + ) + + aci.get_diff(aci_class="snmpCommunityP") + + 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_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_policy.py new file mode 100644 index 000000000..c3956db2f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_policy.py @@ -0,0 +1,272 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_snmp_policy +short_description: Manage Syslog groups (snmp:Pol). +description: +- Manage syslog policies +options: + admin_state: + description: + - Administrative State of the policy + type: str + choices: [ enabled, disabled ] + contact: + description: + - SNMP contact + type: str + description: + description: + - Description of the SNMP policy + type: str + location: + description: + - SNMP location + type: str + name: + description: + - Name of the SNMP policy + type: str + aliases: [ snmp_policy, snmp_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 +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 classes B(snmp:Pol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create an SNMP policy and Set Admin State to Enable + cisco.aci.aci_snmp_policy: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + admin_state: enabled + name: my_snmp_policy + state: present + delegate_to: localhost + +- name: Remove an SNMP policy + cisco.aci.aci_snmp_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_snmp_policy + state: absent + delegate_to: localhost + +- name: Query an SNMP policy + cisco.aci.aci_snmp_policy: + host: apic + username: admin + password: SomeSecretPassword + name: my_snmp_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query all SNMP policies + cisco.aci.aci_snmp_policy: + 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_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 + + +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=["snmp_policy", "snmp_policy_name"]), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + contact=dict(type="str"), + description=dict(type="str"), + location=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") + admin_state = module.params.get("admin_state") + contact = module.params.get("contact") + description = module.params.get("description") + location = module.params.get("location") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="snmpPol", + aci_rn="fabric/snmppol-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["snmpCommunityP", "snmpClientGrpP"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="snmpPol", + class_config=dict(name=name, adminSt=admin_state, contact=contact, descr=description, loc=location), + ) + + aci.get_diff(aci_class="snmpPol") + + 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 new file mode 100644 index 000000000..49a9f5ae5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py @@ -0,0 +1,301 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_snmp_user +short_description: Manage SNMP v3 Users (snmp:UserP). +description: +- Manage SNMP v3 Users +- Note that all properties within the snmpUserP class are Create-only. To modify any property of an existing user, you must delete and re-create it. +options: + auth_type: + description: + - SNMP authentication method + type: str + choices: [ hmac-md5-96, hmac-sha1-96] + auth_key: + description: + - SNMP authentication key + type: str + name: + description: + - Name of the SNMP user policy + type: str + aliases: [ snmp_user_policy ] + policy: + description: + - Name of an existing SNMP policy + type: str + aliases: [ snmp_policy, snmp_policy_name ] + privacy_type: + description: + - SNMP privacy type + type: str + choices: [ aes-128, des, none ] + privacy_key: + description: + - SNMP privacy key + 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(snmp:UserP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create an SNMP user + cisco.aci.aci_snmp_user: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + name: my_snmp_user + auth_type: hmac-sha1-96 + auth_key: "{{ hmac_key }}" + state: present + delegate_to: localhost + +- name: Create an SNMP user with both authentication and privacy + cisco.aci.aci_snmp_user: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + name: my_snmp_user + auth_type: hmac-sha1-96 + auth_key: "{{ hmac_key }}" + privacy_type: aes-128 + privacy_key: "{{ aes_key }}" + state: present + delegate_to: localhost + +- name: Remove an SNMP user + cisco.aci.aci_snmp_user: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + name: my_snmp_user + state: absent + delegate_to: localhost + +- name: Query an SNMP user + cisco.aci.aci_snmp_user: + host: apic + username: admin + password: SomeSecretPassword + policy: my_snmp_policy + name: my_snmp_user + state: query + delegate_to: localhost + register: query_result + +- name: Query all SNMP users + cisco.aci.aci_snmp_user: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + 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"]), + 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), + 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", "name"]], + ["state", "present", ["policy", "name"]], + ], + ) + + aci = ACIModule(module) + + policy = module.params.get("policy") + name = module.params.get("name") + auth_type = module.params.get("auth_type") + auth_key = module.params.get("auth_key") + privacy_type = module.params.get("privacy_type") + privacy_key = module.params.get("privacy_key") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="snmpPol", + aci_rn="fabric/snmppol-{0}".format(policy), + module_object=policy, + target_filter={"name": policy}, + ), + subclass_1=dict( + aci_class="snmpUserP", + aci_rn="user-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + 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), + ) + + aci.get_diff(aci_class="snmpUserP") + + 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_static_binding_to_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py new file mode 100644 index 000000000..b5428449f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_static_binding_to_epg.py @@ -0,0 +1,481 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.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_static_binding_to_epg +short_description: Bind static paths to EPGs (fv:RsPathAtt) +description: +- Bind static paths to EPGs on Cisco ACI fabrics. +options: + tenant: + description: + - Name of an existing 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: + - The name of the end point group. + type: str + aliases: [ epg_name ] + description: + description: + - Description for the static path to EPG binding. + type: str + aliases: [ descr ] + encap_id: + description: + - The encapsulation ID associating the C(epg) with the interface path. + - This acts as the secondary C(encap_id) when using micro-segmentation. + - Accepted values are any valid encap ID for specified encap, currently ranges between C(1) and C(4096). + type: int + aliases: [ vlan, vlan_id ] + primary_encap_id: + 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. + - C(unknown) is the default value and using C(unknown) disables the Micro-Segmentation. + type: str + aliases: [ primary_vlan, primary_vlan_id ] + deploy_immediacy: + description: + - The Deployment Immediacy of Static EPG on PC, VPC or Interface. + - The APIC defaults to C(lazy) when unset during creation. + type: str + choices: [ immediate, lazy ] + interface_mode: + description: + - Determines how layer 2 tags will be read from and added to frames. + - Values C(802.1p) and C(native) are identical. + - Values C(access) and C(untagged) are identical. + - Values C(regular), C(tagged) and C(trunk) are identical. + - The APIC defaults to C(trunk) when unset during creation. + type: str + choices: [ 802.1p, access, native, regular, tagged, trunk, untagged ] + aliases: [ interface_mode_name, mode ] + interface_type: + description: + - The type of interface for the static EPG deployment. + type: str + choices: [ fex, port_channel, switch_port, vpc, fex_port_channel, fex_vpc ] + default: switch_port + pod_id: + description: + - The pod number part of the tDn. + - C(pod_id) is usually an integer below C(10). + type: int + aliases: [ pod, pod_number ] + leafs: + description: + - The switch ID(s) that the C(interface) belongs to. + - When C(interface_type) is C(switch_port), C(port_channel), or C(fex), then C(leafs) is a string of the leaf ID. + - When C(interface_type) is C(vpc), then C(leafs) is a list with both leaf IDs. + - The C(leafs) value is usually something like '101' or '101-102' depending on C(connection_type). + type: list + elements: str + aliases: [ leaves, nodes, paths, switches ] + interface: + description: + - The C(interface) string value part of the tDn. + - Usually a policy group like C(test-IntPolGrp) or an interface of the following format C(1/7) depending on C(interface_type). + type: str + extpaths: + description: + - The C(extpaths) integer value part of the tDn. + - C(extpaths) is only used if C(interface_type) is C(fex), C(fex_vpc) or C(fex_port_channel). + - When C(interface_type) is C(fex_vpc), then C(extpaths) is a list with both fex IDs. + - Usually something like C(1011). + type: list + elements: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant), C(ap), C(epg) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_ap), M(cisco.aci.aci_epg) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_epg +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:RsPathAtt). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Deploy Static Path binding for given EPG + cisco.aci.aci_static_binding_to_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: accessport-code-cert + ap: accessport_code_app + epg: accessport_epg1 + encap_id: 222 + deploy_immediacy: lazy + interface_mode: untagged + interface_type: switch_port + pod_id: 1 + leafs: 101 + interface: '1/7' + state: present + delegate_to: localhost + +- name: Remove Static Path binding for given EPG + cisco.aci.aci_static_binding_to_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: accessport-code-cert + ap: accessport_code_app + epg: accessport_epg1 + interface_type: switch_port + pod: 1 + leafs: 101 + interface: '1/7' + state: absent + delegate_to: localhost + +- name: Get specific Static Path binding for given EPG + cisco.aci.aci_static_binding_to_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: accessport-code-cert + ap: accessport_code_app + epg: accessport_epg1 + interface_type: switch_port + pod: 1 + leafs: 101 + interface: '1/7' + 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 + +INTERFACE_MODE_MAPPING = { + "802.1p": "native", + "access": "untagged", + "native": "native", + "regular": "regular", + "tagged": "regular", + "trunk": "regular", + "untagged": "untagged", +} + +INTERFACE_TYPE_MAPPING = dict( + fex="topology/pod-{pod_id}/paths-{leafs}/extpaths-{extpaths}/pathep-[eth{interface}]", + fex_port_channel="topology/pod-{pod_id}/paths-{leafs}/extpaths-{extpaths}/pathep-[{interface}]", + fex_vpc="topology/pod-{pod_id}/protpaths-{leafs}/extprotpaths-{extpaths}/pathep-[{interface}]", + port_channel="topology/pod-{pod_id}/paths-{leafs}/pathep-[{interface}]", + switch_port="topology/pod-{pod_id}/paths-{leafs}/pathep-[eth{interface}]", + vpc="topology/pod-{pod_id}/protpaths-{leafs}/pathep-[{interface}]", +) + +# TODO: change 'deploy_immediacy' to 'resolution_immediacy' (as seen in aci_epg_to_domain)? + + +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 + ap=dict(type="str", aliases=["app_profile", "app_profile_name"]), # Not required for querying all objects + epg=dict(type="str", aliases=["epg_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + encap_id=dict(type="int", aliases=["vlan", "vlan_id"]), + primary_encap_id=dict(type="str", aliases=["primary_vlan", "primary_vlan_id"]), + deploy_immediacy=dict(type="str", choices=["immediate", "lazy"]), + interface_mode=dict( + type="str", choices=["802.1p", "access", "native", "regular", "tagged", "trunk", "untagged"], aliases=["interface_mode_name", "mode"] + ), + interface_type=dict(type="str", default="switch_port", choices=["fex", "port_channel", "switch_port", "vpc", "fex_port_channel", "fex_vpc"]), + pod_id=dict(type="int", aliases=["pod", "pod_number"]), # Not required for querying all objects + leafs=dict(type="list", elements="str", aliases=["leaves", "nodes", "paths", "switches"]), # Not required for querying all objects + interface=dict(type="str"), # Not required for querying all objects + extpaths=dict(type="list", elements="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["interface_type", "fex", ["extpaths"]], + ["interface_type", "fex_vpc", ["extpaths"]], + ["interface_type", "fex_port_channel", ["extpaths"]], + ["state", "absent", ["ap", "epg", "interface", "leafs", "pod_id", "tenant"]], + ["state", "present", ["ap", "encap_id", "epg", "interface", "leafs", "pod_id", "tenant"]], + ], + ) + + tenant = module.params.get("tenant") + ap = module.params.get("ap") + epg = module.params.get("epg") + description = module.params.get("description") + encap_id = module.params.get("encap_id") + primary_encap_id = module.params.get("primary_encap_id") + deploy_immediacy = module.params.get("deploy_immediacy") + interface_mode = module.params.get("interface_mode") + interface_type = module.params.get("interface_type") + pod_id = module.params.get("pod_id") + leafs = module.params.get("leafs") + interface = module.params.get("interface") + extpaths = module.params.get("extpaths") + state = module.params.get("state") + + aci = ACIModule(module) + + if leafs is not None: + # Process leafs, and support dash-delimited leafs + leafs = [] + for leaf in module.params.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 + extpaths = [] + for extpath in module.params.get("extpaths"): + # Users are likely to use integers for extpaths IDs, which would raise an exception when using the join method + extpaths.extend(str(extpath).split("-")) + if len(extpaths) == 1: + if interface_type == "fex_vpc": + aci.fail_json(msg='A interface_type of "fex_vpc" requires 2 extpaths') + extpaths = extpaths[0] + elif len(extpaths) == 2: + if interface_type != "fex_vpc": + aci.fail_json( + msg='The interface_types "fex" \ + and "fex_port_channel" do not support using multiple extpaths for a single binding' + ) + extpaths = "-".join(extpaths) + else: + aci.fail_json(msg='The "extpaths" parameter must not have more than 2 entries') + + if encap_id is not None: + if encap_id not in range(1, 4097): + aci.fail_json(msg="Valid VLAN assignments are from 1 to 4096") + encap_id = "vlan-{0}".format(encap_id) + + if primary_encap_id is not None: + try: + primary_encap_id = int(primary_encap_id) + if isinstance(primary_encap_id, int) and primary_encap_id in range(1, 4097): + primary_encap_id = "vlan-{0}".format(primary_encap_id) + else: + aci.fail_json(msg="Valid VLAN assignments are from 1 to 4096 or unknown.") + except Exception as e: + if isinstance(primary_encap_id, str) and primary_encap_id != "unknown": + aci.fail_json(msg="Valid VLAN assignments are from 1 to 4096 or unknown. %s" % e) + + static_path = INTERFACE_TYPE_MAPPING[interface_type].format(pod_id=pod_id, leafs=leafs, extpaths=extpaths, interface=interface) + + path_target_filter = {} + if pod_id is not None and leafs is not None and interface is not None and (interface_type != "fex" or extpaths is not None): + path_target_filter = {"tDn": static_path} + + if interface_mode is not None: + interface_mode = INTERFACE_MODE_MAPPING[interface_mode] + + 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="fvRsPathAtt", + aci_rn="rspathAtt-[{0}]".format(static_path), + module_object=static_path, + target_filter=path_target_filter, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvRsPathAtt", + class_config=dict( + descr=description, + encap=encap_id, + primaryEncap=primary_encap_id, + instrImedcy=deploy_immediacy, + mode=interface_mode, + tDn=static_path, + ), + ) + + aci.get_diff(aci_class="fvRsPathAtt") + + 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_static_node_mgmt_address.py b/ansible_collections/cisco/aci/plugins/modules/aci_static_node_mgmt_address.py new file mode 100644 index 000000000..62602487e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_static_node_mgmt_address.py @@ -0,0 +1,346 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Sudhakar Shet Kudtarkar (@kudtarkar1) +# Copyright: (c) 2020, Lionel Hercot <lhercot@cisco.com> +# Copyright: (c) 2020, Shreyas Srish <ssrish@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 + +DOCUMENTATION = r""" +--- +module: aci_static_node_mgmt_address +short_description: In band or Out of band management IP address +description: +- Cisco ACI Fabric Node IP address +options: + epg: + description: + - The name of the end point group + type: str + pod_id: + description: + - The pod number of the leaf, spine or APIC + type: int + node_id: + description: + - ACI Fabric's node id of a leaf, spine or APIC + type: int + ipv4_address: + description: + - ipv4 address of in band/out of band mgmt + type: str + aliases: [ ip ] + ipv4_gw: + description: + - Gateway address of in band / out of band mgmt network + type: str + aliases: [ gw ] + ipv6_address: + description: + - ipv6 address of in band/out of band mgmt + type: str + aliases: [ ipv6 ] + ipv6_gw: + description: + - GW address of in band/out of band mgmt + type: str + type: + description: + - type of management interface + type: str + choices: [ in_band, out_of_band ] + required: true + 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 + +author: +- Sudhakar Shet Kudtarkar (@kudtarkar1) +- Lionel Hercot (@lhercot) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add ipv4 address to in band mgmt interface + cisco.aci.aci_static_node_mgmt_address: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + pod_id: 1 + type: in_band + node_id: 1102 + ipv4_address: "3.1.1.2/24" + ipv4_gw: "3.1.1.1" + state: present + delegate_to: localhost + +- name: Add ipv4 address to out of band mgmt interface + cisco.aci.aci_static_node_mgmt_address: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + pod_id: 1 + type: out_of_band + node_id: 1102 + ipv4_address: "3.1.1.2/24" + ipv4_gw: "3.1.1.1" + state: present + delegate_to: localhost + +- name: Remove ipv4 address to in band mgmt interface + cisco.aci.aci_static_node_mgmt_address: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + pod_id: 1 + type: in_band + node_id: 1102 + ipv4_address: "3.1.1.2/24" + ipv4_gw: "3.1.1.1" + state: absent + delegate_to: localhost + +- name: Query the in band mgmt ipv4 address + cisco.aci.aci_static_node_mgmt_address: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + pod_id: 1 + type: in_band + node_id: 1102 + ipv4_address: "3.1.1.2/24" + ipv4_gw: "3.1.1.1" + state: query + delegate_to: localhost + +- name: Query all addresses in epg out of band25wf + cisco.aci.aci_static_node_mgmt_address: + host: "Host IP" + username: admin + password: SomeSecretePassword + epg: default + type: out_of_band + state: query + delegate_to: localhost + +- name: Query all in band addresses + cisco.aci.aci_static_node_mgmt_address: + host: "Host IP" + username: admin + password: SomeSecretePassword + type: in_band + 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 "/></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: class_map (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( + node_id=dict(type="int"), + pod_id=dict(type="int"), + type=dict(type="str", choices=["in_band", "out_of_band"], required=True), + epg=dict(type="str"), + ipv4_address=dict(type="str", aliases=["ip"]), + ipv4_gw=dict(type="str", aliases=["gw"]), + ipv6_address=dict(type="str", aliases=["ipv6"]), + ipv6_gw=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", ["node_id", "epg"]], ["state", "present", ["node_id", "epg", "ipv4_address", "ipv4_gw"]]], + ) + + pod_id = module.params.get("pod_id") + node_id = module.params.get("node_id") + type = module.params.get("type") + epg = module.params.get("epg") + ipv4_address = module.params.get("ipv4_address") + ipv4_gw = module.params.get("ipv4_gw") + ipv6_address = module.params.get("ipv6_address") + ipv6_gw = module.params.get("ipv6_gw") + state = module.params.get("state") + + class_map = dict( + in_band=list([dict(aci_class="mgmtInb", aci_rn="inb-{0}"), dict(aci_class="mgmtRsInBStNode", aci_rn="rsinBStNode-[{0}]")]), + out_of_band=list([dict(aci_class="mgmtOob", aci_rn="oob-{0}"), dict(aci_class="mgmtRsOoBStNode", aci_rn="rsooBStNode-[{0}]")]), + ) + + static_path = None + if pod_id is not None and node_id is not None: + static_path = "topology/pod-{0}/node-{1}".format(pod_id, node_id) + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-mgmt", + module_object="mgmt", + target_filter={"name": "mgmt"}, + ), + subclass_1=dict( + aci_class="mgmtMgmtP", + aci_rn="mgmtp-default", + module_object="default", + target_filter={"name": "default"}, + ), + subclass_2=dict( + aci_class=class_map.get(type)[0]["aci_class"], + aci_rn=class_map.get(type)[0]["aci_rn"].format(epg), + module_object=epg, + target_filter={"name": epg}, + ), + subclass_3=dict( + aci_class=class_map.get(type)[1]["aci_class"], + aci_rn=class_map.get(type)[1]["aci_rn"].format(static_path), + module_object=static_path, + target_filter={"name": static_path}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=class_map.get(type)[1]["aci_class"], + class_config=dict(addr=ipv4_address, gw=ipv4_gw, v6Addr=ipv6_address, v6Gw=ipv6_gw), + ) + aci.get_diff(aci_class=class_map.get(type)[1]["aci_class"]) + + 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_switch_leaf_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_switch_leaf_selector.py new file mode 100644 index 000000000..2b850f1cd --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_switch_leaf_selector.py @@ -0,0 +1,352 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.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_switch_leaf_selector +short_description: Bind leaf selectors to switch policy leaf profiles (infra:LeafS, infra:NodeBlk, infra:RsAccNodePGrep) +description: +- Bind leaf selectors (with node block range and policy group) to switch policy leaf profiles on Cisco ACI fabrics. +options: + description: + description: + - The description to assign to the C(leaf). + type: str + leaf_profile: + description: + - Name of the Leaf Profile to which we add a Selector. + type: str + aliases: [ leaf_profile_name ] + leaf: + description: + - Name of Leaf Selector. + type: str + aliases: [ name, leaf_name, leaf_profile_leaf_name, leaf_selector_name ] + leaf_node_blk: + description: + - Name of Node Block range to be added to Leaf Selector of given Leaf Profile. + type: str + aliases: [ leaf_node_blk_name, node_blk_name ] + leaf_node_blk_description: + description: + - The description to assign to the C(leaf_node_blk) + type: str + from: + description: + - Start of Node Block range. + type: int + aliases: [ node_blk_range_from, from_range, range_from ] + to: + description: + - Start of Node Block range. + type: int + aliases: [ node_blk_range_to, to_range, range_to ] + policy_group: + description: + - Name of the Policy Group to be added to Leaf Selector of given Leaf Profile. + type: str + aliases: [ name, policy_group_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: +- This module is to be used with M(cisco.aci.aci_switch_policy_leaf_profile). + One first creates a leaf profile (infra:NodeP) and then creates an associated selector (infra:LeafS), +seealso: +- module: cisco.aci.aci_switch_policy_leaf_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:LeafS), + B(infra:NodeBlk) and B(infra:RsAccNodePGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +""" + +EXAMPLES = r""" +- name: adding a switch policy leaf profile selector associated Node Block range (w/ policy group) + cisco.aci.aci_switch_leaf_selector: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + leaf: leaf_selector_name + leaf_node_blk: node_blk_name + from: 1011 + to: 1011 + policy_group: somepolicygroupname + state: present + delegate_to: localhost + +- name: adding a switch policy leaf profile selector associated Node Block range (w/o policy group) + cisco.aci.aci_switch_leaf_selector: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + leaf: leaf_selector_name + leaf_node_blk: node_blk_name + from: 1011 + to: 1011 + state: present + delegate_to: localhost + +- name: Removing a switch policy leaf profile selector + cisco.aci.aci_switch_leaf_selector: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + leaf: leaf_selector_name + state: absent + delegate_to: localhost + +- name: Querying a switch policy leaf profile selector + cisco.aci.aci_switch_leaf_selector: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + leaf: leaf_selector_name + 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( + { + "description": dict(type="str"), + "leaf_profile": dict(type="str", aliases=["leaf_profile_name"]), # Not required for querying all objects + "leaf": dict(type="str", aliases=["name", "leaf_name", "leaf_profile_leaf_name", "leaf_selector_name"]), # Not required for querying all objects + "leaf_node_blk": dict(type="str", aliases=["leaf_node_blk_name", "node_blk_name"]), + "leaf_node_blk_description": dict(type="str"), + # NOTE: Keyword 'from' is a reserved word in python, so we need it as a string + "from": dict(type="int", aliases=["node_blk_range_from", "from_range", "range_from"]), + "to": dict(type="int", aliases=["node_blk_range_to", "to_range", "range_to"]), + "policy_group": dict(type="str", aliases=["policy_group_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", ["leaf_profile", "leaf"]], ["state", "present", ["leaf_profile", "leaf", "leaf_node_blk", "from", "to"]]], + ) + + description = module.params.get("description") + leaf_profile = module.params.get("leaf_profile") + leaf = module.params.get("leaf") + leaf_node_blk = module.params.get("leaf_node_blk") + leaf_node_blk_description = module.params.get("leaf_node_blk_description") + from_ = module.params.get("from") + to_ = module.params.get("to") + policy_group = module.params.get("policy_group") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + # Build child_configs dynamically + child_configs = [ + dict( + infraNodeBlk=dict( + attributes=dict( + descr=leaf_node_blk_description, + name=leaf_node_blk, + from_=from_, + to_=to_, + ), + ), + ), + ] + + # Add infraRsAccNodePGrp only when policy_group was defined + if policy_group is not None: + child_configs.append( + dict( + infraRsAccNodePGrp=dict( + attributes=dict( + tDn="uni/infra/funcprof/accnodepgrp-{0}".format(policy_group), + ), + ), + ) + ) + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="infraNodeP", + aci_rn="infra/nprof-{0}".format(leaf_profile), + module_object=leaf_profile, + target_filter={"name": leaf_profile}, + ), + subclass_1=dict( + aci_class="infraLeafS", + # NOTE: normal rn: leaves-{name}-typ-{type}, hence here hardcoded to range for purposes of module + aci_rn="leaves-{0}-typ-range".format(leaf), + module_object=leaf, + target_filter={"name": leaf}, + ), + # NOTE: infraNodeBlk is not made into a subclass because there is a 1-1 mapping between node block and leaf selector name + child_classes=["infraNodeBlk", "infraRsAccNodePGrp"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraLeafS", + class_config=dict( + descr=description, + name=leaf, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="infraLeafS") + + 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_switch_policy_leaf_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_switch_policy_leaf_profile.py new file mode 100644 index 000000000..8d62ee221 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_switch_policy_leaf_profile.py @@ -0,0 +1,253 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.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_switch_policy_leaf_profile +short_description: Manage switch policy leaf profiles (infra:NodeP) +description: +- Manage switch policy leaf profiles on Cisco ACI fabrics. +options: + leaf_profile: + description: + - The name of the Leaf Profile. + type: str + aliases: [ leaf_profile_name, name ] + description: + description: + - Description for the Leaf 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 + +seealso: +- module: cisco.aci.aci_switch_policy_leaf_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:NodeP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +""" + +EXAMPLES = r""" +- name: creating a Leaf Profile with description + cisco.aci.aci_switch_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + description: sw_description + state: present + delegate_to: localhost + +- name: Deleting a Leaf Profile + cisco.aci.aci_switch_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + state: absent + delegate_to: localhost + +- name: Query a Leaf Profile + cisco.aci.aci_switch_policy_leaf_profile: + host: apic + username: admin + password: SomeSecretPassword + leaf_profile: sw_name + 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( + leaf_profile=dict(type="str", aliases=["name", "leaf_profile_name"]), # Not required for querying all objects + 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", ["leaf_profile"]], + ["state", "present", ["leaf_profile"]], + ], + ) + + leaf_profile = module.params.get("leaf_profile") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="infraNodeP", + aci_rn="infra/nprof-{0}".format(leaf_profile), + module_object=leaf_profile, + target_filter={"name": leaf_profile}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraNodeP", + class_config=dict( + name=leaf_profile, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="infraNodeP") + + 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_switch_policy_vpc_protection_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_switch_policy_vpc_protection_group.py new file mode 100644 index 000000000..a7994db73 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_switch_policy_vpc_protection_group.py @@ -0,0 +1,305 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.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_switch_policy_vpc_protection_group +short_description: Manage switch policy explicit vPC protection groups (fabric:ExplicitGEp, fabric:NodePEp). +description: +- Manage switch policy explicit vPC protection groups on Cisco ACI fabrics. +options: + protection_group: + description: + - The name of the Explicit vPC Protection Group. + type: str + aliases: [ name, protection_group_name ] + protection_group_id: + description: + - The Explicit vPC Protection Group ID. + type: int + aliases: [ id ] + vpc_domain_policy: + description: + - The vPC domain policy to be associated with the Explicit vPC Protection Group. + type: str + aliases: [ vpc_domain_policy_name ] + switch_1_id: + description: + - The ID of the first Leaf Switch for the Explicit vPC Protection Group. + type: int + switch_2_id: + description: + - The ID of the Second Leaf Switch for the Explicit vPC Protection Group. + type: int + 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 + +seealso: +- module: cisco.aci.aci_switch_policy_leaf_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fabric:ExplicitGEp) and B(fabric:NodePEp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Bruno Calogero (@brunocalogero) +""" + +EXAMPLES = r""" +- name: Add vPC Protection Group + cisco.aci.aci_switch_policy_vpc_protection_group: + host: apic + username: admin + password: SomeSecretPassword + protection_group: leafPair101-vpcGrp + protection_group_id: 6 + switch_1_id: 1011 + switch_2_id: 1012 + state: present + delegate_to: localhost + +- name: Remove Explicit vPC Protection Group + cisco.aci.aci_switch_policy_vpc_protection_group: + host: apic + username: admin + password: SomeSecretPassword + protection_group: leafPair101-vpcGrp + state: absent + delegate_to: localhost + +- name: Query vPC Protection Groups + cisco.aci.aci_switch_policy_vpc_protection_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query our vPC Protection Group + cisco.aci.aci_switch_policy_vpc_protection_group: + host: apic + username: admin + password: SomeSecretPassword + protection_group: leafPair101-vpcGrp + 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( + protection_group=dict(type="str", aliases=["name", "protection_group_name"]), # Not required for querying all objects + protection_group_id=dict(type="int", aliases=["id"]), + vpc_domain_policy=dict(type="str", aliases=["vpc_domain_policy_name"]), + switch_1_id=dict(type="int"), + switch_2_id=dict(type="int"), + 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", ["protection_group"]], + ["state", "present", ["protection_group", "protection_group_id", "switch_1_id", "switch_2_id"]], + ], + ) + + protection_group = module.params.get("protection_group") + protection_group_id = module.params.get("protection_group_id") + vpc_domain_policy = module.params.get("vpc_domain_policy") + switch_1_id = module.params.get("switch_1_id") + switch_2_id = module.params.get("switch_2_id") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fabricExplicitGEp", + aci_rn="fabric/protpol/expgep-{0}".format(protection_group), + module_object=protection_group, + target_filter={"name": protection_group}, + ), + child_classes=["fabricNodePEp", "fabricNodePEp", "fabricRsVpcInstPol"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricExplicitGEp", + class_config=dict( + name=protection_group, + id=protection_group_id, + nameAlias=name_alias, + ), + child_configs=[ + dict( + fabricNodePEp=dict( + attributes=dict( + id="{0}".format(switch_1_id), + ), + ), + ), + dict( + fabricNodePEp=dict( + attributes=dict( + id="{0}".format(switch_2_id), + ), + ), + ), + dict( + fabricRsVpcInstPol=dict( + attributes=dict( + tnVpcInstPolName=vpc_domain_policy, + ), + ), + ), + ], + ) + + aci.get_diff(aci_class="fabricExplicitGEp") + + 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_syslog_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_group.py new file mode 100644 index 000000000..bab3379a1 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_group.py @@ -0,0 +1,337 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_syslog_group +short_description: Manage Syslog groups (syslog:Group, syslog:Console, syslog:File and syslog:Prof). +description: +- Manage syslog groups +options: + admin_state: + description: + - Administrative state of the syslog group + type: str + choices: [ enabled, disabled ] + console_logging: + description: + - Log events to console + type: str + choices: [ enabled, disabled ] + console_log_severity: + description: + - Severity of events to log to console + type: str + choices: [ alerts, critical, debugging, emergencies, error, information, notifications, warnings ] + local_file_logging: + description: + - Log to local file + type: str + choices: [ enabled, disabled ] + local_file_log_severity: + description: + - Severity of events to log to local file + type: str + choices: [ alerts, critical, debugging, emergencies, error, information, notifications, warnings ] + format: + description: + - Format of the syslog messages. If omitted when creating a group, ACI defaults to using aci format. + type: str + choices: [ aci, nxos ] + include_ms: + description: + - Include milliseconds in log timestamps + type: bool + include_time_zone: + description: + - Include timezone in log timestamps + type: bool + name: + description: + - Name of the syslog group + type: str + aliases: [ syslog_group, syslog_group_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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(syslog:Group). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a syslog group + cisco.aci.aci_syslog_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_syslog_group + local_file_logging: enabled + local_file_log_severity: warnings + console_logging: enabled + console_log_severity: critical + state: present + delegate_to: localhost + +- name: Disable logging to local file + cisco.aci.aci_syslog_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_syslog_group + local_file_logging: disabled + state: present + delegate_to: localhost + +- name: Remove a syslog group + cisco.aci.aci_syslog_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_syslog_group + state: absent + delegate_to: localhost + +- name: Query a syslog group + cisco.aci.aci_syslog_group: + host: apic + username: admin + password: SomeSecretPassword + name: my_syslog_group + state: query + delegate_to: localhost + register: query_result + +- name: Query all syslog groups + cisco.aci.aci_syslog_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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + name=dict(type="str", aliases=["syslog_group", "syslog_group_name"]), + format=dict(type="str", choices=["aci", "nxos"]), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + console_logging=dict(type="str", choices=["enabled", "disabled"]), + console_log_severity=dict(type="str", choices=["alerts", "critical", "debugging", "emergencies", "error", "information", "notifications", "warnings"]), + local_file_logging=dict(type="str", choices=["enabled", "disabled"]), + local_file_log_severity=dict( + type="str", choices=["alerts", "critical", "debugging", "emergencies", "error", "information", "notifications", "warnings"] + ), + include_ms=dict(type="bool"), + include_time_zone=dict(type="bool"), + 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") + format = module.params.get("format") + admin_state = module.params.get("admin_state") + console_logging = module.params.get("console_logging") + console_log_severity = module.params.get("console_log_severity") + local_file_logging = module.params.get("local_file_logging") + local_file_log_severity = module.params.get("local_file_log_severity") + include_ms = aci.boolean(module.params.get("include_ms")) + include_time_zone = aci.boolean(module.params.get("include_time_zone")) + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="syslogGroup", + aci_rn="fabric/slgroup-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["syslogRemoteDest", "syslogProf", "syslogFile", "syslogConsole"], + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + name=name, + format=format, + includeMilliSeconds=include_ms, + ) + if include_time_zone is not None: + class_config["includeTimeZone"] = include_time_zone + aci.payload( + aci_class="syslogGroup", + class_config=class_config, + child_configs=[ + dict( + syslogProf=dict( + attributes=dict(adminState=admin_state, name="syslog"), + ), + ), + dict( + syslogFile=dict( + attributes=dict(adminState=local_file_logging, format=format, severity=local_file_log_severity), + ), + ), + dict( + syslogConsole=dict( + attributes=dict(adminState=console_logging, format=format, severity=console_log_severity), + ), + ), + ], + ) + + aci.get_diff(aci_class="syslogGroup") + + 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_syslog_remote_dest.py b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_remote_dest.py new file mode 100644 index 000000000..b3c1a02b4 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_remote_dest.py @@ -0,0 +1,333 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_syslog_remote_dest +short_description: Manage Syslog Remote Destinations (syslog:RemoteDest). +description: +- Manage remote destinations for syslog messages within + an existing syslog group object +options: + admin_state: + description: + - Administrative state of the syslog remote destination + type: str + choices: [ enabled, disabled ] + description: + description: + - Description of the remote destination + type: str + destination: + description: + - Hostname or IP address to send syslog messages to + type: str + format: + description: + - Format of the syslog messages + type: str + choices: [ aci, nxos ] + facility: + description: + - Forwarding facility for syslog messages + type: str + choices: [ local0, local1, local2, local3, local4, local5, local6, local7 ] + group: + description: + - Name of an existing syslog group + type: str + aliases: [ syslog_group, syslog_group_name ] + mgmt_epg: + description: + - Name of a management EPG to send syslog messages from + type: str + name: + description: + - Name of the syslog remote destination + type: str + aliases: [ remote_destination_name, remote_destination ] + severity: + description: + - Severity of messages to send to remote syslog + type: str + choices: [ alerts, critical, debugging, emergencies, error, information, notifications, warnings] + 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 + syslog_port: + description: + - UDP port to send syslog messages to + type: int +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(syslog:RemoteDest). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a syslog remote destination + cisco.aci.aci_syslog_remote_dest: + host: apic + username: admin + password: SomeSecretPassword + group: my_syslog_group + facility: local7 + destination: 10.20.30.40 + syslog_port: 5678 + mgmt_epg: oob-default + state: present + delegate_to: localhost + +- name: Delete syslog remote destination + cisco.aci.aci_syslog_remote_dest: + host: apic + username: admin + password: SomeSecretPassword + group: my_syslog_group + destination: 10.20.30.40 + state: absent + delegate_to: localhost + +- name: Query a syslog remote destination + cisco.aci.aci_syslog_remote_dest: + host: apic + username: admin + password: SomeSecretPassword + group: my_syslog_group + destination: 10.20.30.40 + state: query + delegate_to: localhost + register: query_result + +- name: Query all syslog remote destinations + cisco.aci.aci_syslog_remote_dest: + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + name=dict(type="str", aliases=["remote_destination_name", "remote_destination"]), + format=dict(type="str", choices=["aci", "nxos"]), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + description=dict(type="str"), + destination=dict(type="str"), + facility=dict(type="str", choices=["local0", "local1", "local2", "local3", "local4", "local5", "local6", "local7"]), + group=dict(type="str", aliases=["syslog_group", "syslog_group_name"]), + mgmt_epg=dict(type="str"), + syslog_port=dict(type="int"), + severity=dict(type="str", choices=["alerts", "critical", "debugging", "emergencies", "error", "information", "notifications", "warnings"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["group", "destination"]], + ["state", "present", ["group", "destination"]], + ], + ) + + aci = ACIModule(module) + + name = module.params.get("name") + format = module.params.get("format") + admin_state = module.params.get("admin_state") + description = module.params.get("description") + destination = module.params.get("destination") + facility = module.params.get("facility") + group = module.params.get("group") + syslog_port = module.params.get("syslog_port") + severity = module.params.get("severity") + state = module.params.get("state") + mgmt_epg = module.params.get("mgmt_epg") + + aci.construct_url( + root_class=dict( + aci_class="syslogGroup", + aci_rn="fabric/slgroup-{0}".format(group), + module_object=group, + target_filter={"name": group}, + ), + subclass_1=dict( + aci_class="syslogRemoteDest", + aci_rn="rdst-{0}".format(destination), + module_object=destination, + target_filter={"host": destination}, + ), + child_classes=["fileRsARemoteHostToEpg"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if mgmt_epg: + child_configs.append( + dict( + fileRsARemoteHostToEpg=dict( + attributes=dict(tDn=("uni/tn-mgmt/mgmtp-default/{0}".format(mgmt_epg))), + ), + ) + ) + aci.payload( + aci_class="syslogRemoteDest", + class_config=dict( + adminState=admin_state, + descr=description, + format=format, + forwardingFacility=facility, + host=destination, + name=name, + port=syslog_port, + severity=severity, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="syslogRemoteDest") + + 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_syslog_source.py b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py new file mode 100644 index 000000000..95dcd7f39 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Tim Cragg <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_syslog_source +short_description: Manage Syslog Source objects (syslog:Src) +description: +- Manage Syslog Source objects +options: + name: + description: + - Name of the Syslog Source policy + type: str + aliases: [ syslog_src, syslog_source ] + include: + description: + - List of message types to include + type: list + elements: str + choices: [ audit, events, faults, session ] + min_severity: + description: + - Minimum Severity of message to include + type: str + choices: [ alerts, critical, debugging, emergencies, errors, information, notifications, warnings ] + destination_group: + description: + - Name of an existing syslog group + 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 + default: present + choices: [ absent, present, query ] +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(syslog:Src). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new syslog source + cisco.aci.aci_syslog_source: + host: apic + username: admin + password: SomeSecretPassword + name: my_syslog_src + include: + - audit + - events + - faults + min_severity: errors + destination_group: my_syslog_group + state: present + delegate_to: localhost + +- name: Remove an existing syslog source + cisco.aci.aci_syslog_source: + host: apic + username: admin + password: SomeSecretPassword + name: my_syslog_src + state: absent + delegate_to: localhost + +- name: Query all syslog sources + cisco.aci.aci_syslog_source: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific syslog source + cisco.aci.aci_syslog_source: + host: apic + username: admin + password: SomeSecretPassword + name: my_syslog_src + 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( + name=dict(type="str", aliases=["syslog_src", "syslog_source"]), + include=dict(type="list", elements="str", choices=["audit", "events", "faults", "session"]), + min_severity=dict(type="str", choices=["alerts", "critical", "debugging", "emergencies", "errors", "information", "notifications", "warnings"]), + destination_group=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"]], + ], + ) + + name = module.params.get("name") + include = module.params.get("include") + min_severity = module.params.get("min_severity") + destination_group = module.params.get("destination_group") + state = module.params.get("state") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="syslogSrc", + aci_rn="fabric/moncommon/slsrc-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["syslogRsDestGroup"], + ) + + aci.get_existing() + + if state == "present": + class_config = dict(name=name, minSev=min_severity) + if include: + class_config["incl"] = ",".join(include) + + aci.payload( + aci_class="syslogSrc", + class_config=class_config, + child_configs=[ + dict( + syslogRsDestGroup=dict( + attributes=dict(tDn=("uni/fabric/slgroup-{0}".format(destination_group))), + ), + ), + ], + ) + + aci.get_diff(aci_class="syslogSrc") + + 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_system.py b/ansible_collections/cisco/aci/plugins/modules/aci_system.py new file mode 100644 index 000000000..6a3349ec0 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_system.py @@ -0,0 +1,194 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2020, Lionel Hercot (@lhercot) <lhercot@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 + +DOCUMENTATION = r""" +--- +module: aci_system +short_description: Query the ACI system information (top:System) +description: +- Query the ACI system information (top:System) on Cisco ACI. +author: +- Lionel Hercot (@lhercot) +options: + id: + description: + - The controller node ID + aliases: [ controller, node ] + type: int + state: + description: + - Use C(query) for listing an object or multiple objects. + choices: [ query ] + default: query + type: str + +notes: +- More information about the internal APIC class B(top:System) from + L(the APIC Management Information Model reference,https://developer.cisco.com/docs/apic-mim-ref/). +- This module is used to query system information for both cloud and on-premises controllers. + +extends_documentation_fragment: +- cisco.aci.aci +""" + +EXAMPLES = r""" +- name: Query all controllers system information + cisco.aci.aci_system: + host: apic + username: userName + password: somePassword + validate_certs: false + state: query + delegate_to: localhost + +- name: Query controller 1 specific system information + cisco.aci.aci_system: + host: apic + username: userName + password: somePassword + validate_certs: false + id: 1 + 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update( + id=dict(type="int", aliases=["controller", "node"]), + state=dict(type="str", default="query", choices=["query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + id = module.params.get("id") + + aci = ACIModule(module) + aci.construct_url(root_class=dict(aci_class="topSystem", target_filter={"id": id})) + + aci.get_existing() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_taboo_contract.py b/ansible_collections/cisco/aci/plugins/modules/aci_taboo_contract.py new file mode 100644 index 000000000..aaa0914bb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_taboo_contract.py @@ -0,0 +1,289 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2018, Dag Wieers (dagwieers) <dag@wieers.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_taboo_contract +short_description: Manage taboo contracts (vz:BrCP) +description: +- Manage taboo contracts on Cisco ACI fabrics. +options: + taboo_contract: + description: + - The name of the Taboo Contract. + type: str + aliases: [ name ] + description: + description: + - The description for the Taboo Contract. + type: str + aliases: [ descr ] + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + scope: + description: + - The scope of a service contract. + - The APIC defaults to C(context) when unset during creation. + type: str + choices: [ application-profile, context, global, tenant ] + 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(vz:BrCP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add taboo contract + cisco.aci.aci_taboo_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_test + taboo_contract: taboo_contract_test + state: present + delegate_to: localhost + +- name: Remove taboo contract + cisco.aci.aci_taboo_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_test + taboo_contract: taboo_contract_test + state: absent + delegate_to: localhost + +- name: Query all taboo contracts + cisco.aci.aci_taboo_contract: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific taboo contract + cisco.aci.aci_taboo_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_test + taboo_contract: taboo_contract_test + 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( + taboo_contract=dict(type="str", aliases=["name"]), # Not required for querying all contracts + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all contracts + scope=dict(type="str", choices=["application-profile", "context", "global", "tenant"]), + 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", ["tenant", "taboo_contract"]], + ["state", "present", ["tenant", "taboo_contract"]], + ], + ) + + taboo_contract = module.params.get("taboo_contract") + description = module.params.get("description") + scope = module.params.get("scope") + 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="vzTaboo", + aci_rn="taboo-{0}".format(taboo_contract), + module_object=taboo_contract, + target_filter={"name": taboo_contract}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vzTaboo", + class_config=dict( + name=taboo_contract, + descr=description, + scope=scope, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="vzTaboo") + + 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_tag.py b/ansible_collections/cisco/aci/plugins/modules/aci_tag.py new file mode 100644 index 000000000..9508f1d6b --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tag.py @@ -0,0 +1,270 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_tag +short_description: Tagging of ACI objects +description: +- Tagging a object on Cisco ACI fabric. +options: + dn: + description: + - Unique Distinguished Name (DN) from ACI object model. + type: str + tag_key: + description: + - Unique identifier of tag object. + type: str + tag_value: + description: + - Value of the property. + type: str + tag_type: + description: + - Type of tag object. + type: str + choices: [ annotation, instance, tag ] + required: true + 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 + +notes: +- The ACI object must exist before using this module in your playbook. +- CAVEAT - Due to deprecation of the 'tagInst' object, creating a tag with tag_type 'instance' automatically generates a + 'annotation' tag_type tag with an empty value. When deleting a tag_type 'instance', the 'tagAnnotation' object will + remain present and needs to be deleted separately. +seealso: +- name: Cisco APIC System Management Configuration Guide + description: More information about the tagging can be found in the Cisco APIC System Management Configuration Guide. + link: https://www.cisco.com/c/en/us/support/cloud-systems-management/application-policy-infrastructure-controller-apic/tsd-products-support-series-home.html +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a new annotation tag + cisco.aci.aci_tag: + host: apic + username: admin + password: SomeSecretPassword + dn: SomeValidAciDN + tag_key: foo + tag_value: bar + tag_type: annotation + state: present + delegate_to: localhost +- name: Delete annotation tag + cisco.aci.aci_tag: + host: apic + username: admin + password: SomeSecretPassword + dn: SomeValidAciDN + tag_key: foo + tag_type: annotation + state: absent + delegate_to: localhost +- name: Query annotation tag + cisco.aci.aci_tag: + host: apic + username: admin + password: SomeSecretPassword + dn: SomeValidAciDN + tag_key: foo + tag_type: annotation + state: query + delegate_to: localhost +- name: Query annotation tags + cisco.aci.aci_tag: + host: apic + username: admin + password: SomeSecretPassword + tag_type: annotation + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update( + dn=dict(type="str"), + tag_key=dict(type="str", no_log=False), + tag_value=dict(type="str", default=""), + tag_type=dict(type="str", choices=["annotation", "instance", "tag"], required=True), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["dn", "tag_key"]], + ["state", "present", ["dn", "tag_key"]], + ], + ) + + aci = ACIModule(module) + + dn = module.params.get("dn") + tag_key = module.params.get("tag_key") + tag_value = module.params.get("tag_value") + tag_type = module.params.get("tag_type") + state = module.params.get("state") + + if module.params.get("dn") is not None: + dn = dn.lstrip("uni/") + + aci_type = dict( + annotation=dict(dn="{0}/annotationKey-{1}".format(dn, tag_key), name="tagAnnotation", config=dict(value=tag_value)), + instance=dict(dn="{0}/tag-{1}".format(dn, tag_key), name="tagInst", config=dict()), + tag=dict(dn="{0}/tagKey-{1}".format(dn, tag_key), name="tagTag", config=dict(value=tag_value)), + ) + + aci.construct_url( + root_class=dict( + aci_class=aci_type[tag_type]["name"], + aci_rn=aci_type[tag_type]["dn"], + module_object=tag_key, + target_filter={"name": tag_key}, + ) + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class=aci_type[tag_type]["name"], class_config=aci_type[tag_type]["config"]) + aci.get_diff(aci_class=aci_type[tag_type]["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_tenant.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant.py new file mode 100644 index 000000000..95fa8b85a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant.py @@ -0,0 +1,264 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_tenant +short_description: Manage tenants (fv:Tenant) +description: +- Manage tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the tenant. + type: str + aliases: [ name, tenant_name ] + description: + description: + - Description for the tenant. + 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 + +seealso: +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_bd +- module: cisco.aci.aci_contract +- module: cisco.aci.aci_filter +- module: cisco.aci.aci_vrf +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:Tenant). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Add a new tenant + cisco.aci.aci_tenant: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + description: Production tenant + state: present + delegate_to: localhost + +- name: Remove a tenant + cisco.aci.aci_tenant: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + state: absent + delegate_to: localhost + +- name: Query a tenant + cisco.aci.aci_tenant: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + state: query + delegate_to: localhost + register: query_result + +- name: Query all tenants + cisco.aci.aci_tenant: + 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 + + +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=["name", "tenant_name"]), # Not required for querying all objects + 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", ["tenant"]], + ["state", "present", ["tenant"]], + ], + ) + + 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}, + ), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvTenant", + class_config=dict( + name=tenant, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fvTenant") + + 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_tenant_action_rule_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_action_rule_profile.py new file mode 100644 index 000000000..55b76f3d6 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_action_rule_profile.py @@ -0,0 +1,277 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_tenant_action_rule_profile +short_description: Manage action rule profiles (rtctrl:AttrP) +description: +- Manage action rule profiles on Cisco ACI fabrics. +options: + action_rule: + description: + - The name of the action rule profile. + type: str + aliases: [ action_rule_name, name ] + description: + description: + - The description for the action rule profile. + type: str + aliases: [ descr ] + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_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 + +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:AttrP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Create a action rule profile + cisco.aci.aci_tenant_action_rule_profile: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + state: present + delegate_to: localhost + +- name: Delete a action rule profile + cisco.aci.aci_tenant_action_rule_profile: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + state: absent + delegate_to: localhost + +- name: Query all action rule profiles + cisco.aci.aci_tenant_action_rule_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific action rule profile + cisco.aci.aci_tenant_action_rule_profile: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + 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( + action_rule=dict(type="str", aliases=["action_rule_name", "name"]), # Not required for querying all objects + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + 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", ["action_rule", "tenant"]], + ["state", "present", ["action_rule", "tenant"]], + ], + ) + + action_rule = module.params.get("action_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="rtctrlAttrP", + aci_rn="attr-{0}".format(action_rule), + module_object=action_rule, + target_filter={"name": action_rule}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlAttrP", + class_config=dict( + name=action_rule, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlAttrP") + + 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_tenant_ep_retention_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py new file mode 100644 index 000000000..c1896bb3a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_ep_retention_policy.py @@ -0,0 +1,364 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_tenant_ep_retention_policy +short_description: Manage End Point (EP) retention protocol policies (fv:EpRetPol) +description: +- Manage End Point (EP) retention protocol policies on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + epr_policy: + description: + - The name of the end point retention policy. + type: str + aliases: [ epr_name, name ] + bounce_age: + description: + - Bounce entry aging interval in seconds. + - Accepted values range between C(150) and C(65535); 0 is used for infinite. + - The APIC defaults to C(630) when unset during creation. + type: int + bounce_trigger: + description: + - Determines if the bounce entries are installed by RARP Flood or COOP Protocol. + - The APIC defaults to C(coop) when unset during creation. + type: str + choices: [ coop, flood ] + hold_interval: + description: + - Hold interval in seconds. + - Accepted values range between C(5) and C(65535). + - The APIC defaults to C(300) when unset during creation. + type: int + local_ep_interval: + description: + - Local end point aging interval in seconds. + - Accepted values range between C(120) and C(65535); 0 is used for infinite. + - The APIC defaults to C(900) when unset during creation. + type: int + remote_ep_interval: + description: + - Remote end point aging interval in seconds. + - Accepted values range between C(120) and C(65535); 0 is used for infinite. + - The APIC defaults to C(300) when unset during creation. + type: int + move_frequency: + description: + - Move frequency per second. + - Accepted values range between C(0) and C(65535); 0 is used for none. + - The APIC defaults to C(256) when unset during creation. + type: int + description: + description: + - Description for the End point retention 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. + 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(fv:EpRetPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Swetha Chunduri (@schunduri) +""" + +EXAMPLES = r""" +- name: Add a new EPR policy + cisco.aci.aci_tenant_ep_retention_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + epr_policy: EPRPol1 + bounce_age: 630 + hold_interval: 300 + local_ep_interval: 900 + remote_ep_interval: 300 + move_frequency: 256 + description: test + state: present + delegate_to: localhost + +- name: Remove an EPR policy + cisco.aci.aci_tenant_ep_retention_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + epr_policy: EPRPol1 + state: absent + delegate_to: localhost + +- name: Query an EPR policy + cisco.aci.aci_tenant_ep_retention_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + epr_policy: EPRPol1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all EPR policies + cisco.aci.aci_tenant_ep_retention_policy: + 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 + +BOUNCE_TRIG_MAPPING = dict( + coop="protocol", + rarp="rarp-flood", +) + + +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 + epr_policy=dict(type="str", aliases=["epr_name", "name"]), # Not required for querying all objects + bounce_age=dict(type="int"), + bounce_trigger=dict(type="str", choices=["coop", "flood"]), + hold_interval=dict(type="int"), + local_ep_interval=dict(type="int"), + remote_ep_interval=dict(type="int"), + description=dict(type="str", aliases=["descr"]), + move_frequency=dict(type="int"), + 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", ["epr_policy", "tenant"]], + ["state", "present", ["epr_policy", "tenant"]], + ], + ) + + epr_policy = module.params.get("epr_policy") + bounce_age = module.params.get("bounce_age") + if bounce_age is not None and bounce_age != 0 and bounce_age not in range(150, 65536): + module.fail_json(msg="The bounce_age must be a value of 0 or between 150 and 65535") + if bounce_age == 0: + bounce_age = "infinite" + bounce_trigger = module.params.get("bounce_trigger") + if bounce_trigger is not None: + bounce_trigger = BOUNCE_TRIG_MAPPING[bounce_trigger] + description = module.params.get("description") + hold_interval = module.params.get("hold_interval") + if hold_interval is not None and hold_interval not in range(5, 65536): + module.fail_json(msg="The hold_interval must be a value between 5 and 65535") + local_ep_interval = module.params.get("local_ep_interval") + if local_ep_interval is not None and local_ep_interval != 0 and local_ep_interval not in range(120, 65536): + module.fail_json(msg="The local_ep_interval must be a value of 0 or between 120 and 65535") + if local_ep_interval == 0: + local_ep_interval = "infinite" + move_frequency = module.params.get("move_frequency") + if move_frequency is not None and move_frequency not in range(65536): + module.fail_json(msg="The move_frequency must be a value between 0 and 65535") + 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): + 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" + 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="fvEpRetPol", + aci_rn="epRPol-{0}".format(epr_policy), + module_object=epr_policy, + target_filter={"name": epr_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvEpRetPol", + class_config=dict( + name=epr_policy, + descr=description, + bounceAgeIntvl=bounce_age, + bounceTrig=bounce_trigger, + holdIntvl=hold_interval, + localEpAgeIntvl=local_ep_interval, + remoteEpAgeIntvl=remote_ep_interval, + moveFreq=move_frequency, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fvEpRetPol") + + 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_tenant_span_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py new file mode 100644 index 000000000..952e8fe28 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_dst_group.py @@ -0,0 +1,436 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_tenant_span_dst_group +short_description: Manage SPAN destination groups (span:DestGrp) +description: +- Manage SPAN destination groups on Cisco ACI fabrics. +options: + destination_group: + description: + - The name of the SPAN destination group. + type: str + aliases: [ name, dst_group ] + description: + description: + - The description of the SPAN destination group. + type: str + aliases: [ descr ] + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + destination_epg: + description: + - The destination end point group. + type: dict + suboptions: + epg: + description: + - The name of the end point group. + type: str + ap: + description: + - The name of application profile. + type: str + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + source_ip: + description: + - The source IP address or prefix. + type: str + destination_ip: + description: + - The destination IP address. + type: str + span_version: + description: + - SPAN version. + type: str + choices: [ version_1, version_2 ] + flow_id: + description: + - The flow ID of the SPAN packet. + type: int + ttl: + description: + - The time to live of the span session packets. + type: int + mtu: + description: + - The MTU truncation size for the packets. + type: int + dscp: + description: + - The DSCP value for sending the monitored packets using ERSPAN. + 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 ] + version_enforced: + description: + - Enforce SPAN version. + type: bool + 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(span:DestGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Dag Wieers (@dagwieers) +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add SPAN destination group + cisco.aci.aci_tenant_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: Tenant1 + destination_epg: + tenant: Test1 + ap: ap1 + epg: ep1 + destination_group: group1 + description: Test span + destination_ip: 10.0.0.1 + source_ip: 10.0.2.1 + version_enforced: false + span_version: version_1 + ttl: 2 + mtu: 1500 + flow_id: 1 + dscp: CS1 + state: present + delegate_to: localhost + +- name: Remove SPAN destination group + cisco.aci.aci_tenant_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: Tenant1 + destination_group: group1 + state: absent + delegate_to: localhost + +- name: Query SPAN destination group + cisco.aci.aci_tenant_span_dst_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: Tenant1 + destination_group: group1 + state: query + delegate_to: localhost + +- name: Query all SPAN destination groups + cisco.aci.aci_tenant_span_dst_group: + 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 destination_epg_spec(): + return dict( + epg=dict(type="str"), + ap=dict(type="str"), + tenant=dict(type="str", aliases=["tenant_name"]), + ) + + +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 + destination_epg=dict(type="dict", options=destination_epg_spec()), + destination_group=dict(type="str", aliases=["name", "dst_group"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + source_ip=dict(type="str"), + destination_ip=dict(type="str"), + mtu=dict(type="int"), + ttl=dict(type="int"), + flow_id=dict(type="int"), + version_enforced=dict(type="bool"), + span_version=dict(type="str", choices=["version_1", "version_2"]), + 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", + ], + ), + 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", "tenant"]], + ["state", "present", ["destination_group", "destination_ip", "source_ip", "destination_epg", "tenant"]], + ], + ) + + aci = ACIModule(module) + + destination_epg = module.params.get("destination_epg") + destination_group = module.params.get("destination_group") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + destination_ip = module.params.get("destination_ip") + source_ip = module.params.get("source_ip") + span_version = module.params.get("span_version") + name_alias = module.params.get("name_alias") + dscp = module.params.get("dscp") + mtu = str(module.params.get("mtu")) + ttl = str(module.params.get("ttl")) + flow_id = str(module.params.get("flow_id")) + version_enforced = module.params.get("version_enforced") + + 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="spanDestGrp", + aci_rn="destgrp-{0}".format(destination_group), + module_object=destination_group, + target_filter={"name": destination_group}, + ), + child_classes=["spanDest", "spanRsDestEpg"], + ) + + aci.get_existing() + + if state == "present": + dest_tdn = "uni/tn-{0}/ap-{1}/epg-{2}".format(destination_epg["tenant"], destination_epg["ap"], destination_epg["epg"]) + + if version_enforced is True: + version_enforced = "yes" + else: + version_enforced = "no" + + if span_version == "version_1": + span_version = "ver1" + else: + span_version = "ver2" + + child_configs = [ + dict( + spanDest=dict( + attributes=dict(name=destination_group), + children=[ + dict( + spanRsDestEpg=dict( + attributes=dict( + ip=destination_ip, + srcIpPrefix=source_ip, + ver=span_version, + verEnforced=version_enforced, + ttl=ttl, + mtu=mtu, + flowId=flow_id, + dscp=dscp, + tDn=dest_tdn, + ) + ) + ) + ], + ) + ), + ] + + aci.payload( + aci_class="spanDestGrp", + class_config=dict( + name=destination_group, + descr=description, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + 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_tenant_span_src_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py new file mode 100644 index 000000000..ab3e8b341 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_tenant_span_src_group +short_description: Manage SPAN source groups (span:SrcGrp) +description: +- Manage SPAN source groups on Cisco ACI fabrics. +options: + admin_state: + description: + - Enable or disable the span sources. + - The APIC defaults to C(true) when unset during creation. + type: bool + description: + description: + - The description for Span source group. + type: str + aliases: [ descr ] + dst_group: + description: + - The Span destination group to associate with the source group. + type: str + src_group: + description: + - The name of the Span source group. + type: str + aliases: [ 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 + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] +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(span:SrcGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Create a SPAN source group + cisco.aci.aci_tenant_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + src_group: my_span_source_group + tenant: prod + state: present + delegate_to: localhost + +- name: Delete a SPAN source group + cisco.aci.aci_tenant_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + src_group: my_span_source_group + tenant: prod + state: absent + delegate_to: localhost + +- name: Query all SPAN source groups + cisco.aci.aci_tenant_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific SPAN source group + cisco.aci.aci_tenant_span_src_group: + host: apic + username: admin + password: SomeSecretPassword + src_group: my_span_source_group + tenant: prod + 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 + src_group=dict(type="str", aliases=["name"]), # Not required for querying all objects + admin_state=dict(type="bool"), + description=dict(type="str", aliases=["descr"]), + dst_group=dict(type="str"), + 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", ["src_group", "tenant"]], + ["state", "present", ["src_group", "tenant"]], + ], + ) + + aci = ACIModule(module) + + admin_state = aci.boolean(module.params.get("admin_state"), "enabled", "disabled") + description = module.params.get("description") + dst_group = module.params.get("dst_group") + src_group = module.params.get("src_group") + state = module.params.get("state") + tenant = module.params.get("tenant") + 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="spanSrcGrp", + aci_rn="srcgrp-{0}".format(src_group), + module_object=src_group, + target_filter={"name": src_group}, + ), + child_classes=["spanSpanLbl"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="spanSrcGrp", + class_config=dict( + adminSt=admin_state, + descr=description, + name=src_group, + nameAlias=name_alias, + ), + child_configs=[{"spanSpanLbl": {"attributes": {"name": dst_group}}}], + ) + + 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_tenant_span_src_group_src.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py new file mode 100644 index 000000000..02d118416 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_src.py @@ -0,0 +1,316 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2022, 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 + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_tenant_span_src_group_src +short_description: Manage SPAN source group sources (span:Src) +description: +- Manage SPAN source group sources on Cisco ACI fabrics. +options: + name: + description: + - The name of the Span source. + type: str + description: + description: + - The description for Span source. + type: str + aliases: [ descr ] + src_group: + description: + - The name of the Span source group. + type: str + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] + direction: + description: + - The direction of the SPAN source. + type: str + choices: [ incoming, outgoing, both ] + src_epg: + description: + - The name of the Span source epg. + type: str + aliases: [ epg ] + src_ap: + description: + - The name of the Span source ap. + type: str + aliases: [ ap ] + 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) 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(span:SrcGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a SPAN source + cisco.aci.aci_tenant_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + src_group: my_span_source_group + tenant: prod + name: test + direction: incoming + src_epg: epg1 + state: present + delegate_to: localhost + +- name: Delete a SPAN source + cisco.aci.aci_tenant_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + src_group: my_span_source_group + tenant: prod + name: test + state: absent + delegate_to: localhost + +- name: Query all SPAN sources + cisco.aci.aci_tenant_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific SPAN source + cisco.aci.aci_tenant_span_src_group_src: + host: apic + username: admin + password: SomeSecretPassword + name: test + 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 + +DIRECTION_MAP = {"incoming": "in", "outgoing": "out", "both": "both"} + + +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"]), + direction=dict(type="str", choices=["incoming", "outgoing", "both"]), + name=dict(type="str"), # Not required for querying all objects + src_ap=dict(type="str", aliases=["ap"]), + src_epg=dict(type="str", aliases=["epg"]), + src_group=dict(type="str"), # Not required for querying all objects + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name", "src_group", "tenant"]], + ["state", "present", ["name", "direction", "src_group", "tenant"]], + ], + required_together=[("src_ap", "src_epg")], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + direction = module.params.get("direction") + name = module.params.get("name") + src_ap = module.params.get("src_ap") + src_epg = module.params.get("src_epg") + src_group = module.params.get("src_group") + state = module.params.get("state") + tenant = module.params.get("tenant") + + 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="spanSrcGrp", + aci_rn="srcgrp-{0}".format(src_group), + module_object=src_group, + target_filter={"name": src_group}, + ), + subclass_2=dict( + aci_class="spanSrc", + aci_rn="src-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["spanRsSrcToEpg"], + ) + + aci.get_existing() + + if state == "present": + tdn = None + if src_epg: + tdn = "uni/tn-{0}/ap-{1}/epg-{2}".format(tenant, src_ap, src_epg) + + aci.payload( + aci_class="spanSrc", + class_config=dict(descr=description, name=name, dir=DIRECTION_MAP.get(direction)), + child_configs=[{"spanRsSrcToEpg": {"attributes": {"tDn": tdn}}}], + ) + + 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_tenant_span_src_group_to_dst_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_to_dst_group.py new file mode 100644 index 000000000..8ad77bdc3 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tenant_span_src_group_to_dst_group.py @@ -0,0 +1,295 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_tenant_span_src_group_to_dst_group +short_description: Bind SPAN source groups to destination groups (span:SpanLbl) +description: +- Bind SPAN source groups to associated destination groups on Cisco ACI fabrics. +options: + description: + description: + - The description for Span source group to destination group binding. + type: str + aliases: [ descr ] + dst_group: + description: + - The Span destination group to associate with the source group. + type: str + src_group: + description: + - The name of the Span source group. + 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 + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant), C(src_group), and C(dst_group) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_tenant_span_src_group), and M(cisco.aci.aci_tenant_span_dst_group) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_tenant_span_src_group +- module: cisco.aci.aci_tenant_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: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Bind SPAN source to destination group + cisco.aci.aci_tenant_span_src_group_to_dst_group: + host: apic + username: admin + password: SomeSecretPassword + src_group: my_span_source_group + dst_group: my_span_destination_group + tenant: prod + state: present + delegate_to: localhost + +- name: Unbind SPAN source to destination group + cisco.aci.aci_tenant_span_src_group_to_dst_group: + host: apic + username: admin + password: SomeSecretPassword + src_group: my_span_source_group + dst_group: my_span_destination_group + tenant: prod + state: absent + delegate_to: localhost + +- name: Query all SPAN source to destination group bindings + cisco.aci.aci_tenant_span_src_group_to_dst_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific SPAN source to destination group binding + cisco.aci.aci_tenant_span_src_group_to_dst_group: + host: apic + username: admin + password: SomeSecretPassword + src_group: my_span_source_group + dst_group: my_span_destination_group + tenant: prod + 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 + dst_group=dict(type="str"), # Not required for querying all objects + src_group=dict(type="str"), # Not required for querying all objects + 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", ["dst_group", "src_group", "tenant"]], + ["state", "present", ["dst_group", "src_group", "tenant"]], + ], + ) + + description = module.params.get("description") + dst_group = module.params.get("dst_group") + src_group = module.params.get("src_group") + 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="spanSrcGrp", + aci_rn="srcgrp-{0}".format(src_group), + module_object=src_group, + target_filter={"name": src_group}, + ), + subclass_2=dict( + aci_class="spanSpanLbl", + aci_rn="spanlbl-{0}".format(dst_group), + module_object=dst_group, + target_filter={"name": dst_group}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="spanSpanLbl", + class_config=dict( + descr=description, + name=dst_group, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="spanSpanLbl") + + 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_vlan_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool.py new file mode 100644 index 000000000..bd7d5dcef --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool.py @@ -0,0 +1,286 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Jacob McGill (@jmcgill298) +# Copyright: (c) 2018, Dag Wieers (@dagwieers) <dag@wieers.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_vlan_pool +short_description: Manage VLAN pools (fvns:VlanInstP) +description: +- Manage VLAN pools on Cisco ACI fabrics. +options: + pool_allocation_mode: + description: + - The method used for allocating VLANs to resources. + type: str + choices: [ dynamic, static] + aliases: [ allocation_mode, mode ] + description: + description: + - Description for the C(pool). + type: str + aliases: [ descr ] + pool: + description: + - The name of the pool. + type: str + aliases: [ name, pool_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 + +seealso: +- module: cisco.aci.aci_encap_pool +- module: cisco.aci.aci_vlan_pool_encap_block +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fvns:VlanInstP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add a new VLAN pool + cisco.aci.aci_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_allocation_mode: dynamic + description: Production VLANs + state: present + delegate_to: localhost + +- name: Remove a VLAN pool + cisco.aci.aci_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_allocation_mode: dynamic + state: absent + delegate_to: localhost + +- name: Query a VLAN pool + cisco.aci.aci_vlan_pool: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_allocation_mode: dynamic + state: query + delegate_to: localhost + register: query_result + +- name: Query all VLAN pools + cisco.aci.aci_vlan_pool: + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + pool=dict(type="str", aliases=["name", "pool_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + pool_allocation_mode=dict(type="str", aliases=["allocation_mode", "mode"], choices=["dynamic", "static"]), + 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", ["pool"]], + ["state", "present", ["pool"]], + ], + ) + + description = module.params.get("description") + pool = module.params.get("pool") + pool_allocation_mode = module.params.get("pool_allocation_mode") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + pool_name = pool + + # ACI Pool URL requires the allocation mode for vlan and vsan pools (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") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvnsVlanInstP", + aci_rn="infra/vlanns-{0}".format(pool_name), + module_object=pool, + target_filter={"name": pool}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvnsVlanInstP", + class_config=dict( + allocMode=pool_allocation_mode, + descr=description, + name=pool, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fvnsVlanInstP") + + 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_vlan_pool_encap_block.py b/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py new file mode 100644 index 000000000..18a38566a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vlan_pool_encap_block.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Jacob McGill (jmcgill298) +# Copyright: (c) 2018, Dag Wieers (dagwieers) <dag@wieers.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_vlan_pool_encap_block +short_description: Manage encap blocks assigned to VLAN pools (fvns:EncapBlk) +description: +- Manage VLAN encap blocks that are assigned to VLAN pools on Cisco ACI fabrics. +options: + allocation_mode: + description: + - The method used for allocating encaps to resources. + type: str + choices: [ dynamic, inherit, static] + aliases: [ mode ] + description: + description: + - Description for the pool encap block. + type: str + aliases: [ descr ] + pool: + description: + - The name of the pool that the encap block should be assigned to. + type: str + aliases: [ pool_name ] + pool_allocation_mode: + description: + - The method used for allocating encaps to resources. + type: str + choices: [ dynamic, static] + aliases: [ pool_mode ] + block_end: + description: + - The end of encap block. + type: int + aliases: [ end ] + block_name: + description: + - The name to give to the encap block. + type: str + aliases: [ name ] + block_start: + description: + - The start of the encap block. + type: int + aliases: [ start ] + 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(pool) must exist in order to add or delete a encap block. +seealso: +- module: cisco.aci.aci_encap_pool_range +- module: cisco.aci.aci_vlan_pool +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fvns:EncapBlk). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +- Dag Wieers (@dagwieers) +""" + +EXAMPLES = r""" +- name: Add a new VLAN encap block + cisco.aci.aci_vlan_pool_encap_block: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_allocation_mode: static + block_start: 20 + block_end: 50 + state: present + delegate_to: localhost + +- name: Remove a VLAN encap block + cisco.aci.aci_vlan_pool_encap_block: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_allocation_mode: static + block_start: 20 + block_end: 50 + state: absent + delegate_to: localhost + +- name: Query a VLAN encap block + cisco.aci.aci_vlan_pool_encap_block: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_allocation_mode: static + block_start: 20 + block_end: 50 + state: query + delegate_to: localhost + register: query_result + +- name: Query a VLAN pool for encap blocks + cisco.aci.aci_vlan_pool_encap_block: + host: apic + username: admin + password: SomeSecretPassword + pool: production + pool_allocation_mode: static + state: query + delegate_to: localhost + register: query_result + +- name: Query all VLAN encap blocks + cisco.aci.aci_vlan_pool_encap_block: + 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 + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + pool=dict(type="str", aliases=["pool_name"]), # Not required for querying all objects + block_name=dict(type="str", aliases=["name"]), # Not required for querying all objects + block_end=dict(type="int", aliases=["end"]), # Not required for querying all objects + block_start=dict(type="int", aliases=["start"]), # Not required for querying all objects + allocation_mode=dict(type="str", aliases=["mode"], choices=["dynamic", "inherit", "static"]), + description=dict(type="str", aliases=["descr"]), + pool_allocation_mode=dict(type="str", aliases=["pool_mode"], choices=["dynamic", "static"]), + 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", ["pool", "pool_allocation_mode", "block_end", "block_start"]], + ["state", "present", ["pool", "pool_allocation_mode", "block_end", "block_start"]], + ], + ) + + allocation_mode = module.params.get("allocation_mode") + description = module.params.get("description") + pool = module.params.get("pool") + pool_allocation_mode = module.params.get("pool_allocation_mode") + block_end = module.params.get("block_end") + block_name = module.params.get("block_name") + block_start = module.params.get("block_start") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + if block_end is not None: + encap_end = "vlan-{0}".format(block_end) + else: + encap_end = None + + if block_start is not None: + encap_start = "vlan-{0}".format(block_start) + else: + encap_start = None + + # Collect proper mo information + aci_block_mo = "from-[{0}]-to-[{1}]".format(encap_start, encap_end) + pool_name = pool + + # Validate block_end and block_start are valid for its respective encap type + for encap_id in block_end, block_start: + if encap_id is not None: + if not 1 <= encap_id <= 4094: + module.fail_json(msg="vlan pools must have 'block_start' and 'block_end' values between 1 and 4094") + + if block_end is not None and block_start is not None: + # Validate block_start is less than block_end + if block_start > block_end: + module.fail_json(msg="The 'block_start' must be less than or equal to the 'block_end'") + + elif block_end is None and block_start is None: + if block_name is None: + # Reset range managed object to None for aci util to properly handle query + 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") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvnsVlanInstP", + aci_rn="infra/vlanns-{0}".format(pool_name), + module_object=pool, + target_filter={"name": pool}, + ), + subclass_1=dict( + aci_class="fvnsEncapBlk", + aci_rn=aci_block_mo, + module_object=aci_block_mo, + target_filter={"from": encap_start, "to": encap_end, "name": block_name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvnsEncapBlk", + class_config={ + "allocMode": allocation_mode, + "descr": description, + "from": encap_start, + "name": block_name, + "to": encap_end, + "nameAlias": name_alias, + }, + ) + + aci.get_diff(aci_class="fvnsEncapBlk") + + 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_vmm_controller.py b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py new file mode 100644 index 000000000..b066d8b91 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py @@ -0,0 +1,372 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Manuel Widmer <mawidmer@cisco.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_vmm_controller +short_description: Manage VMM Controller for virtual domains profiles (vmm:CtrlrP) +description: +- Manage vCenter virtual domains on Cisco ACI fabrics. +options: + name: + description: + - Name of VMM Controller. + type: str + aliases: [] + controller_hostname: + description: + - Hostname or IP of the controller. + type: str + aliases: [] + dvs_version: + description: + - Version of the VMware DVS. + type: str + aliases: [] + choices: [ 'unmanaged', '5.1', '5.5', '6.0', '6.5', '6.6', '7.0' ] + stats_collection: + description: + - Whether stats collection is enabled. + type: str + choices: [ 'enabled', 'disabled' ] + default: disabled + aliases: [] + domain: + description: + - Name of the virtual domain profile. + type: str + aliases: [ domain_name, domain_profile ] + 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 + credentials: + description: + - Name of the VMM credentials to be used + type: str + inband_management_epg: + description: + - Name of the management EPG to be used by the controller. Only supports in-band management EPGs for now. + type: str + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str + datacenter: + description: + - Name of the data center, as seen in vCenter + type: str + vm_provider: + description: + - The VM platform for VMM Domains. + - Support for Kubernetes was added in ACI v3.0. + - Support for CloudFoundry, OpenShift and Red Hat was added in ACI v3.1. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_vmm_credential +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(vmm:DomP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Manuel Widmer (@lumean) +""" + +EXAMPLES = r""" +- name: Add controller to VMware VMM domain + cisco.aci.aci_vmm_controller: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + name: vCenter1 + controller_hostname: 10.1.1.1 + dvs_version: unmanaged + vm_provider: vmware + credentials: vCenterCredentials1 + datacenter: DC1 + state: present + +- name: Remove controller from VMware VMM domain + cisco.aci.aci_vmm_controller: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + name: vCenter1 + vm_provider: vmware + state: absent + +- name: Query a specific VMware VMM controller + cisco.aci.aci_vmm_controller: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + name: vCenter1 + vm_provider: vmware + state: query + delegate_to: localhost + register: query_result + +- name: Query all VMware VMM controller + cisco.aci.aci_vmm_controller: + host: apic + username: admin + password: SomeSecretPassword + vm_provider: vmware + 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 + +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + +VM_SCOPE_MAPPING = dict( + cloudfoundry="cloudfoundry", + kubernetes="kubernetes", + microsoft="MicrosoftSCVMM", + openshift="openshift", + openstack="openstack", + redhat="rhev", + vmware="vm", +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + 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"]), + 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"]), + credentials=dict(type="str"), + inband_management_epg=dict(type="str"), + name_alias=dict(type="str"), + datacenter=dict(type="str"), + vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["domain", "vm_provider", "name"]], + ["state", "present", ["domain", "vm_provider", "name"]], + ], + ) + + name = module.params.get("name") + controller_hostname = module.params.get("controller_hostname") + dvs_version = module.params.get("dvs_version") + stats_collection = module.params.get("stats_collection") + domain = module.params.get("domain") + state = module.params.get("state") + credentials = module.params.get("credentials") + inband_management_epg = module.params.get("inband_management_epg") + name_alias = module.params.get("name_alias") + datacenter = module.params.get("datacenter") + vm_provider = module.params.get("vm_provider") + + controller_class = "vmmCtrlrP" + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="vmmProvP", + aci_rn="vmmp-{0}".format(VM_PROVIDER_MAPPING.get(vm_provider)), + module_object=vm_provider, + target_filter={"name": vm_provider}, + ), + subclass_1=dict( + aci_class="vmmDomP", + aci_rn="dom-{0}".format(domain), + module_object=domain, + target_filter={"name": domain}, + ), + subclass_2=dict( + aci_class="vmmCtrlrP", + aci_rn="ctrlr-{0}".format(name), + module_object=name, + target_filter={"name": "name"}, + ), + child_classes=["vmmRsMgmtEPg", "vmmRsAcc"], + ) + + aci.get_existing() + + if state == "present": + children = list() + if inband_management_epg is not None: + children.append(dict(vmmRsMgmtEPg=dict(attributes=dict(tDn="uni/tn-mgmt/mgmtp-default/inb-{0}".format(inband_management_epg))))) + + if credentials is not None: + children.append( + dict(vmmRsAcc=dict(attributes=dict(tDn="uni/vmmp-{0}/dom-{1}/usracc-{2}".format(VM_PROVIDER_MAPPING.get(vm_provider), domain, credentials)))) + ) + + aci.payload( + aci_class=controller_class, + class_config=dict( + name=name, + hostOrIp=controller_hostname, + dvsVersion=dvs_version, + statsMode=stats_collection, + rootContName=datacenter, + nameAlias=name_alias, + scope=VM_SCOPE_MAPPING.get(vm_provider), + ), + child_configs=children, + ) + + aci.get_diff(aci_class=controller_class) + + 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_vmm_credential.py b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_credential.py new file mode 100644 index 000000000..8f23aabe5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_credential.py @@ -0,0 +1,319 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_vmm_credential +short_description: Manage virtual domain credential profiles (vmm:UsrAccP) +description: +- Manage virtual domain credential profiles on Cisco ACI fabrics. +options: + name: + description: + - Name of the credential profile. + type: str + aliases: [ credential_name, credential_profile ] + credential_password: + description: + - VMM controller password. + type: str + aliases: [] + credential_username: + description: + - VMM controller username. + type: str + aliases: [] + description: + description: + - Description for the tenant. + type: str + aliases: [ descr ] + domain: + description: + - Name of the virtual domain profile. + type: str + aliases: [ domain_name, domain_profile, 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 + vm_provider: + description: + - The VM platform for VMM Domains. + - Support for Kubernetes was added in ACI v3.0. + - Support for CloudFoundry, OpenShift and Red Hat was added in ACI v3.1. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- module: cisco.aci.aci_domain +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(vmm:DomP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jason Juenger (@jasonjuenger) +""" + +EXAMPLES = r""" +- name: Add credential to VMware VMM domain + cisco.aci.aci_vmm_credential: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + description: secure credential + name: vCenterCredential + credential_username: vCenterUsername + credential_password: vCenterPassword + vm_provider: vmware + state: present + +- name: Remove credential from VMware VMM domain + cisco.aci.aci_vmm_credential: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + name: myCredential + vm_provider: vmware + state: absent + +- name: Query a specific VMware VMM credential + cisco.aci.aci_vmm_credential: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + name: vCenterCredential + vm_provider: vmware + state: query + delegate_to: localhost + register: query_result + +- name: Query all VMware VMM credentials + cisco.aci.aci_vmm_credential: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + vm_provider: vmware + 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 + +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + + +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=["credential_name", "credential_profile"]), + credential_password=dict(type="str", no_log=True), + credential_username=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + domain=dict(type="str", aliases=["domain_name", "domain_profile"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["domain"]], + ["state", "present", ["domain"]], + ], + ) + + name = module.params.get("name") + credential_password = module.params.get("credential_password") + credential_username = module.params.get("credential_username") + description = module.params.get("description") + domain = module.params.get("domain") + state = module.params.get("state") + vm_provider = module.params.get("vm_provider") + name_alias = module.params.get("name_alias") + + credential_class = "vmmUsrAccP" + usracc_mo = "uni/vmmp-{0}/dom-{1}/usracc-{2}".format(VM_PROVIDER_MAPPING.get(vm_provider), domain, name) + usracc_rn = "vmmp-{0}/dom-{1}/usracc-{2}".format(VM_PROVIDER_MAPPING.get(vm_provider), domain, name) + + # Ensure that querying all objects works when only domain is provided + if name is None: + usracc_mo = None + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class=credential_class, + aci_rn=usracc_rn, + module_object=usracc_mo, + target_filter={"name": domain, "usracc": name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=credential_class, + class_config=dict( + descr=description, + name=name, + pwd=credential_password, + usr=credential_username, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class=credential_class) + + 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_vmm_uplink.py b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_uplink.py new file mode 100644 index 000000000..cc46f2778 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_uplink.py @@ -0,0 +1,272 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_vmm_uplink +short_description: Manage VMM uplinks (vmm:UplinkP) +description: +- Manage VMM Uplinks on Cisco ACI fabrics. +options: + domain: + description: + - Name of the VMM domain + type: str + uplink_id: + description: + - Numerical ID of the uplink + type: int + uplink_name: + description: + - Name of the uplink + 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 C(domain) used must exist before using this module in your playbook. + The M(cisco.aci.aci_domain) module can be used for this. +seealso: +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_vmm_uplink_container +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vmm:UplinkP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new uplink + cisco.aci.aci_vmm_uplink: + host: apic + username: admin + password: SomeSecretPassword + domain: my_vmm_domain + uplink_id: 1 + uplink_name: uplink1 + state: present + delegate_to: localhost + +- name: Delete uplink container + cisco.aci.aci_vmm_uplink: + host: apic + username: admin + password: SomeSecretPassword + domain: my_vmm_domain + uplink_id: 1 + state: absent + delegate_to: localhost + +- name: Query uplink container + cisco.aci.aci_vmm_uplink_container: + host: apic + username: admin + password: SomeSecretPassword + domain: my_vmm_domain + 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( + domain=dict(type="str"), + uplink_id=dict(type="int"), + uplink_name=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", ["uplink_id", "domain"]], + ["state", "present", ["uplink_id", "domain"]], + ], + ) + + domain = module.params.get("domain") + uplink_id = module.params.get("uplink_id") + uplink_name = module.params.get("uplink_name") + state = module.params.get("state") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="vmmProvP", + aci_rn="vmmp-VMware", + module_object="VMware", + target_filter={"name": "VMware"}, + ), + subclass_1=dict( + aci_class="vmmDomP", + aci_rn="dom-{0}".format(domain), + module_object=domain, + target_filter={"name": domain}, + ), + subclass_2=dict( + aci_class="vmmUplinkPCont", + aci_rn="uplinkpcont", + module_object="uplinkpcont", + target_filter={"rn": "uplinkpcont"}, + ), + subclass_3=dict( + aci_class="vmmUplinkP", + aci_rn="uplinkp-{0}".format(uplink_id), + module_object=uplink_id, + target_filter={"uplinkId": uplink_id}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vmmUplinkP", + class_config=dict( + uplinkId=uplink_id, + uplinkName=uplink_name, + ), + ) + + aci.get_diff(aci_class="vmmUplinkP") + + 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_vmm_uplink_container.py b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_uplink_container.py new file mode 100644 index 000000000..a32d53ba0 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_uplink_container.py @@ -0,0 +1,260 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_vmm_uplink_container +short_description: Manage VMM uplink containers (vmm:UplinkPCont) +description: +- Manage VMM Uplink containers on Cisco ACI fabrics. +- Individual uplinks within the container are managed using the M(cisco.aci.aci_vmm_uplink) module +options: + domain: + description: + - Name of the VMM domain + type: str + num_of_uplinks: + description: + - Number of uplinks in the container + type: int + 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 C(domain) used must exist before using this module in your playbook. + The M(cisco.aci.aci_domain) module can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_domain +- module: cisco.aci.aci_vrf +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vmm:UplinkPCont). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Add a new uplink container + cisco.aci.aci_vmm_uplink_container: + host: apic + username: admin + password: SomeSecretPassword + domain: my_vmm_domain + num_of_uplinks: 2 + state: present + delegate_to: localhost + +- name: Delete uplink container + cisco.aci.aci_vmm_uplink_container: + host: apic + username: admin + password: SomeSecretPassword + domain: my_vmm_domain + state: absent + delegate_to: localhost + +- name: Query uplink container + cisco.aci.aci_vmm_uplink_container: + host: apic + username: admin + password: SomeSecretPassword + domain: my_vmm_domain + 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( + domain=dict(type="str"), + num_of_uplinks=dict(type="int"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["domain"]], + ["state", "present", ["domain", "num_of_uplinks"]], + ], + ) + + domain = module.params.get("domain") + num_of_uplinks = module.params.get("num_of_uplinks") + state = module.params.get("state") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="vmmProvP", + aci_rn="vmmp-VMware", + module_object="VMware", + target_filter={"name": "VMware"}, + ), + subclass_1=dict( + aci_class="vmmDomP", + aci_rn="dom-{0}".format(domain), + module_object=domain, + target_filter={"name": domain}, + ), + subclass_2=dict( + aci_class="vmmUplinkPCont", + aci_rn="uplinkpcont", + module_object="uplinkpcont", + target_filter={"rn": "uplinkpcont"}, + ), + child_classes=["vmmUplinkP"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="vmmUplinkPCont", + class_config=dict( + numOfUplinks=num_of_uplinks, + ), + ) + + aci.get_diff(aci_class="vmmUplinkPCont") + + 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_vmm_vswitch_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_vswitch_policy.py new file mode 100644 index 000000000..79d8182ff --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_vswitch_policy.py @@ -0,0 +1,472 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Manuel Widmer <mawidmer@cisco.com> +# Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_vmm_vswitch_policy +short_description: Manage vSwitch policy for VMware virtual domains profiles (vmm:DomP) +description: +- Manage vSwitch policy for VMware VMM domains on Cisco ACI fabrics. +options: + port_channel_policy: + description: + - Name of the fabric access port-channel policy. + type: str + lldp_policy: + description: + - Name of the fabric access LLDP policy. + type: str + cdp_policy: + description: + - Name of the fabric access CDP policy. + type: str + mtu_policy: + description: + - VMWare only. + - Name of the fabric access MTU policy. + type: str + domain: + description: + - Name of the virtual domain profile. + type: str + aliases: [ domain_name, domain_profile ] + enhanced_lag: + description: + - List of enhanced LAG policies if vSwitch needs to be connected via VPC. + type: list + elements: dict + suboptions: + name: + description: + - Name of the enhanced Lag policy. + type: str + required: true + lacp_mode: + description: + - LACP port channel mode. + type: str + choices: [ active, passive ] + load_balancing_mode: + description: + - Load balancing mode of the port channel. + - See also https://pubhub.devnetcloud.com/media/apic-mim-ref-421/docs/MO-lacpEnhancedLagPol.html. + type: str + choices: + - dst-ip + - dst-ip-l4port + - dst-ip-vlan + - dst-ip-l4port-vlan + - dst-mac + - dst-l4port + - src-ip + - src-ip-l4port + - src-ip-vlan + - src-ip-l4port-vlan + - src-mac + - src-l4port + - src-dst-ip + - src-dst-ip-l4port + - src-dst-ip-vlan + - src-dst-ip-l4port-vlan + - src-dst-mac + - src-dst-l4port + - src-port-id + - vlan + number_uplinks: + description: + - Number of uplinks, must be between 2 and 8. + type: int + stp_policy: + description: + - SCVMM only. + - Name of the STP policy. + type: str + netflow_exporter: + description: + - Parameters for the netflow exporter policy + type: dict + suboptions: + name: + description: + - Name of the netflow exporter policy + type: str + required: true + active_flow_timeout: + description: + - Specifies the delay in seconds that NetFlow waits after the active flow is initiated, after which NetFlow sends the collected data. + - The range is from 60 to 3600. The default value is 60 + type: int + idle_flow_timeout: + description: + - Specifies the delay in seconds that NetFlow waits after the idle flow is initiated, after which NetFlow sends the collected data. + - The range is from 10 to 600. The default value is 15. + type: int + sampling_rate: + description: + - (VDS only) Specifies how many packets that NetFlow will drop after every collected packet. + If you specify a value of 0, then NetFlow does not drop any packets. + - The range is from 0 to 1000. The default value is 0. + type: int + 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 + vm_provider: + description: + - The VM platform for VMM Domains. + - Support for Kubernetes was added in ACI v3.0. + - Support for CloudFoundry, OpenShift and Red Hat was added in ACI v3.1. + type: str + choices: [ cloudfoundry, kubernetes, microsoft, openshift, openstack, redhat, vmware ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- module: cisco.aci.aci_domain +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(vmm:DomP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Manuel Widmer (@lumean) +- Anvitha Jain (@anvitha-jain) +""" + +EXAMPLES = r""" +- name: Add a vSwitch policy with LLDP + cisco.aci.aci_vmm_vswitch_policy: + host: apic + username: admin + password: SomeSecretPassword + lldp_policy: LLDP_ENABLED + domain: vmware_dom + vm_provider: vmware + state: present + +- name: Add a vSwitch policy with link aggregation + cisco.aci.aci_vmm_vswitch_policy: + host: apic + username: admin + password: SomeSecretPassword + port_channel_policy: LACP_ACTIVE + lldp_policy: LLDP_ENABLED + domain: vmware_dom + vm_provider: vmware + enhanced_lag: + - name: my_lacp_uplink + lacp_mode: active + load_balancing_mode: src-dst-ip + number_uplinks: 2 + state: present + +- name: Remove vSwitch Policy from VMware VMM domain + cisco.aci.aci_vmm_vswitch_policy: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + vm_provider: vmware + state: absent + +- name: Query the vSwitch policy of the VMWare domain + cisco.aci.aci_vmm_vswitch_policy: + host: apic + username: admin + password: SomeSecretPassword + domain: vmware_dom + vm_provider: vmware + 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, enhanced_lag_spec, netflow_spec +from ansible_collections.cisco.aci.plugins.module_utils.aci import aci_annotation_spec, aci_owner_spec + +# via UI vSwitch Policy can only be added for VMware and Microsoft vmm domains +# behavior for other domains is currently untested. +VM_PROVIDER_MAPPING = dict( + cloudfoundry="CloudFoundry", + kubernetes="Kubernetes", + microsoft="Microsoft", + openshift="OpenShift", + openstack="OpenStack", + redhat="Redhat", + vmware="VMware", +) + +# enhanced_lag_spec = dict( +# name=dict(type='str', required=True), +# lacp_mode=dict(type='str', choices=['active', 'passive']), +# load_balancing_mode=dict( +# type='str', +# choices=['dst-ip', 'dst-ip-l4port', 'dst-ip-vlan', 'dst-ip-l4port-vlan', 'dst-mac', 'dst-l4port', +# 'src-ip', 'src-ip-l4port', 'src-ip-vlan', 'src-ip-l4port-vlan', 'src-mac', 'src-l4port', +# 'src-dst-ip', 'src-dst-ip-l4port', 'src-dst-ip-vlan', 'src-dst-ip-l4port-vlan', 'src-dst-mac', +# 'src-dst-l4port', 'src-port-id', 'vlan']), +# number_uplinks=dict(type='int'), +# ) + +# netflow_spec = dict( +# name=dict(type='str', required=True), +# active_flow_timeout=dict(type='int'), +# idle_flow_timeout=dict(type='int'), +# sampling_rate=dict(type='int'), +# ) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + port_channel_policy=dict(type="str"), + lldp_policy=dict(type="str"), + cdp_policy=dict(type="str"), + mtu_policy=dict(type="str"), + stp_policy=dict(type="str"), + enhanced_lag=dict(type="list", elements="dict", options=enhanced_lag_spec()), + netflow_exporter=dict(type="dict", options=netflow_spec()), + domain=dict(type="str", aliases=["domain_name", "domain_profile"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + vm_provider=dict(type="str", choices=list(VM_PROVIDER_MAPPING.keys())), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["domain", "vm_provider"]], + ["state", "present", ["domain", "vm_provider"]], + ], + ) + + port_channel_policy = module.params.get("port_channel_policy") + lldp_policy = module.params.get("lldp_policy") + cdp_policy = module.params.get("cdp_policy") + mtu_policy = module.params.get("mtu_policy") + stp_policy = module.params.get("stp_policy") + netflow_exporter = module.params.get("netflow_exporter") + enhanced_lag = module.params.get("enhanced_lag") + domain = module.params.get("domain") + state = module.params.get("state") + vm_provider = module.params.get("vm_provider") + + aci = ACIModule(module) + vswitch_class = "vmmVSwitchPolicyCont" + + child_classes = ["vmmRsVswitchOverrideLldpIfPol", "vmmRsVswitchOverrideLacpPol", "vmmRsVswitchOverrideCdpIfPol", "lacpEnhancedLagPol"] + if mtu_policy is not None: + child_classes.append("vmmRsVswitchOverrideMtuPol") + + if stp_policy is not None: + child_classes.append("vmmRsVswitchOverrideStpPol") + + if isinstance(netflow_exporter, dict): + child_classes.append("vmmRsVswitchExporterPol") + + aci.construct_url( + root_class=dict( + aci_class="vmmProvP", + aci_rn="vmmp-{0}".format(VM_PROVIDER_MAPPING.get(vm_provider)), + module_object=vm_provider, + target_filter={"name": vm_provider}, + ), + subclass_1=dict( + aci_class="vmmDomP", + aci_rn="dom-{0}".format(domain), + module_object=domain, + target_filter={"name": domain}, + ), + subclass_2=dict( + aci_class="vmmVSwitchPolicyCont", + aci_rn="vswitchpolcont", + module_object="vswitchpolcont", + target_filter={"name": "vswitchpolcont"}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + children = list() + + if port_channel_policy is not None: + children.append(dict(vmmRsVswitchOverrideLacpPol=dict(attributes=dict(tDn="uni/infra/lacplagp-{0}".format(port_channel_policy))))) + + if lldp_policy is not None: + children.append(dict(vmmRsVswitchOverrideLldpIfPol=dict(attributes=dict(tDn="uni/infra/lldpIfP-{0}".format(lldp_policy))))) + + if cdp_policy is not None: + children.append(dict(vmmRsVswitchOverrideCdpIfPol=dict(attributes=dict(tDn="uni/infra/cdpIfP-{0}".format(cdp_policy))))) + + if mtu_policy is not None: + children.append(dict(vmmRsVswitchOverrideMtuPol=dict(attributes=dict(tDn="uni/fabric/l2pol-{0}".format(mtu_policy))))) + + if stp_policy is not None: + children.append(dict(vmmRsVswitchOverrideStpPol=dict(attributes=dict(tDn="uni/infra/ifPol-{0}".format(stp_policy))))) + + if isinstance(netflow_exporter, dict): + children.append( + dict( + vmmRsVswitchExporterPol=dict( + attributes=dict( + tDn="uni/infra/vmmexporterpol-{0}".format(netflow_exporter["name"]), + activeFlowTimeOut=netflow_exporter["active_flow_timeout"], + idleFlowTimeOut=netflow_exporter["idle_flow_timeout"], + samplingRate=netflow_exporter["sampling_rate"], + ) + ) + ) + ) + + if isinstance(enhanced_lag, list): + for lag_dict in enhanced_lag: + children.append( + dict( + lacpEnhancedLagPol=dict( + attributes=dict( + name=lag_dict["name"], + mode=lag_dict["lacp_mode"], + lbmode=lag_dict["load_balancing_mode"], + numLinks=lag_dict["number_uplinks"], + ) + ) + ) + ) + + aci.payload(aci_class=vswitch_class, class_config=dict(rn="vswitchpolcont"), child_configs=children) + + aci.get_diff(aci_class=vswitch_class) + + 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_vrf.py b/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py new file mode 100644 index 000000000..49c0c9fc1 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vrf.py @@ -0,0 +1,328 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 +short_description: Manage contexts or VRFs (fv:Ctx) +description: +- Manage contexts or VRFs on Cisco ACI fabrics. +- Each context is a private network associated to a tenant, i.e. VRF. +- Enable Preferred Groups for VRF +options: + tenant: + description: + - The name of the Tenant the VRF should belong to. + type: str + aliases: [ tenant_name ] + vrf: + description: + - The name of the VRF. + type: str + aliases: [ context, name, vrf_name ] + policy_control_direction: + description: + - Determines if the policy should be enforced by the fabric on ingress or egress. + type: str + choices: [ egress, ingress ] + policy_control_preference: + description: + - Determines if the fabric should enforce contract policies to allow routing and packet forwarding. + type: str + choices: [ enforced, unenforced ] + description: + description: + - The description for the VRF. + 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 + preferred_group: + description: + - Enables preferred groups for the VRF under vzAny + type: str + choices: [enabled, disabled] + match_type: + description: + - Configures match type for contracts under vzAny + type: str + choices: [ all, at_least_one, at_most_one, none] +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(fv:Ctx). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Jacob McGill (@jmcgill298) +""" + +EXAMPLES = r""" +- name: Add a new VRF to a tenant + cisco.aci.aci_vrf: + host: apic + username: admin + password: SomeSecretPassword + vrf: vrf_lab + tenant: lab_tenant + descr: Lab VRF + policy_control_preference: enforced + policy_control_direction: ingress + state: present + delegate_to: localhost + +- name: Remove a VRF for a tenant + cisco.aci.aci_vrf: + host: apic + username: admin + password: SomeSecretPassword + vrf: vrf_lab + tenant: lab_tenant + state: absent + delegate_to: localhost + +- name: Query a VRF of a tenant + cisco.aci.aci_vrf: + host: apic + username: admin + password: SomeSecretPassword + vrf: vrf_lab + tenant: lab_tenant + state: query + delegate_to: localhost + register: query_result + +- name: Query all VRFs + cisco.aci.aci_vrf: + 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 + +MATCH_TYPE_MAPPING = dict( + all="All", + at_least_one="AtleastOne", + at_most_one="AtmostOne", + none="None", +) + + +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 + description=dict(type="str", aliases=["descr"]), + policy_control_direction=dict(type="str", choices=["egress", "ingress"]), + policy_control_preference=dict(type="str", choices=["enforced", "unenforced"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + 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"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["tenant", "vrf"]], + ["state", "present", ["tenant", "vrf"]], + ], + ) + + description = module.params.get("description") + policy_control_direction = module.params.get("policy_control_direction") + policy_control_preference = module.params.get("policy_control_preference") + state = module.params.get("state") + tenant = module.params.get("tenant") + vrf = module.params.get("vrf") + name_alias = module.params.get("name_alias") + preferred_group = module.params.get("preferred_group") + match_type = module.params.get("match_type") + + if match_type is not None: + match_type = MATCH_TYPE_MAPPING[match_type] + + 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}, + ), + child_classes=["vzAny"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvCtx", + class_config=dict( + descr=description, + pcEnfDir=policy_control_direction, + pcEnfPref=policy_control_preference, + name=vrf, + nameAlias=name_alias, + ), + child_configs=[ + dict(vzAny=dict(attributes=dict(prefGrMemb=preferred_group, matchT=match_type))), + ], + ) + + aci.get_diff(aci_class="fvCtx") + + 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_vzany_to_contract.py b/ansible_collections/cisco/aci/plugins/modules/aci_vzany_to_contract.py new file mode 100644 index 000000000..f2632307c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vzany_to_contract.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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_vzany_to_contract +short_description: Attach contracts to vzAny (vz:RsAnyToProv, vz:RsAnyToCons, vz:RsAnyToConsIf) +description: +- Bind contracts to vzAny on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] + vrf: + description: + - The name of the VRF. + type: str + aliases: [ context, vrf_name ] + contract: + description: + - The name of the contract or contract interface. + type: str + aliases: [ contract_name ] + type: + description: + - Determines if this is a provided or consumed contract or a consumed contract interface. + type: str + aliases: [ contract_type ] + required: true + choices: [ provider, consumer, interface ] + 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: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_vrf +- module: cisco.aci.aci_contract +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(fv:RsCons) and B(fv:RsProv). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Marcel Zehnder (@maercu) +""" + +EXAMPLES = r""" +- name: Add a new contract to vzAny + cisco.aci.aci_vzany_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: vzatest + vrf: vzatest + contract: vzatest_http + type: provider + state: present + delegate_to: localhost + +- name: Remove an existing contract from vzAny + cisco.aci.aci_vzany_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: vzatest + vrf: vzatest + contract: vzatest_http + type: provider + state: absent + delegate_to: localhost + +- name: Query a specific contract to vzAny binding + cisco.aci.aci_vzany_to_contract: + host: apic + username: admin + password: SomeSecretPassword + tenant: vzatest + vrf: vzatest + contract: vzatest_http + type: provider + state: query + delegate_to: localhost + register: query_result + +- name: Query all provider contract to vzAny bindings + cisco.aci.aci_vzany_to_contract: + host: apic + username: admin + password: SomeSecretPassword + type: provider + 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_CLASS_MAPPING = dict( + provider={"class": "vzRsAnyToProv", "rn": "rsanyToProv-", "target_attribute": "tnVzBrCPName"}, + consumer={"class": "vzRsAnyToCons", "rn": "rsanyToCons-", "target_attribute": "tnVzBrCPName"}, + interface={"class": "vzRsAnyToConsIf", "rn": "rsanyToConsIf-", "target_attribute": "tnVzCPIfName"}, +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + vrf=dict(type="str", aliases=["context", "vrf_name"]), + contract=dict(type="str", aliases=["contract_name"]), + type=dict(type="str", required=True, choices=["provider", "consumer", "interface"], aliases=["contract_type"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[["state", "absent", ["contract", "vrf", "tenant"]], ["state", "present", ["contract", "vrf", "tenant"]]], + ) + + tenant = module.params.get("tenant") + vrf = module.params.get("vrf") + contract = module.params.get("contract") + type = module.params.get("type") + state = module.params.get("state") + + aci_class = ACI_CLASS_MAPPING[type]["class"] + aci_rn = ACI_CLASS_MAPPING[type]["rn"] + aci_target_attribute = ACI_CLASS_MAPPING[type]["target_attribute"] + + 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="vzAny", aci_rn="any", module_object="any", target_filter={"name": "any"}), + subclass_3=dict(aci_class=aci_class, aci_rn="{0}{1}".format(aci_rn, contract), module_object=contract, target_filter={aci_target_attribute: contract}), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class=aci_class, class_config={aci_target_attribute: contract}) + + aci.get_diff(aci_class=aci_class) + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() |