diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-26 04:06:02 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-26 04:06:02 +0000 |
commit | e3eb94c23206603103f3c4faec6c227f59a1544c (patch) | |
tree | f2639459807ba88f55fc9c54d745bd7075d7f15c /ansible_collections/cisco/aci/plugins | |
parent | Releasing progress-linux version 9.4.0+dfsg-1~progress7.99u1. (diff) | |
download | ansible-e3eb94c23206603103f3c4faec6c227f59a1544c.tar.xz ansible-e3eb94c23206603103f3c4faec6c227f59a1544c.zip |
Merging upstream version 9.5.1+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/aci/plugins')
153 files changed, 26079 insertions, 539 deletions
diff --git a/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py b/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py index e6b18a289..aaedff669 100644 --- a/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py +++ b/ansible_collections/cisco/aci/plugins/doc_fragments/aci.py @@ -2,6 +2,7 @@ # Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com> # Copyright: (c) 2017, Swetha Chunduri (@schunduri) +# Copyright: (c) 2024, Samita Bhattacharjee (@samiib) <samitab@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 @@ -94,6 +95,15 @@ options: - 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 + suppress_verification: + description: + - If C(true), a verifying GET will not be sent after a POST update to APIC. + - If the value is not specified in the task, the value of environment variable C(ACI_NO_VERIFICATION) will be used instead. + - The default value is C(false). + - WARNING - This causes the current return value to be set to the proposed value. + - The current object including default values will be unverifiable in a single task. + type: bool + aliases: [ no_verification, no_verify, suppress_verify ] seealso: - ref: aci_guide description: Detailed information on how to manage your ACI infrastructure using Ansible. diff --git a/ansible_collections/cisco/aci/plugins/filter/listify.py b/ansible_collections/cisco/aci/plugins/filter/listify.py new file mode 100644 index 000000000..2bb090b25 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/filter/listify.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2017, Ramses Smeyers <rsmeyers@cisco.com> +# Copyright: (c) 2023, Shreyas Srish <ssrish@cisco.com> +# Copyright: (c) 2024, Akini Ross <akinross@cisco.com> +# 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": "certified"} + +DOCUMENTATION = r""" + name: aci_listify + short_description: Flattens the nested dictionaries representing the ACI model data. + description: + - This filter flattens and transforms the input data into a list. + - See the Examples section below. + options: + data: + description: This option represents the ACI model data which is a list of dictionaries or a dictionary with any level of nesting data. + type: raw + required: True + keys: + description: Comma separated keys of type string denoting the ACI objects. + required: True +""" + +EXAMPLES = r""" +- name: Set vars + ansible.builtin.set_fact: + data: + tenant: + - name: ansible_test + description: Created using listify + app: + - name: app_test + epg: + - name: web + bd: web_bd + - name: app + bd: app_bd + bd: + - name: bd_test + subnet: + - name: 10.10.10.1 + mask: 24 + scope: + - public + - shared + vrf: vrf_test + - name: bd_test2 + subnet: + - name: 20.20.20.1 + mask: 24 + scope: public + vrf: vrf_test + vrf: + - name: vrf_test + policies: + protocol: + bfd: + - name: BFD-ON + description: Enable BFD + admin_state: enabled + detection_multiplier: 3 + min_tx_interval: 50 + min_rx_interval: 50 + echo_rx_interval: 50 + echo_admin_state: enabled + sub_interface_optimization_state: enabled + ospf: + interface: + - name: OSPF-P2P-IntPol + network_type: p2p + priority: 1 + - name: OSPF-Broadcast-IntPol + network_type: bcast + priority: 1 + +- name: Create tenants + cisco.aci.aci_tenant: + host: apic + username: admin + password: SomeSecretPassword + tenant: '{{ item.tenant_name }}' + description: '{{ item.tenant_description }}' + with_items: '{{ data|cisco.aci.aci_listify("tenant") }}' + +- name: Create VRFs + cisco.aci.aci_vrf: + host: apic + username: admin + password: SomeSecretPassword + tenant: '{{ item.tenant_name }}' + vrf_name: '{{ item.tenant_vrf_name }}' + with_items: '{{ data|cisco.aci.aci_listify("tenant","vrf") }}' + +- name: Create BDs + cisco.aci.aci_bd: + host: apic + username: admin + password: SomeSecretPassword + tenant: '{{ item.tenant_name }}' + vrf: '{{ item.tenant_bd_vrf }}' + bd: '{{ item.tenant_bd_name }}' + enable_routing: yes + with_items: '{{ data|cisco.aci.aci_listify("tenant","bd") }}' + +- name: Create BD subnets + cisco.aci.aci_bd_subnet: + host: apic + username: admin + password: SomeSecretPassword + tenant: '{{ item.tenant_name }}' + bd: '{{ item.tenant_bd_name }}' + gateway: '{{ item.tenant_bd_subnet_name }}' + mask: '{{ item.tenant_bd_subnet_mask }}' + scope: '{{ item.tenant_bd_subnet_scope }}' + with_items: '{{ data|cisco.aci.aci_listify("tenant","bd","subnet") }}' + +- name: Create APs + cisco.aci.aci_ap: + host: apic + username: admin + password: SomeSecretPassword + tenant: '{{ item.tenant_name }}' + app_profile: '{{ item.tenant_app_name }}' + with_items: '{{ data|cisco.aci.aci_listify("tenant","app") }}' + +- name: Create EPGs + cisco.aci.aci_epg: + host: apic + username: admin + password: SomeSecretPassword + tenant: '{{ item.tenant_name }}' + app_profile: '{{ item.tenant_app_name }}' + epg: '{{ item.tenant_app_epg_name }}' + bd: '{{ item.tenant_app_epg_bd }}' + with_items: '{{ data|cisco.aci.aci_listify("tenant","app","epg") }}' +""" + +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 +""" + + +# This function takes a dictionary and a series of keys, +# and returns a list of dictionaries using recursive helper function 'listify_worker' +def listify(d, *keys): + return list(listify_worker(d, keys, 0, {}, "")) + + +# This function walks through a dictionary 'd', depth-first, +# using the keys provided, and generates a new dictionary for each key:value pair encountered +def listify_worker(d, keys, depth, cache, prefix): + # The prefix in the code is used to store the path of keys traversed in the nested dictionary, + # which helps to generate unique keys for each value when flattening the dictionary. + prefix += keys[depth] + "_" + + if keys[depth] in d: + for item in d[keys[depth]]: + cache_work = cache.copy() + if isinstance(item, dict): + for k, v in item.items(): + if isinstance(v, list) and all(isinstance(x, (str, int, float, bool, bytes)) for x in v) or not isinstance(v, (dict, list)): + # The cache in this code is a temporary storage that holds key-value pairs as the function navigates through the nested dictionary. + # It helps to generate the final output by remembering the traversed path in each recursive call. + cache_key = prefix + k + cache_value = v + cache_work[cache_key] = cache_value + # If we're at the deepest level of keys + if len(keys) - 1 == depth: + yield cache_work + else: + for k, v in item.items(): + if k == keys[depth + 1] and isinstance(v, (dict, list)): + for result in listify_worker({k: v}, keys, depth + 1, cache_work, prefix): + yield result + # Conditional to support nested dictionaries which are detected by item is string + elif isinstance(item, str) and isinstance(d[keys[depth]], dict): + for result in listify_worker({item: d[keys[depth]][item]}, keys, depth + 1, cache_work, prefix): + yield result + + +class FilterModule(object): + """Ansible core jinja2 filters""" + + def filters(self): + return { + "aci_listify": listify, + } diff --git a/ansible_collections/cisco/aci/plugins/httpapi/aci.py b/ansible_collections/cisco/aci/plugins/httpapi/aci.py index a0474576a..dfab2c14e 100644 --- a/ansible_collections/cisco/aci/plugins/httpapi/aci.py +++ b/ansible_collections/cisco/aci/plugins/httpapi/aci.py @@ -102,19 +102,6 @@ class HttpApi(HttpApiBase): exc_login.path = path raise - def logout(self): - method = "POST" - path = "/api/aaaLogout.json" - payload = {"aaaUser": {"attributes": {"name": self.connection.get_option("remote_user")}}} - data = json.dumps(payload) - try: - response, response_data = self.connection.send(path, data, method=method) - except Exception as exc_logout: - msg = "Error on attempt to logout from APIC. {0}".format(exc_logout) - raise ConnectionError(self._return_info("", method, path, msg)) - self.connection._auth = None - self._verify_response(response, method, path, response_data) - def set_parameters(self): connection_parameters = {} for key in CONNECTION_KEYS: diff --git a/ansible_collections/cisco/aci/plugins/module_utils/aci.py b/ansible_collections/cisco/aci/plugins/module_utils/aci.py index 9c6e2db2d..88b49cd6e 100644 --- a/ansible_collections/cisco/aci/plugins/module_utils/aci.py +++ b/ansible_collections/cisco/aci/plugins/module_utils/aci.py @@ -15,6 +15,8 @@ # Copyright: (c) 2020, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> # Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # Copyright: (c) 2023, Shreyas Srish (@shrsr) <ssrish@cisco.com> +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# Copyright: (c) 2024, Samita Bhattacharjee (@samiib) <samitab@cisco.com> # All rights reserved. # Redistribution and use in source and binary forms, with or without modification, @@ -134,6 +136,7 @@ def aci_argument_spec(): use_ssl=dict(type="bool", fallback=(env_fallback, ["ACI_USE_SSL"])), validate_certs=dict(type="bool", fallback=(env_fallback, ["ACI_VALIDATE_CERTS"])), output_path=dict(type="str", fallback=(env_fallback, ["ACI_OUTPUT_PATH"])), + suppress_verification=dict(type="bool", aliases=["no_verification", "no_verify", "suppress_verify"], fallback=(env_fallback, ["ACI_NO_VERIFICATION"])), ) @@ -320,6 +323,64 @@ def integrate_url(httpapi_url, local_path): return {"protocol": parse_url.scheme, "host": parse_url.netloc, "path": local_path} +def action_rule_set_comm_spec(): + return dict( + community=dict(type="str"), + criteria=dict(type="str", choices=["append", "none", "replace"]), + ) + + +def action_rule_set_dampening_spec(): + return dict( + half_life=dict(type="int"), + max_suppress_time=dict(type="int"), + reuse=dict(type="int"), + suppress=dict(type="int"), + ) + + +def associated_netflow_exporter_epg_spec(): + return dict( + tenant=dict(type="str"), + ap=dict(type="str"), + epg=dict(type="str"), + ) + + +def associated_netflow_exporter_extepg_spec(): + return dict( + tenant=dict(type="str"), + l3out=dict(type="str"), + extepg=dict(type="str"), + ) + + +def associated_netflow_exporter_vrf_spec(): + return dict( + tenant=dict(type="str"), + vrf=dict(type="str"), + ) + + +def pim_interface_profile_spec(): + return dict( + tenant=dict(type="str", aliases=["tenant_name"]), + pim=dict(type="str", aliases=["pim_interface_policy", "name"]), + ) + + +def igmp_interface_profile_spec(): + return dict(tenant=dict(type="str", aliases=["tenant_name"]), igmp=dict(type="str", aliases=["igmp_interface_policy", "name"])) + + +def storm_control_policy_rate_spec(): + return dict( + rate=dict(type="str"), + burst_rate=dict(type="str"), + rate_type=dict(type="str", choices=["percentage", "pps"], required=True), + ) + + class ACIModule(object): def __init__(self, module): self.module = module @@ -356,6 +417,9 @@ class ACIModule(object): self.imdata = None self.totalCount = None + # get no verify flag + self.suppress_verification = self.params.get("suppress_verification") + # Ensure protocol is set self.define_protocol() @@ -779,8 +843,10 @@ class ACIModule(object): subclass_3=None, subclass_4=None, subclass_5=None, + subclass_6=None, child_classes=None, config_only=True, + rsp_subtree="full", ): """ This method is used to retrieve the appropriate URL path and filter_string to make the request to the APIC. @@ -796,6 +862,7 @@ class ACIModule(object): :type subclass_3: dict :type subclass_4: dict :type subclass_5: dict + :type subclass_6: dict :type child_classes: list :return: The path and filter_string needed to build the full URL. """ @@ -806,7 +873,9 @@ class ACIModule(object): else: self.child_classes = set(child_classes) - if subclass_5 is not None: + if subclass_6 is not None: + self._construct_url_7(root_class, subclass_1, subclass_2, subclass_3, subclass_4, subclass_5, subclass_6, config_only) + elif 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) @@ -828,10 +897,12 @@ class ACIModule(object): # Append child_classes to filter_string if filter string is empty self.update_qs( { - "rsp-subtree": "full", + "rsp-subtree": rsp_subtree, "rsp-subtree-class": ",".join(sorted(self.child_classes)), } ) + elif rsp_subtree == "children": + self.update_qs({"rsp-subtree": rsp_subtree}) def _construct_url_1(self, obj, config_only=True): """ @@ -1077,7 +1148,7 @@ class ACIModule(object): 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. + This method is used by construct_url when the object is the fifth-level class. """ root_rn = root.get("aci_rn") root_obj = root.get("module_object") @@ -1145,6 +1216,85 @@ class ACIModule(object): # 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 _construct_url_7(self, root, quin, quad, ter, sec, parent, obj, config_only=True): + """ + This method is used by construct_url when the object is the sixth-level class. + """ + root_rn = root.get("aci_rn") + root_obj = root.get("module_object") + quin_rn = quin.get("aci_rn") + quin_obj = quin.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}/{6}.json".format(root_rn, quin_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 quin_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 quin_filter, parent and obj_filter + self.update_qs({"rsp-subtree-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}/{1}.json".format(root_rn, quin_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 quin_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}/{2}.json".format(root_rn, quin_rn, quad_rn) + # NOTE: No need to select by quad_filter + # self.update_qs({'query-target-filter': self.build_filter(quin_class, quin_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}/{3}.json".format(root_rn, quin_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}/{4}.json".format(root_rn, quin_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}/{5}.json".format(root_rn, quin_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}/{6}.json".format(root_rn, quin_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 @@ -1161,7 +1311,7 @@ class ACIModule(object): self.result["changed"] = True self.method = "DELETE" - def get_diff(self, aci_class): + def get_diff(self, aci_class, required_properties=None): """ 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 @@ -1183,6 +1333,8 @@ class ACIModule(object): # 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 + if required_properties and isinstance(required_properties, dict): + config.update(required_properties) config = {aci_class: {"attributes": config}} # check for updates to child configs and update new config dictionary @@ -1369,23 +1521,30 @@ class ACIModule(object): # 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) + children = self.handle_child_configs(child_configs) if children: self.proposed[aci_class].update(dict(children=children)) + def handle_child_configs(self, child_configs): + children = [] + for child in child_configs: + child_copy = deepcopy(child) + has_value = False + for class_name in child_copy.keys(): + for attribute, value in child_copy[class_name]["attributes"].items(): + if value is None: + child[class_name]["attributes"].pop(attribute) + else: + child[class_name]["attributes"][attribute] = str(value) + has_value = True + if child_copy[class_name].get("children") is not None: + has_value = True + child[class_name]["children"] = self.handle_child_configs(child_copy[class_name]["children"]) + if has_value: + children.append(child) + return children + def post_config(self, parent_class=None): """ This method is used to handle the logic when the modules state is equal to present. The method only pushes a change if @@ -1438,7 +1597,15 @@ class ACIModule(object): if "state" in self.params: self.original = self.existing if self.params.get("state") in ("absent", "present"): - self.get_existing() + if self.suppress_verification: + if self.result["changed"]: + self.result["current_verified"] = False + self.existing = [self.proposed] + else: + self.result["current_verified"] = True + # exisiting already equals the previous + else: + self.get_existing() # if self.module._diff and self.original != self.existing: # self.result['diff'] = dict( diff --git a/ansible_collections/cisco/aci/plugins/module_utils/annotation_unsupported.py b/ansible_collections/cisco/aci/plugins/module_utils/annotation_unsupported.py new file mode 100644 index 000000000..6e34889fb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/module_utils/annotation_unsupported.py @@ -0,0 +1,853 @@ +# Code generated by release_script GitHub action; DO NOT EDIT MANUALLY. +ANNOTATION_UNSUPPORTED = [ + "topRoot", + "monPol", + "monATarget", + "monTarget", + "moTopProps", + "moModifiable", + "moOwnable", + "moResolvable", + "moASubj", + "actionACont", + "taskExec", + "namingNamedObject", + "namingNamedIdentifiedObject", + "conditionInfo", + "conditionRetP", + "conditionRecord", + "conditionLoggable", + "faultInfo", + "relnInst", + "relnTo", + "relnFrom", + "statsItem", + "statsAColl", + "statsAThrP", + "relnTaskRef", + "hvsNode", + "qosACong", + "qosAQueue", + "qosABuffer", + "qosASched", + "qosClassification", + "qosADscpClass", + "qosADot1PClass", + "lacpALagPol", + "pconsRef", + "eventARetP", + "faultAThrValue", + "faultARetP", + "configABackupP", + "dnsAProfile", + "dnsAProv", + "dnsADomain", + "dnsALbl", + "sysdebugRepository", + "sysdebugFile", + "sysdebugLogBehavior", + "firmwareSource", + "firmwareAFwStatusCont", + "firmwareARunning", + "firmwareAFwP", + "maintAMaintP", + "maintUserNotif", + "fabricComp", + "fabricANode", + "fabricALink", + "ruleDefinition", + "ruleItem", + "ruleRequirement", + "ruleSizeRequirement", + "stpAIfPol", + "haHaTest", + "rtctrlAMatchRule", + "rtctrlAMatchIpRule", + "rtctrlAMatchRtType", + "rtctrlASubnet", + "rtctrlAAttrP", + "rtctrlASetRule", + "rtctrlASetTag", + "rtctrlASetComm", + "rtctrlASetRtMetric", + "rtctrlASetPref", + "rtctrlASetNh", + "rtctrlASetOspfFwdAddr", + "rtctrlASetOspfNssa", + "bgpAAsP", + "bgpACtxPol", + "bgpAPeerPfxPol", + "bgpAPeerP", + "bgpAExtP", + "cdpAIfPol", + "fabricProtoIfPol", + "fabricL2IfPol", + "fabricL3IfPol", + "lldpAIfPol", + "fabricProtoPol", + "fabricProtoComp", + "fabricL2ProtoPol", + "fabricL3ProtoPol", + "fabricL2ProtoComp", + "fabricL3ProtoComp", + "fabricProtoInstPol", + "fabricUtilInstPol", + "fabricL2InstPol", + "fabricL3InstPol", + "fabricProtoDomPol", + "fabricL2DomPol", + "fabricL3DomPol", + "fabricL3CtxPol", + "l2AInstPol", + "fabricMaintPol", + "fabricNodeGrp", + "fabricAPodBlk", + "fabricANodeBlk", + "fabricAPortBlk", + "fabricSelector", + "fabricANodeS", + "fabricNodeS", + "fabricACardS", + "fabricCardS", + "fabricAPortS", + "fabricPortS", + "fabricIntfPol", + "fabricAProfile", + "fabricProfile", + "fabricPolGrp", + "fabricNodeP", + "fabricCardP", + "fabricPortP", + "fabricAPortPGrp", + "fabricANodePGrp", + "fabricACardPGrp", + "fabricSpAPortPGrp", + "fabricLeAPortPGrp", + "fabricAProtPol", + "fabricANodePEp", + "fabricPol", + "fabricInfrP", + "fabricInfrExP", + "fabricDom", + "fabricDef", + "fabricAPolGrp", + "vsvcAProvLbl", + "vsvcAConsLbl", + "compASvcVM", + "compNic", + "compPNic", + "compEntity", + "compElement", + "compContE", + "compObj", + "compCont", + "compProvP", + "compDomP", + "compCtrlrP", + "compAccessP", + "compUsrAccP", + "compHost", + "compPHost", + "vzACollection", + "vzACtrct", + "vzABrCP", + "vzAIf", + "vzAFilterable", + "vzAFilterableUnit", + "vzASubj", + "vzATerm", + "vzASTerm", + "vzALbl", + "vzACompLbl", + "vzAnyToCollection", + "vzAFilter", + "vzAFiltEntry", + "ospfACtxPol", + "ospfAIfP", + "ospfAExtP", + "dhcpARelayP", + "dhcpALbl", + "dhcpAInfraProvP", + "poolElement", + "poolPoolable", + "poolPoolMember", + "poolPool", + "pkiItem", + "aaaDefinition", + "commDefinition", + "pkiDefinition", + "aaaSystemUser", + "aaaBanner", + "aaaUserAction", + "aaaARetP", + "commComp", + "commWeb", + "commShell", + "aaaRealm", + "aaaConfig", + "aaaAuthMethod", + "aaaEp", + "aaaAProvider", + "aaaProviderGroup", + "adcomATsInfoUnit", + "adcomARwi", + "healthARetP", + "healthAInst", + "igmpASnoopPol", + "sysfileEp", + "sysfileRepository", + "sysfileInstance", + "fileARemoteHost", + "fileARemotePath", + "monProtoP", + "monSecAuthP", + "monGroup", + "monSubj", + "monSrc", + "conditionCondP", + "ctrlrDom", + "l2extADomP", + "l2extALNodeP", + "l2extAIfP", + "l2extAInstPSubnet", + "extnwEPg", + "extnwOut", + "extnwDomP", + "extnwAInstPSubnet", + "extnwALNodeP", + "extnwALIfP", + "l3extADomP", + "l3extALNodeP", + "l3extAIfP", + "l3extAMember", + "l3extAInstPSubnet", + "trigSchedWindowP", + "trigInst", + "trigWindow", + "trigSchedWindow", + "trigExecutable", + "trigTriggerable", + "trigSingleTriggerable", + "fvACont", + "fvADeplCont", + "fvL2Dom", + "fvABD", + "fvABDPol", + "fvAEpRetPol", + "fvComp", + "fvATg", + "fvEPgToCollection", + "fvADomP", + "fvEPg", + "fvCEPg", + "fvAREpPCtrct", + "fvDom", + "fvL3Dom", + "fvACtx", + "fvNwEp", + "fvATp", + "fvEp", + "fvPEp", + "fvAEpDef", + "fvTo", + "fvFrom", + "mgmtAZone", + "mgmtAInstPSubnet", + "eqptdiagpTestSet", + "eqptdiagpTestSetBoot", + "eqptdiagpTestSetHealth", + "eqptdiagpTestSetOd", + "eqptdiagpPortTestSetOd", + "eqptdiagpPortTestSetBt", + "eqptdiagpPortTestSetHl", + "eqptdiagpLpTsOd", + "eqptdiagpFpTsOd", + "eqptdiagpCardTestSetOd", + "eqptdiagpSupCTsOd", + "eqptdiagpSysCTsOd", + "eqptdiagpFcTsOd", + "eqptdiagpLcTsOd", + "eqptdiagpExtChCardTsOd", + "eqptdiagpPol", + "eqptdiagpHealthPol", + "eqptdiagpASynthObj", + "oamExec", + "pingAExec", + "tracerouteAExec", + "bgpAf", + "dhcpAddr", + "dhcpNode", + "dhcpResp", + "eqptALPort", + "dbgACRulePCommon", + "dbgacTenantSpaceCmn", + "dbgacEpgCmn", + "dbgacFromEpgCmn", + "dbgacToEpgCmn", + "dbgacFromEpCmn", + "dbgacToEpCmn", + "dbgexpExportP", + "dbgexpNodeStatus", + "spanASrcGrp", + "spanASrc", + "spanASpanLbl", + "spanADest", + "spanAVSrc", + "spanAVSrcGrp", + "spanAVDestGrp", + "spanAVDest", + "svccorePol", + "svccoreACore", + "traceroutepTrP", + "syntheticObject", + "syntheticAContext", + "syntheticATestObj", + "syntheticTLTestObj", + "syntheticCTestObj", + "monExportP", + "ipARouteP", + "ipANexthopP", + "infraFexBlk", + "infraANodeS", + "infraNodeGrp", + "infraPortS", + "infraEPg", + "infraACEPg", + "infraAPEPg", + "infraANode", + "infraAIpP", + "infraProfile", + "infraPolGrp", + "infraAccBaseGrp", + "infraFexGrp", + "infraAccGrp", + "infraLbl", + "infraAPEp", + "infraACEp", + "infraAFunc", + "infraGeNode", + "infraADomP", + "infraDomP", + "infraExP", + "datetimeAPol", + "datetimeANtpAuthKey", + "datetimeANtpProv", + "fvnsAAddrBlk", + "fvnsAEncapBlk", + "fvnsAInstP", + "fvnsAVxlanInstP", + "fvnsAAddrInstP", + "polAttTgt", + "snmpAPol", + "snmpACommunityP", + "snmpAUserP", + "snmpAClientGrpP", + "snmpAClientP", + "snmpACtxP", + "fvDef", + "fvNp", + "fvUp", + "polNToRef", + "polNFromRef", + "polObj", + "polDef", + "polDefRoot", + "polComp", + "polInstr", + "polCont", + "polDom", + "polCtrlr", + "polCompl", + "polComplElem", + "polConsElem", + "polLbl", + "polProvLbl", + "polConsLbl", + "polIf", + "polProvIf", + "polConsIf", + "polRelnHolder", + "polNs", + "polAConfIssues", + "vnsAGraph", + "vnsANode", + "vnsAFuncNode", + "vnsAFolder", + "vnsACCfgRel", + "vnsAParam", + "vnsATerm", + "vnsATermNode", + "vnsAbsTermNode", + "vnsALDevCtx", + "vnsALIfCtx", + "vnsAConn", + "vnsAFuncConn", + "vnsATermConn", + "vnsAConnection", + "vnsAL4L7ServiceFault", + "vnsACCfg", + "vnsALDevIf", + "vnsALDev", + "vnsALIf", + "vnsALDevLIf", + "vnsDevItem", + "fabricAPathIssues", + "conditionSevAsnP", + "fvAStCEp", + "fabricNodeToPolicy", + "aaaADomainRef", + "fvAEpTaskAggr", + "vzToRFltP", + "fvAToBD", + "frmwrkARelDelCont", + "infraDomainToNs", + "infraToAInstP", + "aaaARbacRule", + "statsDebugItem", + "vzInterfaceToCollection", + "spanAToCEp", + "fabricAOOSReln", + "l4AVxlanInstPol", + "fabricVxlanInstPol", + "fabricL1IfPol", + "frmwrkARelDelControl", + "spanACEpDef", + "eptrkEpRslt", + "mcpAIfPol", + "ipmcsnoopRtrIf", + "bfdAf", + "bgpALocalAsnP", + "bgpACtxAfPol", + "l3extADefaultRouteLeakP", + "fvAStIp", + "rtctrlASetRtMetricType", + "vnsAEPpInfo", + "ndAIfPol", + "ndAPfxPol", + "eigrpACtxAfPol", + "eigrpAStubP", + "eigrpAIfP", + "eigrpAExtP", + "l3extAIp", + "fabricProtoConsFrom", + "fabricProtoConsTo", + "fabricAPathS", + "infraAAccBndlGrp", + "fabricNodeToPathOverridePolicy", + "adcomARwiAdvanced", + "fvAClassifier", + "fvACrtrn", + "fvAttr", + "fvAVmAttr", + "fvAIpAttr", + "fvAMacAttr", + "fvAProtoAttr", + "polADependentOn", + "vnsAVRoutingNetworks", + "l3extARouteTagPol", + "nwsAFwPol", + "fabricL4IfPol", + "fvAVip", + "fvAAREpPUpdate", + "conditionSummary", + "polAPrToPol", + "polAObjToPolReln", + "fvACrRule", + "fvASCrtrn", + "fvnsAVlanInstP", + "vzAnyToInterface", + "fvEPgToInterface", + "fabricPodGrp", + "fabricAPodS", + "infraPodGrp", + "vnsAMgmt", + "fabricPolicyGrpToMonitoring", + "plannerIPs", + "plannerAObject", + "plannerAEpg", + "compAPltfmP", + "compAVmmPltfmP", + "compAVmmSecP", + "bgpARRP", + "bgpARtTargetP", + "bgpARtTarget", + "l3extAFabricExtRoutingP", + "fvAFabricExtConnP", + "fvAPodConnP", + "fvAPeeringP", + "fvANode", + "plannerATmpl", + "nwsASrc", + "nwsASyslogSrc", + "throttlerASub", + "qosADscpTrans", + "fvARsToRemote", + "vnsOrchReq", + "vnsOrchResp", + "fcprARs", + "dbgexpTechSupOnDBase", + "infraGeSnNode", + "l3extAConsLbl", + "l3extAProvLbl", + "trigATriggeredWindow", + "qosADppPol", + "bfdAInstPol", + "bfdAIfPol", + "bfdAIfP", + "plannerADomainTmpl", + "plannerAEpgDomain", + "vmmACapObj", + "vmmACapInfo", + "fvARsToRemoteFC", + "rtctrlAMatchCommFactor", + "rtctrlAMatchCommTerm", + "rtctrlAMatchCommRegexTerm", + "rtctrlASetDamp", + "rtctrlASetWeight", + "hvsContE", + "hvsUsegContE", + "compAPvlanP", + "usegAUsegEPg", + "fvAStAttr", + "fvADyAttr", + "mgmtAIp", + "faultARsToRemote", + "analyticsACfgSrv", + "infraAAccGrp", + "infraSpAccGrp", + "infraANodeP", + "infraPortP", + "infraAPathS", + "ipmcsnoopMcSrc", + "ipmcsnoopTgtIf", + "analyticsACluster", + "rtdmcAIfPol", + "rtdmcARtMapPol", + "rtdmcAExtP", + "rtdmcACtxPol", + "rtdmcARPPol", + "rtdmcAAutoRPPol", + "rtdmcABSRPPol", + "rtdmcAStaticRPPol", + "rtdmcAStaticRPEntry", + "rtdmcARPGrpRangePol", + "rtdmcARegTrPol", + "rtdmcAResPol", + "rtdmcAPatPol", + "rtdmcAASMPatPol", + "rtdmcASGRangeExpPol", + "rtdmcASharedRangePol", + "rtdmcASSMPatPol", + "rtdmcASSMRangePol", + "rtdmcABidirPatPol", + "pimAIfP", + "rtdmcARtMapEntry", + "rtdmcAJPFilterPol", + "rtdmcANbrFilterPol", + "rtdmcAMAFilter", + "rtdmcABSRFilter", + "rtdmcAFilterPol", + "fvAEPgPathAtt", + "fvnsAVsanInstP", + "ipmcAIfPol", + "ipmcAStRepPol", + "ipmcAStateLPol", + "ipmcARepPol", + "ipmcACtxPol", + "ipmcASSMXlateP", + "igmpAIfP", + "vmmEpgAggr", + "apPluginName", + "rtdmcAIfPolCont", + "fabricL2PortSecurityPol", + "fcAPinningP", + "fcAPinningLbl", + "fabricAONodeS", + "infraAONodeS", + "analyticsRemoteNode", + "analyticsTarget", + "fabricASubPortBlk", + "bgpARtTargetInstrP", + "hsrpAIfPol", + "hsrpAGroupPol", + "hsrpAGroupP", + "hsrpASecVip", + "hsrpAIfP", + "netflowARecordPol", + "netflowAExporterPol", + "netflowARsInterfaceToMonitor", + "netflowARsMonitorToRecord", + "netflowARsMonitorToExporter", + "netflowAMonitorPol", + "igmpASnoopStaticGroup", + "igmpASnoopAccessGroup", + "netflowAFabExporterPol", + "fabricQinqIfPol", + "orchsEntity", + "orchsElement", + "vnsNATReq", + "vnsLBReq", + "aclACL", + "aclL3ACE", + "apDockerName", + "hostprotASubj", + "macsecAAIfPol", + "macsecAIfPol", + "macsecAAParamPol", + "macsecAParamPol", + "macsecAAKeyChainPol", + "macsecAAKeyPol", + "macsecAKeyChainPol", + "macsecAKeyPol", + "fvAIntersiteConnP", + "fvAIntersiteConnPDef", + "fvASiteConnP", + "coppAProfile", + "coppACustomValues", + "infraNodeS", + "rtctrlASetASPath", + "rtctrlASetASPathASN", + "fvADnsAttr", + "dnsepgFault", + "dnsepgASvrGrp", + "dnsepgAMgmt", + "dnsepgASvr", + "dnsepgADomain", + "ipANexthopEpP", + "vmmInjectedObject", + "poeAIfPol", + "iaclAProfile", + "telemetryAServer", + "snmpATrapFwdServerP", + "dwdmAOptChnlIfPol", + "vzARuleOwner", + "telemetryAServerPol", + "telemetryARemoteServer", + "telemetryAFlowServers", + "telemetryAServerP", + "plannerAEpgFilter", + "callhomeASrc", + "callhomeADest", + "callhomeAGroup", + "fvAEpAnycast", + "tagTag", + "tagAnnotation", + "tagASelector", + "infraFcAccGrp", + "infraAFcAccBndlGrp", + "vsanARsVsanPathAtt", + "vsanARtVsanPathAtt", + "vzSubjectToFilter", + "telemetryAStreamEnable", + "cloudsecASaKeyP", + "rtdmcAInterVRFPol", + "rtdmcAInterVRFEntry", + "cloudsecAControl", + "adepgAResElement", + "adepgAElement", + "adepgACont", + "adepgAOrgUnit", + "adepgEntity", + "adepgContE", + "fvAIdAttr", + "edmObj", + "edmCont", + "edmGroupP", + "edmMgrP", + "edmEntity", + "edmElement", + "edmContE", + "authASvrGroup", + "authASvr", + "authBaseUsrAccP", + "cloudsecASaKeyPLocal", + "cloudsecASaKeyPRemote", + "cloudsecASaKeyStatus", + "cloudsecASaKeyStatusLocal", + "cloudsecASaKeyStatusRemote", + "cloudsecASpKeySt", + "cloudsecACapability", + "aaaRbacAnnotation", + "lacpAEnhancedLagPol", + "compAHvHealth", + "mldASnoopPol", + "fvAExtRoutes", + "fvAExtRoutableRemoteSitePodSubnet", + "fvAEpNlb", + "extdevSDWanASlaPol", + "eigrpAAuthIfP", + "fvAAKeyChainPol", + "fvAAKeyPol", + "fvAKeyChainPol", + "fvAKeyPol", + "fvSyntheticIp", + "edmACapFlags", + "cloudABgpPeerP", + "cloudABgpAsP", + "cloudARouterP", + "cloudAIntNetworkP", + "cloudAExtNetworkP", + "cloudAHostRouterPol", + "cloudAHostBootstrapPol", + "cloudAVpnGwPol", + "cloudAHostIfP", + "cloudALoopbackIfP", + "cloudAL3IfP", + "cloudAIpv4AddrP", + "cloudAL3TunnelIfP", + "cloudAIpSecTunnelIfP", + "cloudAOspfIfP", + "cloudAOspfAreaP", + "cloudACtxProfile", + "cloudACidr", + "cloudASubnet", + "cloudAAwsLogGroup", + "cloudAAwsFlowLogPol", + "cloudAProvider", + "cloudAAwsProvider", + "cloudAAEPg", + "cloudAEPSelector", + "cloudAEPSelectorDef", + "cloudADomP", + "genericsARule", + "ipsecAIsakmpPhase1Pol", + "ipsecAIsakmpPhase2Pol", + "resolutionARsToRemote", + "cloudtemplateASubnetPool", + "fvAACrtrn", + "fvAUsegAssocBD", + "cloudALDev", + "cloudAListener", + "cloudAPool", + "cloudAListenerRule", + "cloudARuleCondition", + "cloudARuleAction", + "cloudACertStore", + "cloudACertificate", + "arpAIfPol", + "compNameIdentEntity", + "edmAOperCont", + "edmAStatsCont", + "cloudASvcPol", + "hcloudATgStats", + "statsANWStatsObj", + "statsATunnel", + "cloudAController", + "cloudAApicSubnet", + "cloudAApicSubnetPool", + "cloudABaseEPg", + "cloudASvcEPg", + "statsAALbStats", + "mldASnoopStaticGroup", + "mldASnoopAccessGroup", + "datetimeANtpIFFKey", + "hcloudRouterStateOper", + "vmmAUplinkP", + "vmmAUplinkPCont", + "cloudAAFilter", + "fvASDWanPrefixTaskAggr", + "cloudAProvResP", + "bfdAMhInstPol", + "bfdAMhIfPol", + "fvAEPSelector", + "ptpAACfg", + "ptpACfg", + "fabricL2BDPol", + "rtdmcABDPol", + "rtdmcABDFilter", + "ptpAProfile", + "mdpADomP", + "mdpADomainPeeringPol", + "mdpAPeeringDomain", + "mdpANodeP", + "mdpAClassId", + "mdpATenant", + "mplsAExtP", + "mplsAIfP", + "mplsALabelPol", + "mplsANodeSidP", + "qosAMplsIngressRule", + "qosMplsMarking", + "qosAMplsEgressRule", + "cloudAGatewayRouterP", + "cloudATransitHubGwPol", + "cloudAL3IntTunnelIfP", + "cloudABdiId", + "mplsASrgbLabelPol", + "bfdAMhNodePol", + "bfdANodeP", + "leakASubnet", + "rtctrlASetNhUnchanged", + "mdpAService", + "cloudALIf", + "cloudAParamPol", + "cloudAComputePol", + "cloudAMgmtPol", + "cloudANWParams", + "fvRtScopeFrom", + "cloudACtxUnderlayP", + "cloudAHealthProbe", + "leakARouteCont", + "netflowAVmmExporterPol", + "cloudABrownfield", + "cloudAMapping", + "cloudASelectedEP", + "statsANlbStats", + "mdpAEntity", + "dbgANodeInst", + "snmpACtrlrInst", + "cloudAFrontendIPv4Addr", + "qosAUburst", + "telemetryAFteEvents", + "telemetryAFteEventsTcpFlags", + "telemetryAFteEventsExt", + "cloudACloudSvcEPg", + "hcloudASvcResBase", + "bgpADomainIdBase", + "fvAEpTag", + "bgpASiteOfOriginP", + "fvAEPSelectorDef", + "vmmCFaultInfo", + "synceAAIfPol", + "synceAIfPol", + "rtctrlASetRedistMultipath", + "cloudANextHopIp", + "cloudAVrfRouteLeakPol", + "hcloudALeakedPfx", + "leakAPrefix", + "fvARogueExceptionMac", + "bfdAMicroBfdP", + "hcloudAIntPfx", + "hcloudAExtPfx", + "cloudAVpnNetworkP", + "snmpUser", + "cloudAVirtualWanP", + "fvAFBRGroup", + "fvAFBRoute", + "fvAFBRMember", + "rtctrlASetPolicyTag", + "fvACtxRtSummPol", + "fvARtSummSubnet", + "fvAIntraVrfFabricImpRtctrlP", + "fvAFabricExpRtctrlP", + "cloudAVip", + "rtctrlAMatchAsPathRegexTerm", + "cloudAGcpFlowLogPol", + "statsAGcpNWStatsObj", + "cloudABfdPol", + "cloudABfdP", + "bgpACtxAddlPathPol", + "xcvrOpticsIfPol", + "xcvrOpticsFabIfPol", + "hostprotANamespace", + "fvARouteDeployP", + "rtdmcACSWPol", + "rtdmcACSWEntry", + "analyticsTo", + "analyticsFrom", + "dmemotltestAbsObject", + "bgpAsnmpBgpTraps", + "l3extARogueExceptionMac", + "l3extARogueExceptionMacGroup", + "vzAFiltPZEntry", +] diff --git a/ansible_collections/cisco/aci/plugins/module_utils/constants.py b/ansible_collections/cisco/aci/plugins/module_utils/constants.py index 165b63431..26818ff64 100644 --- a/ansible_collections/cisco/aci/plugins/module_utils/constants.py +++ b/ansible_collections/cisco/aci/plugins/module_utils/constants.py @@ -97,4 +97,340 @@ EP_LOOP_PROTECTION_ACTION_MAPPING = {"bd": "bd-learn-disable", "port": "port-dis FABRIC_POD_SELECTOR_TYPE_MAPPING = dict(all="ALL", range="range") -TLS_MAPPING = {"tls_v1.0": "TLSv1", "tls_v1.1": "TLSv1.1", "tls_v1.2": "TLSv1.2"} +OPFLEX_TLS_MAPPING = {"tls_v1.0": "TLSv1", "tls_v1.1": "TLSv1.1", "tls_v1.2": "TLSv1.2"} + +HTTP_TLS_MAPPING = {"tls_v1.0": "TLSv1", "tls_v1.1": "TLSv1.1", "tls_v1.2": "TLSv1.2", "tls_v1.3": "TLSv1.3"} + +ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING = dict( + spine=dict( + class_name="infraSpineAccNodePGrp", + rn="infra/funcprof/spaccnodepgrp-{0}", + copp_pre_filter_policy=dict(class_name="infraRsIaclSpineProfile", tn_name="tnIaclSpineProfileName"), + bfd_ipv4_policy=dict(class_name="infraRsSpineBfdIpv4InstPol", tn_name="tnBfdIpv4InstPolName"), + bfd_ipv6_policy=dict(class_name="infraRsSpineBfdIpv6InstPol", tn_name="tnBfdIpv6InstPolName"), + copp_policy=dict(class_name="infraRsSpineCoppProfile", tn_name="tnCoppSpineProfileName"), + cdp_policy=dict(class_name="infraRsSpinePGrpToCdpIfPol", tn_name="tnCdpIfPolName"), + lldp_policy=dict(class_name="infraRsSpinePGrpToLldpIfPol", tn_name="tnLldpIfPolName"), + usb_configuration_policy=dict(class_name="infraRsSpineTopoctrlUsbConfigProfilePol", tn_name="tnTopoctrlUsbConfigProfilePolName"), + ), + leaf=dict( + class_name="infraAccNodePGrp", + rn="infra/funcprof/accnodepgrp-{0}", + copp_pre_filter_policy=dict(class_name="infraRsIaclLeafProfile", tn_name="tnIaclLeafProfileName"), + bfd_ipv4_policy=dict(class_name="infraRsBfdIpv4InstPol", tn_name="tnBfdIpv4InstPolName"), + bfd_ipv6_policy=dict(class_name="infraRsBfdIpv6InstPol", tn_name="tnBfdIpv6InstPolName"), + copp_policy=dict(class_name="infraRsLeafCoppProfile", tn_name="tnCoppLeafProfileName"), + cdp_policy=dict(class_name="infraRsLeafPGrpToCdpIfPol", tn_name="tnCdpIfPolName"), + lldp_policy=dict(class_name="infraRsLeafPGrpToLldpIfPol", tn_name="tnLldpIfPolName"), + usb_configuration_policy=dict(class_name="infraRsLeafTopoctrlUsbConfigProfilePol", tn_name="tnTopoctrlUsbConfigProfilePolName"), + ), +) + +PIM_SETTING_CONTROL_STATE_MAPPING = {"fast": "fast-conv", "strict": "strict-rfc-compliant"} + +ACI_CLASS_MAPPING = dict( + consumer={ + "class": "fvRsCons", + "rn": "rscons-", + "name": "tnVzBrCPName", + }, + provider={ + "class": "fvRsProv", + "rn": "rsprov-", + "name": "tnVzBrCPName", + }, + taboo={ + "class": "fvRsProtBy", + "rn": "rsprotBy-", + "name": "tnVzTabooName", + }, + interface={ + "class": "fvRsConsIf", + "rn": "rsconsIf-", + "name": "tnVzCPIfName", + }, + intra_epg={ + "class": "fvRsIntraEpg", + "rn": "rsintraEpg-", + "name": "tnVzBrCPName", + }, +) + +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", +) + +SUBJ_LABEL_RN = dict( + consumer="conssubjlbl-", + provider="provsubjlbl-", +) + +MATCH_ACTION_RULE_SET_METRIC_TYPE_MAPPING = {"ospf_type_1": "ospf-type1", "ospf_type_2": "ospf-type2", "": ""} + +MATCH_EIGRP_INTERFACE_POLICY_DELAY_UNIT_MAPPING = dict(picoseconds="pico", tens_of_microseconds="tens-of-micro") + +MATCH_EIGRP_INTERFACE_POLICY_CONTROL_STATE_MAPPING = dict(bfd="bfd", nexthop_self="nh-self", passive="passive", split_horizon="split-horizon") + +MATCH_TARGET_COS_MAPPING = { + "background": "0", + "best_effort": "1", + "excellent_effort": "2", + "critical_applications": "3", + "video": "4", + "voice": "5", + "internetwork_control": "6", + "network_control": "7", + "unspecified": "unspecified", +} + +MATCH_PIM_INTERFACE_POLICY_CONTROL_STATE_MAPPING = dict(multicast_domain_boundary="border", strict_rfc_compliant="strict-rfc-compliant", passive="passive") + +MATCH_PIM_INTERFACE_POLICY_AUTHENTICATION_TYPE_MAPPING = dict(none="none", md5_hmac="ah-md5") + +MATCH_COLLECT_NETFLOW_RECORD_MAPPING = dict( + bytes_counter="count-bytes", + pkts_counter="count-pkts", + pkt_disposition="pkt-disp", + sampler_id="sampler-id", + source_interface="src-intf", + tcp_flags="tcp-flags", + first_pkt_timestamp="ts-first", + recent_pkt_timestamp="ts-recent", +) + +MATCH_MATCH_NETFLOW_RECORD_MAPPING = dict( + destination_ipv4_v6="dst-ip", + destination_ipv4="dst-ipv4", + destination_ipv6="dst-ipv6", + destination_mac="dst-mac", + destination_port="dst-port", + ethertype="ethertype", + ip_protocol="proto", + source_ipv4_v6="src-ip", + source_ipv4="src-ipv4", + source_ipv6="src-ipv6", + source_mac="src-mac", + source_port="src-port", + ip_tos="tos", + unspecified="unspecified", + vlan="vlan", +) + +MATCH_SOURCE_IP_TYPE_NETFLOW_EXPORTER_MAPPING = dict( + custom_source_ip="custom-src-ip", + inband_management_ip="inband-mgmt-ip", + out_of_band_management_ip="oob-mgmt-ip", + ptep="ptep", +) + +ECC_CURVE = {"P256": "prime256v1", "P384": "secp384r1", "P521": "secp521r1", "none": "none"} + +THROTTLE_UNIT = dict(requests_per_second="r/s", requests_per_minute="r/m") + +SSH_CIPHERS = dict( + aes128_ctr="aes128-ctr", + aes192_ctr="aes192-ctr", + aes256_ctr="aes256-ctr", + aes128_gcm="aes128-gcm@openssh.com", + aes256_gcm="aes256-gcm@openssh.com", + chacha20="chacha20-poly1305@openssh.com", +) + +SSH_MACS = dict( + sha1="hmac-sha1", + sha2_256="hmac-sha2-256", + sha2_512="hmac-sha2-512", + sha2_256_etm="hmac-sha2-256-etm@openssh.com", + sha2_512_etm="hmac-sha2-512-etm@openssh.com", +) + +KEX_ALGORITHMS = dict( + dh_sha1="diffie-hellman-group14-sha1", + dh_sha256="diffie-hellman-group14-sha256", + dh_sha512="diffie-hellman-group16-sha512", + curve_sha256="curve25519-sha256", + curve_sha256_libssh="curve25519-sha256@libssh.org", + ecdh_256="ecdh-sha2-nistp256", + ecdh_384="ecdh-sha2-nistp384", + ecdh_521="ecdh-sha2-nistp521", +) + +USEG_ATTRIBUTE_MAPPING = dict( + ip=dict(attribute_type="ip", attribute_class="fvIpAttr", rn_format="ipattr-{0}"), + mac=dict(attribute_type="mac", attribute_class="fvMacAttr", rn_format="macattr-{0}"), + dns=dict(attribute_type="dns", attribute_class="fvDnsAttr", rn_format="dnsattr-{0}"), + ad_group=dict(attribute_type="ad", attribute_class="fvIdGroupAttr", rn_format="idgattr-[{0}]"), + vm_custom_attr=dict(attribute_type="custom-label", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_vmm_domain=dict(attribute_type="domain", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_operating_system=dict(attribute_type="guest-os", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_hypervisor_id=dict(attribute_type="hv", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_datacenter=dict(attribute_type="rootContName", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_id=dict(attribute_type="vm", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_name=dict(attribute_type="vm-name", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_folder=dict(attribute_type="vm-folder", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_folder_path=dict(attribute_type="vmfolder-path", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_vnic=dict(attribute_type="vnic", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), + vm_tag=dict(attribute_type="tag", attribute_class="fvVmAttr", rn_format="vmattr-{0}"), +) + +OPERATOR_MAPPING = dict(equals="equals", contains="contains", starts_with="startsWith", ends_with="endsWith") + +MATCH_STORM_CONTROL_POLICY_TYPE_MAPPING = dict(all_types="Invalid", unicast_broadcast_multicast="Valid") + +POLICY_LABEL_COLORS = [ + "alice_blue", + "antique_white", + "aqua", + "aquamarine", + "azure", + "beige", + "bisque", + "black", + "blanched_almond", + "blue", + "blue_violet", + "brown", + "burlywood", + "cadet_blue", + "chartreuse", + "chocolate", + "coral", + "cornflower_blue", + "cornsilk", + "crimson", + "cyan", + "dark_blue", + "dark_cyan", + "dark_goldenrod", + "dark_gray", + "dark_green", + "dark_khaki", + "dark_magenta", + "dark_olive_green", + "dark_orange", + "dark_orchid", + "dark_red", + "dark_salmon", + "dark_sea_green", + "dark_slate_blue", + "dark_slate_gray", + "dark_turquoise", + "dark_violet", + "deep_pink", + "deep_sky_blue", + "dim_gray", + "dodger_blue", + "fire_brick", + "floral_white", + "forest_green", + "fuchsia", + "gainsboro", + "ghost_white", + "gold", + "goldenrod", + "gray", + "green", + "green_yellow", + "honeydew", + "hot_pink", + "indian_red", + "indigo", + "ivory", + "khaki", + "lavender", + "lavender_blush", + "lawn_green", + "lemon_chiffon", + "light_blue", + "light_coral", + "light_cyan", + "light_goldenrod_yellow", + "light_gray", + "light_green", + "light_pink", + "light_salmon", + "light_sea_green", + "light_sky_blue", + "light_slate_gray", + "light_steel_blue", + "light_yellow", + "lime", + "lime_green", + "linen", + "magenta", + "maroon", + "medium_aquamarine", + "medium_blue", + "medium_orchid", + "medium_purple", + "medium_sea_green", + "medium_slate_blue", + "medium_spring_green", + "medium_turquoise", + "medium_violet_red", + "midnight_blue", + "mint_cream", + "misty_rose", + "moccasin", + "navajo_white", + "navy", + "old_lace", + "olive", + "olive_drab", + "orange", + "orange_red", + "orchid", + "pale_goldenrod", + "pale_green", + "pale_turquoise", + "pale_violet_red", + "papaya_whip", + "peachpuff", + "peru", + "pink", + "plum", + "powder_blue", + "purple", + "red", + "rosy_brown", + "royal_blue", + "saddle_brown", + "salmon", + "sandy_brown", + "sea_green", + "seashell", + "sienna", + "silver", + "sky_blue", + "slate_blue", + "slate_gray", + "snow", + "spring_green", + "steel_blue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + "white_smoke", + "yellow", + "yellow_green", +] + +MATCH_ACCESS_POLICIES_SELECTOR_TYPE = dict(range="range", all="ALL") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_aaa_certificate_authority.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_certificate_authority.py new file mode 100644 index 000000000..673515185 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_certificate_authority.py @@ -0,0 +1,290 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_certificate_authority +short_description: Manage AAA Certificate Authorities (pki:TP) +description: +- Manage AAA Certificate Authorities on Cisco ACI fabrics. +options: + name: + description: + - The name of the Certificate Authority. + type: str + aliases: [ certificate_authority, cert_authority, cert_authority_name, certificate_authority_name ] + cloud_tenant: + description: + - The name of the cloud tenant. + - This attribute is only applicable for Cloud APIC. + type: str + aliases: [ tenant, tenant_name ] + description: + description: + - The description of the Certificate Authority. + type: str + aliases: [ descr ] + certificate_chain: + description: + - The PEM-encoded chain of trust from the trustpoint to a trusted root authority. + type: str + aliases: [ cert_data, certificate_data, cert, certificate ] + 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(cloud_tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(pki:TP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Certificate Authority + cisco.aci.aci_aaa_certificate_authority: + host: apic + username: admin + password: SomeSecretPassword + name: example_authority + certificate_chain: '{{ lookup("file", "pki/example_authority.crt") }}' + state: present + delegate_to: localhost + +- name: Query a Certificate Authority + cisco.aci.aci_aaa_certificate_authority: + host: apic + username: admin + password: SomeSecretPassword + name: example_authority + state: query + delegate_to: localhost + register: query_result + +- name: Query all Certificate Authorities + cisco.aci.aci_aaa_certificate_authority: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Certificate Authority + cisco.aci.aci_aaa_certificate_authority: + host: apic + username: admin + password: SomeSecretPassword + name: example_authority + 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=["certificate_authority", "cert_authority", "cert_authority_name", "certificate_authority_name"] + ), # Not required for querying all objects + cloud_tenant=dict(type="str", aliases=["tenant", "tenant_name"]), + description=dict(type="str", aliases=["descr"]), + certificate_chain=dict(type="str", aliases=["cert_data", "certificate_data", "cert", "certificate"]), + 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", "certificate_chain"]], + ], + ) + + name = module.params.get("name") + cloud_tenant = module.params.get("cloud_tenant") + description = module.params.get("description") + certificate_chain = module.params.get("certificate_chain") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci_class = "pkiTP" + parent_class = "cloudCertStore" if cloud_tenant else "pkiEp" + parent_rn = "tn-{0}/certstore".format(cloud_tenant) if cloud_tenant else "userext/pkiext" + + aci.construct_url( + root_class=dict( + aci_class=parent_class, + aci_rn=parent_rn, + ), + subclass_1=dict( + aci_class=aci_class, + aci_rn="tp-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_class, + class_config=dict( + name=name, + descr=description, + certChain=certificate_chain, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class=aci_class) + + # Only wrap the payload in parent class if cloud_tenant is set to avoid apic error + aci.post_config(parent_class=parent_class if cloud_tenant else None) + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_aaa_key_ring.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_key_ring.py new file mode 100644 index 000000000..2aa2649c7 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_key_ring.py @@ -0,0 +1,339 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_key_ring +short_description: Manage AAA Key Rings (pki:KeyRing) +description: +- Manage AAA Key Rings on Cisco ACI fabrics. +options: + name: + description: + - The name of the Key Ring. + type: str + aliases: [ key_ring, key_ring_name ] + cloud_tenant: + description: + - The name of the cloud tenant. + - This attribute is only applicable for Cloud APIC. + type: str + aliases: [ tenant, tenant_name ] + description: + description: + - The description of the Key Ring. + type: str + aliases: [ descr ] + certificate: + description: + - The certificate of the Key Ring. + - The certificate contains a public key and signed information for verifying the identity. + type: str + aliases: [ cert_data, certificate_data, cert ] + modulus: + description: + - The modulus is the length of the key in bits. + type: int + choices: [ 512, 1024, 1536, 2048 ] + certificate_authority: + description: + - The name of the Certificate Authority. + type: str + aliases: [ cert_authority, cert_authority_name, certificate_authority_name ] + key: + description: + - The private key for the certificate. + type: str + key_type: + description: + - The type of the private key. + - This attribute is only configurable in ACI versions 6.0(2h) and above. + type: str + choices: [ rsa, ecc ] + ecc_curve: + description: + - The curve of the private key. + - This attribute requires O(key_type=ecc). + - This attribute is only configurable in ACI versions 6.0(2h) and above. + type: str + choices: [ P256, P384, P521, none ] + aliases: [ curve ] + 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(cloud_tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(pki:KeyRing). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Key Ring + cisco.aci.aci_aaa_key_ring: + host: apic + username: admin + password: SomeSecretPassword + name: example_key_ring + certificate: '{{ lookup("file", "pki/example_certificate.crt") }}' + modulus: 2048 + certificate_authority: example_certificate_authority + key: '{{ lookup("file", "pki/example_key.key") }}' + state: present + delegate_to: localhost + +- name: Query a Key Ring + cisco.aci.aci_aaa_key_ring: + host: apic + username: admin + password: SomeSecretPassword + name: example_key_ring + state: query + delegate_to: localhost + register: query_result + +- name: Query all Certificate Authorities + cisco.aci.aci_aaa_key_ring: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Key Ring + cisco.aci.aci_aaa_key_ring: + host: apic + username: admin + password: SomeSecretPassword + name: example_key_ring + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import ECC_CURVE + + +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=["key_ring", "key_ring_name"]), # Not required for querying all objects + cloud_tenant=dict(type="str", aliases=["tenant_name", "tenant"]), + description=dict(type="str", aliases=["descr"]), + certificate=dict(type="str", aliases=["cert_data", "certificate_data", "cert"]), + modulus=dict(type="int", choices=[512, 1024, 1536, 2048]), + certificate_authority=dict(type="str", aliases=["cert_authority", "cert_authority_name", "certificate_authority_name"]), + key=dict(type="str", no_log=True), + key_type=dict(type="str", choices=["rsa", "ecc"]), + ecc_curve=dict(type="str", choices=["P256", "P384", "P521", "none"], aliases=["curve"]), + 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") + cloud_tenant = module.params.get("cloud_tenant") + description = module.params.get("description") + certificate = module.params.get("certificate") + modulus = "mod{0}".format(module.params.get("modulus")) if module.params.get("modulus") else None + certificate_authority = module.params.get("certificate_authority") + key = module.params.get("key") + key_type = module.params.get("key_type") + ecc_curve = ECC_CURVE[module.params.get("ecc_curve")] if module.params.get("ecc_curve") else None + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci_class = "pkiKeyRing" + parent_class = "cloudCertStore" if cloud_tenant else "pkiEp" + parent_rn = "tn-{0}/certstore".format(cloud_tenant) if cloud_tenant else "userext/pkiext" + + aci.construct_url( + root_class=dict( + aci_class=parent_class, + aci_rn=parent_rn, + ), + subclass_1=dict( + aci_class=aci_class, + aci_rn="keyring-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + aci.get_existing() + + if state == "present": + class_config = dict( + name=name, + descr=description, + cert=certificate, + modulus=modulus, + tp=certificate_authority, + nameAlias=name_alias, + key=key, + ) + + if key_type: + class_config["keyType"] = key_type.upper() + + if ecc_curve: + class_config["eccCurve"] = ecc_curve + + aci.payload(aci_class=aci_class, class_config=class_config) + + aci.get_diff(aci_class=aci_class, required_properties=dict(name=name) if cloud_tenant else None) + + # Only wrap the payload in parent class if cloud_tenant is set to avoid apic error + aci.post_config(parent_class=parent_class if cloud_tenant else None) + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_aaa_security_default_settings.py b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_security_default_settings.py new file mode 100644 index 000000000..4ba18fded --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_security_default_settings.py @@ -0,0 +1,531 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_aaa_security_default_settings +short_description: Manage AAA Key Rings (pki:Ep) +description: +- Manage AAA Key Rings on Cisco ACI fabrics. +options: + password_strength_check: + description: + - Enable password strength check. + - Use C(true) to enable and C(false) to disable. + - The APIC defaults to C(true) when unset during creation. + type: bool + password_strength_profile: + description: + - The password strength profile (aaa:PwdStrengthProfile). + type: dict + suboptions: + enable: + description: + - Enable or disable password strength profile. + - Use C(true) to enable and C(false) to disable. + type: bool + required: true + type: + description: + - The type of the password strength profile. + - The APIC defaults to C(any_three) when unset during creation. + type: str + choices: [ custom, any_three ] + min_length: + description: + - The minimum length of the password. + - The APIC defaults to C(8) when unset during creation. + type: int + aliases: [ minimum_length, min ] + max_length: + description: + - The maximum length of the password. + - The APIC defaults to C(64) when unset during creation. + type: int + aliases: [ maximum_length, max ] + class_flags: + description: + - The class flags of the password strength profile. + - At least 3 class flags must be specified. + - This attribute is only applicable when type is set to O(password_strength_profile.type=custom). + - The APIC defaults to C(digits,lowercase,uppercase) when unset during creation. + type: list + elements: str + choices: [ digits, lowercase, specialchars, uppercase ] + aliases: [ flags ] + password_change: + description: + - The password change interval (aaa:PwdProfile). + type: dict + suboptions: + enable: + description: + - Enforce password change interval. + - Use C(true) to enable and C(false) to disable. + - The APIC defaults to C(true) when unset during creation. + type: bool + interval: + description: + - The password change interval in hours. + - The APIC defaults to C(48) when unset during creation. + type: int + allowed_changes: + description: + - The number of changes allowed within the change interval. + - The APIC defaults to C(2) when unset during creation. + type: int + minimum_period: + description: + - The minimum period between password changes in hours. + - The APIC defaults to C(24) when unset during creation. + type: int + aliases: [ minimum_period_between_password_changes, min_period ] + history_storage_amount: + description: + - The number of recent user passwords to store. + - The APIC defaults to C(5) when unset during creation. + type: int + aliases: [ history, amount ] + lockout: + description: + - Lockout behaviour after multiple failed login attempts (aaa:BlockLoginProfile). + type: dict + suboptions: + enable: + description: + - Enable lockout behaviour. + - Use C(true) to enable and C(false) to disable. + - The APIC defaults to C(false) when unset during creation. + type: bool + max_attempts: + description: + - The maximum number of failed attempts before user is locked out. + - The APIC defaults to C(5) when unset during creation. + type: int + aliases: [ max_failed_attempts, failed_attempts, attempts ] + window: + description: + - The time period in which consecutive attempts were failed in minutes. + - The APIC defaults to C(5) when unset during creation. + type: int + aliases: [ max_failed_attempts_window, failed_attempts_window ] + duration: + description: + - The duration of lockout in minutes. + - The APIC defaults to C(60) when unset during creation. + type: int + web_token: + description: + - The web token related configuration (pki:WebTokenData). + type: dict + suboptions: + timeout: + description: + - The web token timeout in seconds. + - The APIC defaults to C(600) when unset during creation. + type: int + idle_timeout: + description: + - The web/console (SSH/Telnet) session idle timeout in seconds. + - The APIC defaults to C(1200) when unset during creation. + type: int + aliases: [ session_idle_timeout ] + validity_period: + description: + - The maximum validity period in hours. + - The APIC defaults to C(24) when unset during creation. + type: int + aliases: [ maximum_validity_period ] + refresh: + description: + - Include refresh in session records. + - Use C(true) to include and C(false) to exclude. + - 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: [ 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(pki:Ep). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Set AAA Security Default Settings + cisco.aci.aci_aaa_security_default_settings: + host: apic + username: admin + password: SomeSecretPassword + password_strength_check: true + password_strength_profile: + enable: true + type: custom + min_length: 10 + max_length: 60 + class_flags: + - digits + - lowercase + - specialchars + - uppercase + password_change: + enable: true + interval: 49 + allowed_changes: 6 + minimum_period_between_password_changes: 25 + history_storage_amount: 6 + lockout: + enable: true + max_attempts: 6 + window: 6 + duration: 61 + web_token: + timeout: 601 + idle_timeout: 1201 + validity_period: 23 + refresh: true + state: present + delegate_to: localhost + +- name: Set AAA Security Default Settings to Default Values + cisco.aci.aci_aaa_security_default_settings: + host: apic + username: admin + password: SomeSecretPassword + password_strength_check: true + password_strength_profile: + enable: false + password_change: + enable: true + interval: 48 + allowed_changes: 2 + minimum_period_between_password_changes: 24 + history_storage_amount: 5 + lockout: + enable: false + max_attempts: 5 + window: 5 + duration: 60 + web_token: + timeout: 600 + idle_timeout: 1200 + validity_period: 24 + refresh: false + state: present + delegate_to: localhost + +- name: Query AAA Security Default Settings + cisco.aci.aci_aaa_security_default_settings: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + password_strength_check=dict(type="bool", no_log=False), + password_strength_profile=dict( + type="dict", + no_log=False, + options=dict( + enable=dict(type="bool", required=True), + type=dict(type="str", choices=["custom", "any_three"]), + min_length=dict(type="int", aliases=["minimum_length", "min"]), + max_length=dict(type="int", aliases=["maximum_length", "max"]), + class_flags=dict(type="list", elements="str", choices=["digits", "lowercase", "specialchars", "uppercase"], aliases=["flags"]), + ), + ), + password_change=dict( + type="dict", + no_log=False, + options=dict( + enable=dict(type="bool"), + interval=dict(type="int"), + allowed_changes=dict(type="int"), + minimum_period=dict(type="int", aliases=["minimum_period_between_password_changes", "min_period"]), + history_storage_amount=dict(type="int", aliases=["history", "amount"]), + ), + ), + lockout=dict( + type="dict", + options=dict( + enable=dict(type="bool"), + max_attempts=dict(type="int", aliases=["max_failed_attempts", "failed_attempts", "attempts"]), + window=dict(type="int", aliases=["max_failed_attempts_window", "failed_attempts_window"]), + duration=dict(type="int"), + ), + ), + web_token=dict( + type="dict", + no_log=False, + options=dict( + timeout=dict(type="int"), + idle_timeout=dict(type="int", aliases=["session_idle_timeout"]), + validity_period=dict(type="int", aliases=["maximum_validity_period"]), + refresh=dict(type="bool"), + ), + ), + state=dict(type="str", default="present", choices=["present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + aci = ACIModule(module) + + password_strength_check = aci.boolean(module.params.get("password_strength_check")) + password_strength_profile = module.params.get("password_strength_profile") + password_change = module.params.get("password_change") + lockout = module.params.get("lockout") + web_token = module.params.get("web_token") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci_class = "aaaUserEp" + child_classes = ["aaaPwdStrengthProfile", "aaaPwdProfile", "aaaBlockLoginProfile", "pkiWebTokenData"] + + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn="userext", + ), + child_classes=child_classes, + ) + aci.get_existing() + + if state == "present": + child_configs = [] + + class_config = dict( + pwdStrengthCheck=password_strength_check, + nameAlias=name_alias, + ) + + if password_strength_profile: + if password_strength_profile.get("enable"): + child_configs.append( + dict( + aaaPwdStrengthProfile=dict( + attributes=dict( + pwdStrengthTestType=password_strength_profile.get("type"), + pwdMinLength=password_strength_profile.get("min_length"), + pwdMaxLength=password_strength_profile.get("max_length"), + pwdClassFlags=",".join(sorted(password_strength_profile.get("class_flags"))), + ), + ), + ), + ) + # Delete existing aaaPwdStrengthProfile if enable is set to false and it exists + # This is done for setting the correct output for changed state + elif len(aci.existing) > 0 and len(aci.existing[0].get("aaaUserEp", {}).get("children", {})) > 3: + for child in aci.existing[0].get("aaaUserEp", {}).get("children", {}): + if "aaaPwdStrengthProfile" in child.keys(): + child_configs.append(dict(aaaPwdStrengthProfile=dict(attributes=dict(status="deleted")))) + break + + if password_change: + child_configs.append( + dict( + aaaPwdProfile=dict( + attributes=dict( + changeDuringInterval=aci.boolean(password_change.get("enable"), "enable", "disable"), + changeInterval=password_change.get("interval"), + changeCount=password_change.get("allowed_changes"), + noChangeInterval=password_change.get("minimum_period"), + historyCount=password_change.get("history_storage_amount"), + ), + ), + ), + ) + + if lockout: + child_configs.append( + dict( + aaaBlockLoginProfile=dict( + attributes=dict( + enableLoginBlock=aci.boolean(lockout.get("enable"), "enable", "disable"), + maxFailedAttempts=lockout.get("max_attempts"), + maxFailedAttemptsWindow=lockout.get("window"), + blockDuration=lockout.get("duration"), + ), + ), + ), + ) + + if web_token: + child_configs.append( + dict( + pkiEp=dict( + attributes=dict(descr=""), + children=[ + dict( + pkiWebTokenData=dict( + attributes=dict( + webtokenTimeoutSeconds=web_token.get("timeout"), + uiIdleTimeoutSeconds=web_token.get("idle_timeout"), + maximumValidityPeriod=web_token.get("validity_period"), + sessionRecordFlags="login,logout,refresh" if web_token.get("refresh") else "login,logout", + ), + ), + ), + ], + ), + ), + ) + + aci.payload( + aci_class=aci_class, + class_config=class_config, + child_configs=child_configs, + ) + + aci.get_diff(aci_class=aci_class) + + aci.post_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 index df4732f28..e1eb4b4d7 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_aaa_ssh_auth.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aaa_ssh_auth.py @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_aaa_ssh_auth -short_description: Manage AAA SSH auth (aaaSshAuth) objects. +short_description: Manage AAA SSH auth objects (aaa:SshAuth) description: - Manage AAA SSH Auth key configuration on Cisco ACI fabrics. options: @@ -47,7 +47,7 @@ notes: 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). + description: More information about the internal APIC class B(aaa:SshAuth). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) 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 index 646423290..0067d0463 100644 --- 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 @@ -4,6 +4,7 @@ # 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> +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -15,55 +16,60 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported 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) +short_description: Manage Port blocks of Fabric Access Leaf/Spine Interface Port Selectors (infra:PortBlk) description: -- Manage port blocks of Fabric interface policy leaf profile interface selectors on Cisco ACI fabrics. +- Manage Port blocks of Fabric Access Interface Leaf/Spine Port Selectors on Cisco ACI fabrics. options: interface_profile: description: - - The name of the Fabric access policy leaf interface profile. + - The name of the Fabric access policy leaf/spine interface profile. type: str - aliases: [ leaf_interface_profile_name, leaf_interface_profile, interface_profile_name ] + aliases: + - leaf_interface_profile_name + - leaf_interface_profile + - interface_profile_name + - spine_interface_profile + - spine_interface_profile_name access_port_selector: description: - - The name of the Fabric access policy leaf interface profile access port selector. + - The name of the Fabric access policy leaf/spine interface 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. + - The name of the Fabric access policy interface 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). + - The description for the port block. 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. + - The beginning (from-range) of the port range block for the 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. + - The end (to-range) of the port range block for the 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. + - The beginning (from-range) of the card range block for the 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. + - The end (to-range) of the card range block for the port block. type: str aliases: [ to_card_range ] type: description: - - The type of access port block to be created under respective access port. + - The type of port block to be created under respective access port. type: str - choices: [ fex, leaf ] + choices: [ fex, leaf, spine ] default: leaf state: description: @@ -77,18 +83,27 @@ extends_documentation_fragment: - cisco.aci.annotation notes: -- The C(interface_profile) and C(access_port_selector) must exist before using this module in your playbook. +- If Adding a port block on an access leaf interface port selector of I(type) C(leaf), + The I(interface_profile) and I(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. +- If Adding a port block on an access interface port selector of C(type) C(spine), + The I(interface_profile) and I(access_port_selector) must exist before using this module in your playbook. + The M(cisco.aci.aci_access_spine_interface_profile) and M(cisco.aci.aci_access_spine_interface_selector) modules can be used for this. seealso: +- module: cisco.aci.aci_interface_policy_leaf_profile +- module: cisco.aci.aci_access_port_to_interface_policy_leaf_profile +- module: cisco.aci.aci_access_spine_interface_profile +- module: cisco.aci.aci_access_spine_interface_selector - name: APIC Management Information Model reference - description: More information about the internal APIC classes B(infra:HPortS) and B(infra:PortBlk). + description: More information about the internal APIC classes B(infra:PortBlk). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Simon Metzger (@smnmtzgr) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" -- name: Associate an access port block (single port) to an interface selector +- name: Associate a Fabric access policy interface port block (single port) to an interface selector cisco.aci.aci_access_port_block_to_access_port: host: apic username: admin @@ -101,7 +116,7 @@ EXAMPLES = r""" state: present delegate_to: localhost -- name: Associate an access port block (port range) to an interface selector +- name: Associate a Fabric access policy interface port block (port range) to an interface selector cisco.aci.aci_access_port_block_to_access_port: host: apic username: admin @@ -114,7 +129,7 @@ EXAMPLES = r""" state: present delegate_to: localhost -- name: Associate an access port block (single port) to an interface selector of type fex +- name: Associate a Fabric access policy interface port block (single port) to an interface selector of type fex cisco.aci.aci_access_port_block_to_access_port: host: apic username: admin @@ -128,7 +143,7 @@ EXAMPLES = r""" state: present delegate_to: localhost -- name: Associate an access port block (port range) to an interface selector of type fex +- name: Associate a Fabric access policy interface port block (port range) to an interface selector of type fex cisco.aci.aci_access_port_block_to_access_port: host: apic username: admin @@ -142,7 +157,7 @@ EXAMPLES = r""" state: present delegate_to: localhost -- name: Remove an access port block from an interface selector +- name: Query Specific Fabric access policy interface port block under given access port selector cisco.aci.aci_access_port_block_to_access_port: host: apic username: admin @@ -150,12 +165,11 @@ EXAMPLES = r""" interface_profile: leafintprfname access_port_selector: accessportselectorname port_blk: leafportblkname - from_port: 13 - to_port: 13 - state: absent + state: query delegate_to: localhost + register: query_result -- name: Remove an access port block from an interface selector of type fex +- name: Query Specific Fabric access policy interface port block under given access port selector of type fex cisco.aci.aci_access_port_block_to_access_port: host: apic username: admin @@ -164,75 +178,76 @@ EXAMPLES = r""" interface_profile: leafintprfname_fex access_port_selector: accessportselectorname_fex port_blk: leafportblkname_fex - from_port: 13 - to_port: 13 - state: absent + state: query delegate_to: localhost + register: query_result -- name: Query Specific access port block under given access port selector +- name: Query all Fabric access policy interface 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 - 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 +- name: Query all Fabric access policy interface 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 - 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 +- name: Query all Fabric access policy interface port blocks in the fabric 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 +- name: Query all Fabric access policy interface 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 - interface_profile: leafintprfname_fex state: query delegate_to: localhost register: query_result -- name: Query all access port blocks in the fabric +- name: Remove a Fabric access policy interface port block from an interface selector cisco.aci.aci_access_port_block_to_access_port: host: apic username: admin password: SomeSecretPassword - state: query + interface_profile: leafintprfname + access_port_selector: accessportselectorname + port_blk: leafportblkname + from_port: 13 + to_port: 13 + state: absent delegate_to: localhost - register: query_result -- name: Query all access port blocks in the fabric of type fex +- name: Remove a Fabric access policy interface 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 - state: query + interface_profile: leafintprfname_fex + access_port_selector: accessportselectorname_fex + port_blk: leafportblkname_fex + from_port: 13 + to_port: 13 + state: absent delegate_to: localhost - register: query_result """ RETURN = r""" @@ -348,7 +363,16 @@ 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"]), + interface_profile=dict( + type="str", + aliases=[ + "leaf_interface_profile_name", + "leaf_interface_profile", + "interface_profile_name", + "spine_interface_profile", + "spine_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"]), @@ -357,15 +381,15 @@ def main(): 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 + type=dict(type="str", default="leaf", choices=["fex", "leaf", "spine"]), # 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"]], + ["state", "absent", ["interface_profile", "access_port_selector", "port_blk"]], + ["state", "present", ["interface_profile", "access_port_selector", "port_blk", "from_port", "to_port"]], ], ) @@ -381,26 +405,45 @@ def main(): 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), + subclass_1 = dict( + aci_class=aci_class, + aci_rn="{0}-{1}".format(aci_rn, interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ) + subclass_2 = dict( + aci_class="infraHPortS", + aci_rn="hports-{0}-typ-range".format(access_port_selector), + module_object=access_port_selector, + target_filter={"name": access_port_selector}, + ) + if type_port == "spine": + subclass_1 = dict( + aci_class="infraSpAccPortP", + aci_rn="spaccportprof-{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), + ) + subclass_2 = dict( + aci_class="infraSHPortS", + aci_rn="shports-{0}-typ-range".format(access_port_selector), module_object=access_port_selector, target_filter={"name": access_port_selector}, + ) + aci.construct_url( + root_class=dict( + aci_class="infraInfra", + aci_rn="infra", ), - subclass_2=dict( + subclass_1=subclass_1, + subclass_2=subclass_2, + subclass_3=dict( aci_class="infraPortBlk", aci_rn="portblk-{0}".format(port_blk), module_object=port_blk, @@ -420,7 +463,6 @@ def main(): toPort=to_port, fromCard=from_card, toCard=to_card, - # type='range', ), ) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py index 7b5c896ad..f2e921394 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_port_to_interface_policy_leaf_profile.py @@ -14,7 +14,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported 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) +short_description: Manage Fabric interface policy leaf profile interface selectors (infra:HPortS, infra:RsAccBaseGrp, and infra:PortBlk) description: - Manage Fabric interface policy leaf profile interface selectors on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_interface_profile.py new file mode 100644 index 000000000..a34f269ae --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_interface_profile.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Eric Girard <@netgirard> +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "certified", +} + +DOCUMENTATION = r""" +--- +module: aci_access_spine_interface_profile +short_description: Manage fabric interface policy spine profiles (infra:SpAccPortP) +description: +- Manage fabric interface policy spine profiles on Cisco ACI fabrics. +options: + interface_profile: + description: + - The name of the Fabric access policy spine interface profile. + type: str + aliases: [ name, spine_interface_profile_name, spine_interface_profile, interface_profile_name ] + description: + description: + - The description for the Fabric access policy spine 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 +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:SpAccPortP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Eric Girard (@netgirard) +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new spine_interface_profile + cisco.aci.aci_access_spine_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: spineintprfname + description: spineintprfname description + state: present + delegate_to: localhost + +- name: Query a spine_interface_profile + cisco.aci.aci_access_spine_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: spineintprfname + state: query + delegate_to: localhost + register: query_result + +- name: Query all spine_interface_profiles + cisco.aci.aci_access_spine_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove a spine_interface_profile + cisco.aci.aci_access_spine_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + interface_profile: spineintprfname + 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( + interface_profile=dict( + type="str", + aliases=[ + "name", + "spine_interface_profile_name", + "spine_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"), + ) + + 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") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="infraInfra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="infraSpAccPortP", + aci_rn="spaccportprof-{0}".format(interface_profile), + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraSpAccPortP", + class_config=dict( + name=interface_profile, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="infraSpAccPortP") + + 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_spine_interface_profile_to_spine_switch_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_interface_profile_to_spine_switch_profile.py new file mode 100644 index 000000000..17d8517c1 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_interface_profile_to_spine_switch_profile.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Eric Girard <@netgirard> +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "certified", +} + +DOCUMENTATION = r""" +--- +module: aci_access_spine_interface_profile_to_spine_switch_profile +short_description: Bind Fabric Access Spine Interface Profiles to Fabric Acces Spine Switch Profiles (infra:RsSpAccPortP) +description: +- Bind access spine interface selector profiles to access switch policy spine profiles on Cisco ACI fabrics. +options: + switch_profile: + description: + - The name of the Fabric Access Spine Switch Profile to which we add a Spine Interface Selector Profile. + type: str + aliases: [ switch_profile_name, spine_switch_profile, spine_switch_profile_name ] + interface_profile: + description: + - The name of the Fabric Access Spine Interface Profile to be added and associated with the Spine Switch Profile. + type: str + aliases: [ interface_profile_name, spine_interface_profile, spine_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 +- cisco.aci.owner + +notes: +- This module requires an existing I(switch_profile). + The module M(cisco.aci.aci_access_spine_switch_profile) can be used for this. +- The I(interface_profile) accepts non existing spine interface profile names. + They appear on APIC GUI with a state of "missing-target". + The module M(cisco.aci.aci_access_spine_interface_profile) can be used to create them. +seealso: +- module: cisco.aci.aci_access_spine_switch_profile +- module: cisco.aci.aci_access_spine_interface_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:RsSpAccPortP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Eric Girard (@netgirard) +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Associating an interface selector profile to a switch policy spine profile + cisco.aci.aci_access_spine_interface_profile_to_spine_switch_profile: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: sw_name + interface_profile: interface_profile_name + state: present + delegate_to: localhost + +- name: Query an interface selector profile associated with a switch policy spine profile + cisco.aci.aci_access_spine_interface_profile_to_spine_switch_profile: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: sw_name + interface_profile: interface_profile_name + state: query + delegate_to: localhost + +- name: Query all association of interface selector profiles with a switch policy spine profile + cisco.aci.aci_access_spine_interface_profile_to_spine_switch_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove an interface selector profile associated with a switch policy spine profile + cisco.aci.aci_access_spine_interface_profile_to_spine_switch_profile: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: sw_name + interface_profile: interface_profile_name + 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( + switch_profile=dict( + type="str", + aliases=[ + "switch_profile_name", + "spine_switch_profile", + "spine_switch_profile_name", + ], + ), # Not required for querying all objects + interface_profile=dict( + type="str", + aliases=[ + "interface_profile_name", + "spine_interface_profile", + "spine_interface_profile_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", ["switch_profile", "interface_profile"]], + ["state", "present", ["switch_profile", "interface_profile"]], + ], + ) + + switch_profile = module.params.get("switch_profile") + interface_profile = module.params.get("interface_profile") + state = module.params.get("state") + + # Defining the interface profile tDn for clarity + interface_profile_tDn = "uni/infra/spaccportprof-{0}".format(interface_profile) + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="infraInfra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="infraSpineP", + aci_rn="spprof-{0}".format(switch_profile), + module_object=switch_profile, + target_filter={"name": switch_profile}, + ), + subclass_2=dict( + aci_class="infraRsSpAccPortP", + aci_rn="rsspAccPortP-[{0}]".format(interface_profile_tDn), + module_object=interface_profile, + target_filter={"tDn": interface_profile_tDn}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraRsSpAccPortP", + class_config=dict(tDn=interface_profile_tDn), + ) + + aci.get_diff(aci_class="infraRsSpAccPortP") + + 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_spine_interface_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_interface_selector.py new file mode 100644 index 000000000..373a31e76 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_interface_selector.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_access_spine_interface_selector +short_description: Manage Fabric Access Policy Spine Interface Port Selectors (infra:SHPortS) +description: +- Manage Fabric Access Policy Spine Interface Port Selectors on Cisco ACI fabrics. +- This selector is used for applying infrastructure policies on selected ports. +options: + spine_interface_profile: + description: + - The name of the Fabric access policy spine interface profile. + type: str + aliases: [ spine_interface_profile_name, interface_profile, interface_profile_name ] + spine_interface_selector: + description: + - The name of the Fabric access spine interface port selector. + type: str + aliases: [ name, spine_interface_selector_name, interface_selector, interface_selector_name, access_port_selector, access_port_selector_name ] + description: + description: + - The description for the spine interface port selector. + type: str + policy_group: + description: + - The name of the fabric access policy group to be associated with the spine interface port selector. + type: str + aliases: [ policy_group_name ] + selector_type: + description: + - The host port selector type. + - If using a port block to specify range of interfaces, the type must be set to C(range). + type: str + choices: [ all, range ] + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(spine_interface_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_access_spine_interface_profile) module can be used for this. +- If a I(policy_group) is used, it must exist before using this module in your playbook. + The M(cisco.aci.aci_interface_policy_spine_policy_group) module can be used for this. +seealso: +- module: cisco.aci.aci_access_port_block_to_access_port +- module: cisco.aci.aci_interface_policy_spine_policy_group +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:SHPortS) and B(infra:RsSpAccGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new access spine interface selector + cisco.aci.aci_access_spine_interface_selector: + host: apic + username: admin + password: SomeSecretPassword + spine_interface_profile: my_access_spine_interface_profile + spine_interface_selector: my_access_spine_interface_selector + selector_type: range + policy_group: my_access_spine_interface_policy_group + state: present + delegate_to: localhost + +- name: Query a specific access spine interface selector under given spine_interface_profile + cisco.aci.aci_access_spine_interface_selector: + host: apic + username: admin + password: SomeSecretPassword + spine_interface_profile: my_access_spine_interface_profile + spine_interface_selector: my_access_spine_interface_selector + selector_type: range + state: query + delegate_to: localhost + +- name: Query all access spine interface selectors under given spine_interface_profile + cisco.aci.aci_access_spine_interface_selector: + host: apic + username: admin + password: SomeSecretPassword + spine_interface_profile: my_access_spine_interface_profile + state: query + delegate_to: localhost + +- name: Query all access spine interface selectors + cisco.aci.aci_access_spine_interface_selector: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove an access spine interface selector + cisco.aci.aci_access_spine_interface_selector: + host: apic + username: admin + password: SomeSecretPassword + spine_interface_profile: my_access_spine_interface_profile + spine_interface_selector: my_access_spine_interface_selector + selector_type: range + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_ACCESS_POLICIES_SELECTOR_TYPE + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + spine_interface_profile=dict(type="str", aliases=["spine_interface_profile_name", "interface_profile", "interface_profile_name"]), + spine_interface_selector=dict( + type="str", + aliases=[ + "name", + "spine_interface_selector_name", + "interface_selector", + "interface_selector_name", + "access_port_selector", + "access_port_selector_name", + ], + ), # Not required for querying all objects + description=dict(type="str"), + policy_group=dict(type="str", aliases=["policy_group_name"]), + selector_type=dict(type="str", choices=list(MATCH_ACCESS_POLICIES_SELECTOR_TYPE.keys()), aliases=["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", ["spine_interface_profile", "spine_interface_selector", "selector_type"]], + ["state", "present", ["spine_interface_profile", "spine_interface_selector", "selector_type"]], + ], + ) + + spine_interface_profile = module.params.get("spine_interface_profile") + spine_interface_selector = module.params.get("spine_interface_selector") + description = module.params.get("description") + policy_group = module.params.get("policy_group") + selector_type = MATCH_ACCESS_POLICIES_SELECTOR_TYPE.get(module.params.get("selector_type")) + state = module.params.get("state") + + child_configs = [] + if policy_group is not None: + child_configs.append(dict(infraRsSpAccGrp=dict(attributes=dict(tDn="uni/infra/funcprof/spaccportgrp-{0}".format(policy_group))))) + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="infraInfra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="infraSpAccPortP", + aci_rn="spaccportprof-{0}".format(spine_interface_profile), + module_object=spine_interface_profile, + target_filter={"name": spine_interface_profile}, + ), + subclass_2=dict( + aci_class="infraSHPortS", + aci_rn="shports-{0}-typ-{1}".format(spine_interface_selector, selector_type), + module_object=spine_interface_selector, + target_filter={"name": spine_interface_selector}, + ), + child_classes=["infraRsSpAccGrp"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraSHPortS", + class_config=dict( + descr=description, + name=spine_interface_selector, + type=selector_type, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="infraSHPortS") + + 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_spine_switch_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_switch_profile.py new file mode 100644 index 000000000..22d4756a7 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_switch_profile.py @@ -0,0 +1,277 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Eric Girard <@netgirard> +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "certified", +} + +DOCUMENTATION = r""" +--- +module: aci_access_spine_switch_profile +short_description: Manage Fabric Access Spine Switch Profiles (infra:SpineP) +description: +- Manage Fabric access switch policy spine profiles on Cisco ACI fabrics. +options: + switch_profile: + description: + - The name of the Fabric Access Spine Switch Profile. + type: str + aliases: [ switch_profile_name, name, spine_switch_profile, spine_switch_profile_name ] + description: + description: + - The description for the Fabric Access Spine Switch 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: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(infra:SpineP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Eric Girard (@netgirard) +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new Access Spine Switch Profile + cisco.aci.aci_access_spine_switch_profile: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: sw_name + description: sw_description + state: present + delegate_to: localhost + +- name: Query an Access Spine Switch Profile + cisco.aci.aci_access_spine_switch_profile: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: sw_name + state: query + delegate_to: localhost + +- name: Query all Access Spine Switch Profiles + cisco.aci.aci_access_spine_switch_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove an Access Spine Switch Profile + cisco.aci.aci_access_spine_switch_profile: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: sw_name + 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( + switch_profile=dict( + type="str", + aliases=[ + "name", + "switch_profile_name", + "spine_switch_profile", + "spine_switch_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", ["switch_profile"]], + ["state", "present", ["switch_profile"]], + ], + ) + + switch_profile = module.params.get("switch_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="infraInfra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="infraSpineP", + aci_rn="spprof-{0}".format(switch_profile), + module_object=switch_profile, + target_filter={"name": switch_profile}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraSpineP", + class_config=dict( + name=switch_profile, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="infraSpineP") + + 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_spine_switch_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_switch_selector.py new file mode 100644 index 000000000..c7cee4b5e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_spine_switch_selector.py @@ -0,0 +1,321 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Eric Girard <@netgirard> +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "certified", +} + +DOCUMENTATION = r""" +--- +module: aci_access_spine_switch_selector +short_description: Manage Fabric Access Policy Spine Switch Port Selectors (infra:SpineS) +description: +- Manage Fabric Access Policy Spine Switch Port Selectors on Cisco ACI fabrics. +options: + spine_switch_profile: + description: + - The name of the Fabric access policy spine switch profile. + type: str + aliases: [ spine_switch_profile_name, switch_profile, switch_profile_name ] + spine_switch_selector: + description: + - The name of the Fabric access spine switch port selector. + type: str + aliases: [ name, spine_switch_selector_name, switch_selector, switch_selector_name, access_port_selector, access_port_selector_name ] + description: + description: + - The description for the spine switch port selector. + type: str + policy_group: + description: + - The name of the fabric access policy group to be associated with the spine switch port selector. + type: str + aliases: [ policy_group_name ] + selector_type: + description: + - The host port selector type. + - If using a port block to specify range of switches, the type must be set to C(range). + type: str + choices: [ all, range ] + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(spine_switch_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_access_spine_switch_profile) module can be used for this. +- If a I(policy_group) is used, it must exist before using this module in your playbook. + The M(cisco.aci.aci_switch_policy_group) module can be used for this. +seealso: +- module: cisco.aci.aci_access_spine_switch_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:SpineS) and B(infra:RsAccNodePGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Eric Girard (@netgirard) +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a switch policy spine profile selector (with policy group) + cisco.aci.aci_access_spine_switch_selector: + host: apic + username: admin + password: SomeSecretPassword + spine_switch_profile: sw_name + spine_switch_selector: spine_selector_name + selector_type: range + policy_group: somepolicygroupname + state: present + delegate_to: localhost + +- name: Query a switch policy spine profile selector + cisco.aci.aci_access_spine_switch_selector: + host: apic + username: admin + password: SomeSecretPassword + spine_switch_profile: sw_name + spine_switch_selector: spine_selector_name + selector_type: range + state: query + delegate_to: localhost + +- name: Query all switch policy spine profile selectors + cisco.aci.aci_access_spine_switch_selector: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Remove a switch policy spine profile selector + cisco.aci.aci_access_spine_switch_selector: + host: apic + username: admin + password: SomeSecretPassword + spine_switch_profile: sw_name + spine_switch_selector: spine_selector_name + selector_type: range + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_ACCESS_POLICIES_SELECTOR_TYPE + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + spine_switch_profile=dict(type="str", aliases=["spine_switch_profile_name", "switch_profile", "switch_profile_name"]), + spine_switch_selector=dict( + type="str", + aliases=[ + "name", + "spine_switch_selector_name", + "switch_selector", + "switch_selector_name", + "access_port_selector", + "access_port_selector_name", + ], + ), # Not required for querying all objects + description=dict(type="str"), + policy_group=dict(type="str", aliases=["policy_group_name"]), + selector_type=dict(type="str", choices=list(MATCH_ACCESS_POLICIES_SELECTOR_TYPE.keys()), aliases=["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", ["spine_switch_profile", "spine_switch_selector", "selector_type"]], + ["state", "present", ["spine_switch_profile", "spine_switch_selector", "selector_type"]], + ], + ) + + spine_switch_profile = module.params.get("spine_switch_profile") + spine_switch_selector = module.params.get("spine_switch_selector") + description = module.params.get("description") + policy_group = module.params.get("policy_group") + selector_type = MATCH_ACCESS_POLICIES_SELECTOR_TYPE.get(module.params.get("selector_type")) + state = module.params.get("state") + + child_configs = [] + if policy_group is not None: + child_configs.append(dict(infraRsSpineAccNodePGrp=dict(attributes=dict(tDn="uni/infra/funcprof/spaccnodepgrp-{0}".format(policy_group))))) + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="infraInfra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="infraSpineP", + aci_rn="spprof-{0}".format(spine_switch_profile), + module_object=spine_switch_profile, + target_filter={"name": spine_switch_profile}, + ), + subclass_2=dict( + aci_class="infraSpineS", + aci_rn="spines-{0}-typ-{1}".format(spine_switch_selector, selector_type), + module_object=spine_switch_selector, + target_filter={"name": spine_switch_selector}, + ), + child_classes=["infraRsSpineAccNodePGrp"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraSpineS", + class_config=dict( + descr=description, + name=spine_switch_selector, + type=selector_type, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="infraSpineS") + + 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 index 8073c6ca2..a3006ad4c 100644 --- 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 @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported 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) +short_description: Manage sub port blocks of Fabric interface policy leaf profile interface selectors (infra:HPortS and infra:SubPortBlk) description: - Manage sub port blocks of Fabric interface policy leaf profile interface selectors on Cisco ACI fabrics. seealso: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_access_switch_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_access_switch_policy_group.py new file mode 100644 index 000000000..d1ed40408 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_access_switch_policy_group.py @@ -0,0 +1,600 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_access_switch_policy_group +short_description: Manage Access Switch Policy Groups (infra:AccNodePGrp and infra:SpineAccNodePGrp). +description: +- Manage Access Switch Policy Groups on Cisco ACI fabrics. +options: + name: + description: + - The name of the access switch policy group. + aliases: [ policy_group ] + type: str + description: + description: + - The description of the access switch policy group. + type: str + switch_type: + description: + - Whether this is a leaf or spine policy group + type: str + choices: [ leaf, spine ] + required: true + spanning_tree_policy: + description: + - The spanning tree policy bound to the access switch policy group. + - Only available in APIC version 5.2 or later. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + bfd_ipv4_policy: + description: + - The BFD IPv4 policy bound to the access switch policy group. + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + bfd_ipv6_policy: + description: + - The BFD IPv6 policy bound to the access switch policy group. + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + bfd_multihop_ipv4_policy: + description: + - The BFD multihop IPv4 policy bound to the access switch policy group. + - Only available in APIC version 5.x or later. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + bfd_multihop_ipv6_policy: + description: + - The BFD multihop IPv6 policy bound to the access switch policy group. + - Only available in APIC version 5.x or later. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + fibre_channel_node_policy: + description: + - The fibre channel node policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + poe_node_policy: + description: + - The PoE node policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + fibre_channel_san_policy: + description: + - The fibre channel SAN policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + monitoring_policy: + description: + - The monitoring policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + netflow_node_policy: + description: + - The netflow node policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + copp_policy: + description: + - The CoPP policy bound to the access switch policy group. + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + forward_scale_profile_policy: + description: + - The forward scale profile policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + fast_link_failover_policy: + description: + - The fast link failover policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + node_802_1x_authentication_policy: + description: + - The 802.1x node authentication policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + copp_pre_filter_policy: + description: + - The CoPP pre-filter policy bound to the access switch policy group. + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + equipment_flash_policy: + description: + - The equipment flash policy bound to the access switch policy group. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + cdp_policy: + description: + - The CDP policy bound to the access switch policy group. + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + lldp_policy: + description: + - The LLDP policy bound to the access switch policy group. + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + sync_e_node_policy: + description: + - The SyncE node policy bound to the access switch policy group. + - Only available in APIC version 5.x or later. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + ptp_node_policy: + description: + - The PTP node policy bound to the access switch policy group. + - Only available in APIC version 5.2 or later. + - Only available when I(switch_type=leaf). + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + type: str + usb_configuration_policy: + description: + - The USB configuration policy bound to the access switch policy group. + - Only available in APIC version 5.2 or later. + - The APIC defaults to C("") which results in the target DN set to the default policy when unset during creation. + 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:AccNodePGrp) and B(infra:SpineAccNodePGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create Leaf Access Switch Policy Group + cisco.aci.aci_access_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: ansible_pol_grp_spine + switch_type: leaf + spanning_tree_policy: example_spanning_tree_policy + bfd_ipv4_policy: example_bfd_ipv4_policy + bfd_ipv6_policy: example_bfd_ipv6_policy + fibre_channel_node_policy: example_fibre_channel_node_policy + poe_node_policy: example_poe_node_policy + fibre_channel_san_policy: example_fibre_channel_san_policy + monitoring_policy: example_monitoring_policy + copp_policy: example_copp_policy + forward_scale_profile_policy: example_forward_scale_profile_policy + fast_link_failover_policy: example_fast_link_failover_policy + node_802_1x_authentication_policy: example_node_802_1x_authentication_policy + copp_pre_filter_policy: example_copp_pre_filter_policy + equipment_flash_policy: example_equipment_flash_policy + cdp_policy: example_cdp_policy + lldp_policy: example_lldp_policy + state: present + delegate_to: localhost + +- name: Create Spine Access Switch Policy Group + cisco.aci.aci_access_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: ansible_pol_grp_leaf + switch_type: spine + bfd_ipv4_policy: example_bfd_ipv4_policy + bfd_ipv6_policy: example_bfd_ipv6_policy + copp_policy: example_copp_policy + copp_pre_filter_policy: example_copp_pre_filter_policy + cdp_policy: example_cdp_policy + lldp_policy: example_lldp_policy + state: present + delegate_to: localhost + +- name: Delete Leaf Access Switch Policy Group + cisco.aci.aci_access_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: ansible_pol_grp_leaf + switch_type: leaf + state: absent + delegate_to: localhost + +- name: Query Leaf Access Switch Policy Group + cisco.aci.aci_access_switch_policy_group: + host: apic + username: admin + password: SomeSecretPassword + name: ansible_pol_grp_leaf + switch_type: leaf + state: query + delegate_to: localhost + register: query_result + +- name: Query All Leaf Access Switch Policy Groups + cisco.aci.aci_access_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_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + name=dict(type="str", aliases=["policy_group"]), + description=dict(type="str"), + switch_type=dict(type="str", choices=["leaf", "spine"], required=True), + spanning_tree_policy=dict(type="str"), + bfd_ipv4_policy=dict(type="str"), + bfd_ipv6_policy=dict(type="str"), + bfd_multihop_ipv4_policy=dict(type="str"), + bfd_multihop_ipv6_policy=dict(type="str"), + fibre_channel_node_policy=dict(type="str"), + poe_node_policy=dict(type="str"), + fibre_channel_san_policy=dict(type="str"), + monitoring_policy=dict(type="str"), + netflow_node_policy=dict(type="str"), + copp_policy=dict(type="str"), + forward_scale_profile_policy=dict(type="str"), + fast_link_failover_policy=dict(type="str"), + node_802_1x_authentication_policy=dict(type="str"), + copp_pre_filter_policy=dict(type="str"), + equipment_flash_policy=dict(type="str"), + cdp_policy=dict(type="str"), + lldp_policy=dict(type="str"), + sync_e_node_policy=dict(type="str"), + ptp_node_policy=dict(type="str"), + usb_configuration_policy=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") + description = module.params.get("description") + switch_type = module.params.get("switch_type") + spanning_tree_policy = module.params.get("spanning_tree_policy") + bfd_ipv4_policy = module.params.get("bfd_ipv4_policy") + bfd_ipv6_policy = module.params.get("bfd_ipv6_policy") + bfd_multihop_ipv4_policy = module.params.get("bfd_multihop_ipv4_policy") + bfd_multihop_ipv6_policy = module.params.get("bfd_multihop_ipv6_policy") + fibre_channel_node_policy = module.params.get("fibre_channel_node_policy") + poe_node_policy = module.params.get("poe_node_policy") + fibre_channel_san_policy = module.params.get("fibre_channel_san_policy") + monitoring_policy = module.params.get("monitoring_policy") + netflow_node_policy = module.params.get("netflow_node_policy") + copp_policy = module.params.get("copp_policy") + forward_scale_profile_policy = module.params.get("forward_scale_profile_policy") + fast_link_failover_policy = module.params.get("fast_link_failover_policy") + node_802_1x_authentication_policy = module.params.get("node_802_1x_authentication_policy") + copp_pre_filter_policy = module.params.get("copp_pre_filter_policy") + equipment_flash_policy = module.params.get("equipment_flash_policy") + cdp_policy = module.params.get("cdp_policy") + lldp_policy = module.params.get("lldp_policy") + sync_e_node_policy = module.params.get("sync_e_node_policy") + ptp_node_policy = module.params.get("ptp_node_policy") + usb_configuration_policy = module.params.get("usb_configuration_policy") + state = module.params.get("state") + + aci = ACIModule(module) + + if switch_type == "spine" and not all( + v is None + for v in [ + spanning_tree_policy, + bfd_multihop_ipv4_policy, + bfd_multihop_ipv6_policy, + fibre_channel_node_policy, + poe_node_policy, + fibre_channel_san_policy, + monitoring_policy, + netflow_node_policy, + forward_scale_profile_policy, + fast_link_failover_policy, + node_802_1x_authentication_policy, + equipment_flash_policy, + sync_e_node_policy, + ptp_node_policy, + ] + ): + aci.fail_json(msg="Unsupported policy provided for spine switch type.") + + class_name = ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type).get("class_name") + + aci.construct_url( + root_class=dict( + aci_class=class_name, + aci_rn=ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type).get("rn").format(name), + module_object=name, + target_filter={"name": name}, + ), + rsp_subtree="children", + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if forward_scale_profile_policy is not None: + child_configs.append({"infraRsTopoctrlFwdScaleProfPol": {"attributes": {"tnTopoctrlFwdScaleProfilePolName": forward_scale_profile_policy}}}) + if usb_configuration_policy is not None: + child_configs.append( + { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type) + .get("usb_configuration_policy") + .get("class_name"): { + "attributes": { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type) + .get("usb_configuration_policy") + .get("tn_name"): usb_configuration_policy + } + } + } + ) + if lldp_policy is not None: + child_configs.append( + { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type) + .get("lldp_policy") + .get("class_name"): { + "attributes": {ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type).get("lldp_policy").get("tn_name"): lldp_policy} + } + } + ) + if cdp_policy is not None: + child_configs.append( + { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type) + .get("cdp_policy") + .get("class_name"): { + "attributes": {ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type).get("cdp_policy").get("tn_name"): cdp_policy} + } + } + ) + if bfd_ipv4_policy is not None: + child_configs.append( + { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type) + .get("bfd_ipv4_policy") + .get("class_name"): { + "attributes": {ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type).get("bfd_ipv4_policy").get("tn_name"): bfd_ipv4_policy} + } + } + ) + if bfd_ipv6_policy is not None: + child_configs.append( + { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type) + .get("bfd_ipv6_policy") + .get("class_name"): { + "attributes": {ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type).get("bfd_ipv6_policy").get("tn_name"): bfd_ipv6_policy} + } + } + ) + if sync_e_node_policy is not None: + child_configs.append({"infraRsSynceInstPol": {"attributes": {"tnSynceInstPolName": sync_e_node_policy}}}) + if poe_node_policy is not None: + child_configs.append({"infraRsPoeInstPol": {"attributes": {"tnPoeInstPolName": poe_node_policy}}}) + if bfd_multihop_ipv4_policy is not None: + child_configs.append({"infraRsBfdMhIpv4InstPol": {"attributes": {"tnBfdMhIpv4InstPolName": bfd_multihop_ipv4_policy}}}) + if bfd_multihop_ipv6_policy is not None: + child_configs.append({"infraRsBfdMhIpv6InstPol": {"attributes": {"tnBfdMhIpv6InstPolName": bfd_multihop_ipv6_policy}}}) + if equipment_flash_policy is not None: + child_configs.append({"infraRsEquipmentFlashConfigPol": {"attributes": {"tnEquipmentFlashConfigPolName": equipment_flash_policy}}}) + if monitoring_policy is not None: + child_configs.append({"infraRsMonNodeInfraPol": {"attributes": {"tnMonInfraPolName": monitoring_policy}}}) + if fibre_channel_node_policy is not None: + child_configs.append({"infraRsFcInstPol": {"attributes": {"tnFcInstPolName": fibre_channel_node_policy}}}) + if fast_link_failover_policy is not None: + child_configs.append( + {"infraRsTopoctrlFastLinkFailoverInstPol": {"attributes": {"tnTopoctrlFastLinkFailoverInstPolName": fast_link_failover_policy}}} + ) + if spanning_tree_policy is not None: + child_configs.append({"infraRsMstInstPol": {"attributes": {"tnStpInstPolName": spanning_tree_policy}}}) + if fibre_channel_san_policy is not None: + child_configs.append({"infraRsFcFabricPol": {"attributes": {"tnFcFabricPolName": fibre_channel_san_policy}}}) + if copp_policy is not None: + child_configs.append( + { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type) + .get("copp_policy") + .get("class_name"): { + "attributes": {ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type).get("copp_policy").get("tn_name"): copp_policy} + } + } + ) + if node_802_1x_authentication_policy is not None: + child_configs.append({"infraRsL2NodeAuthPol": {"attributes": {"tnL2NodeAuthPolName": node_802_1x_authentication_policy}}}) + if copp_pre_filter_policy is not None: + child_configs.append( + { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type) + .get("copp_pre_filter_policy") + .get("class_name"): { + "attributes": { + ACI_ACCESS_SWITCH_POLICY_GROUP_CLASS_MAPPING.get(switch_type).get("copp_pre_filter_policy").get("tn_name"): copp_pre_filter_policy + } + } + } + ) + if netflow_node_policy is not None: + child_configs.append({"infraRsNetflowNodePol": {"attributes": {"tnNetflowNodePolName": netflow_node_policy}}}) + if ptp_node_policy is not None: + child_configs.append({"infraRsPtpInstPol": {"attributes": {"tnPtpInstPolName": ptp_node_policy}}}) + + if child_configs == []: + child_configs = None + + 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_action_rule_additional_communities.py b/ansible_collections/cisco/aci/plugins/modules/aci_action_rule_additional_communities.py new file mode 100644 index 000000000..c6bd998d9 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_action_rule_additional_communities.py @@ -0,0 +1,304 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_action_rule_additional_communities +short_description: Manage Action Rules based on Additional Communities (rtctrl:SetAddComm) +description: +- Set additional communities for the action rule profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + action_rule: + description: + - The name of the action rule profile. + type: str + aliases: [ action_rule_name ] + community: + description: + - The community value. + type: str + criteria: + description: + - The community criteria. + - The option to append or replace the community value. + type: str + choices: [ append, replace, none ] + description: + description: + - The description for the action rule profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant) and the C(action_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_tenant_action_rule_profile) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_tenant_action_rule_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:SetAddComm). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create an additional communities action rule + cisco.aci.aci_action_rule_additional_communities: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + community: no-advertise + criteria: replace + state: present + delegate_to: localhost + +- name: Delete an additional communities action rule + cisco.aci.aci_action_rule_additional_communities: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + community: no-advertise + state: absent + delegate_to: localhost + +- name: Query all additional communities action rules + cisco.aci.aci_action_rule_additional_communities: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query an additional communities action rule + cisco.aci.aci_action_rule_additional_communities: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + community: no-advertise + 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 + action_rule=dict(type="str", aliases=["action_rule_name"]), # Not required for querying all objects + community=dict(type="str"), + criteria=dict(type="str", choices=["append", "replace", "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", "absent", ["action_rule", "tenant", "community"]], + ["state", "present", ["action_rule", "tenant", "community"]], + ], + ) + + community = module.params.get("community") + criteria = module.params.get("criteria") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + action_rule = module.params.get("action_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlAttrP", + aci_rn="attr-{0}".format(action_rule), + module_object=action_rule, + target_filter={"name": action_rule}, + ), + subclass_2=dict( + aci_class="rtctrlSetAddComm", + aci_rn="saddcomm-{0}".format(community), + module_object=community, + target_filter={"community": community}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlSetAddComm", + class_config=dict( + community=community, + setCriteria=criteria, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlSetAddComm") + + 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_action_rule_set_as_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_action_rule_set_as_path.py new file mode 100644 index 000000000..580cc4cff --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_action_rule_set_as_path.py @@ -0,0 +1,304 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_action_rule_set_as_path +short_description: Manage the AS Path action rules (rtctrl:SetASPath) +description: +- Set AS path action rule for the action rule profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + action_rule: + description: + - The name of the action rule profile. + type: str + aliases: [ action_rule_name ] + last_as: + description: + - The last AS number value. + type: int + aliases: [ last_as_number ] + criteria: + description: + - The option to append the specified AS number or to prepend the last AS numbers to the AS Path. + type: str + choices: [ prepend, prepend-last-as ] + description: + description: + - The description for the action rule profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant) and the C(action_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_tenant_action_rule_profile) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_tenant_action_rule_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:SetASPath). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a Set AS path action rule + cisco.aci.aci_action_rule_set_as_path: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + last_as: 0 + criteria: prepend + state: present + delegate_to: localhost + +- name: Delete a Set AS path action rule + cisco.aci.aci_action_rule_set_as_path: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + criteria: prepend + state: absent + delegate_to: localhost + +- name: Query all Set AS path action rules + cisco.aci.aci_action_rule_set_as_path: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a Set AS path action rule + cisco.aci.aci_action_rule_set_as_path: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + criteria: prepend + 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 + action_rule=dict(type="str", aliases=["action_rule_name"]), # Not required for querying all objects + last_as=dict(type="int", aliases=["last_as_number"]), + criteria=dict(type="str", choices=["prepend", "prepend-last-as"]), + 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", "criteria"]], + ["state", "present", ["action_rule", "tenant", "criteria"]], + ], + ) + + last_as = module.params.get("last_as_number") + criteria = module.params.get("criteria") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + action_rule = module.params.get("action_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlAttrP", + aci_rn="attr-{0}".format(action_rule), + module_object=action_rule, + target_filter={"name": action_rule}, + ), + subclass_2=dict( + aci_class="rtctrlSetASPath", + aci_rn="saspath-{0}".format(criteria), + module_object=criteria, + target_filter={"criteria": criteria}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlSetASPath", + class_config=dict( + lastnum=last_as, + criteria=criteria, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlSetASPath") + + 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_action_rule_set_as_path_asn.py b/ansible_collections/cisco/aci/plugins/modules/aci_action_rule_set_as_path_asn.py new file mode 100644 index 000000000..fdc675ddb --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_action_rule_set_as_path_asn.py @@ -0,0 +1,312 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_action_rule_set_as_path_asn +short_description: Manage the AS Path ASN (rtctrl:SetASPathASN) +description: +- Set the ASN for the AS Path action rules on Cisco ACI fabrics. +- Only used if the AS Path action rule is set to C(prepend). +options: + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] + action_rule: + description: + - The name of the action rule profile. + type: str + aliases: [ action_rule_name ] + asn: + description: + - The ASN number. + type: int + order: + description: + - The ASN order. + type: int + description: + description: + - The description for the action rule profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant) and the C(action_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_tenant_action_rule_profile) modules can be used for this. +- A Set AS Path action rule with criteria set to C(prepend) must exist before using this module in your playbook. + The M(cisco.aci.aci_action_rule_set_as_path) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_tenant_action_rule_profile +- module: cisco.aci.aci_action_rule_set_as_path +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:SetASPathASN). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a Set AS Path ASN action rule + cisco.aci.aci_action_rule_set_as_path_asn: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + order: 1 + asn: 1 + state: present + delegate_to: localhost + +- name: Delete a Set AS Path ASN action rule + cisco.aci.aci_action_rule_set_as_path_asn: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + order: 1 + state: absent + delegate_to: localhost + +- name: Query all Set AS Path ASN action rules + cisco.aci.aci_action_rule_set_as_path_asn: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a Set AS Path ASN action rule + cisco.aci.aci_action_rule_set_as_path_asn: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + order: 1 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + action_rule=dict(type="str", aliases=["action_rule_name"]), # Not required for querying all objects + asn=dict(type="int"), + order=dict(type="int"), + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["action_rule", "tenant", "order"]], + ["state", "present", ["action_rule", "tenant", "order"]], + ], + ) + + asn = module.params.get("asn") + order = module.params.get("order") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + action_rule = module.params.get("action_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlAttrP", + aci_rn="attr-{0}".format(action_rule), + module_object=action_rule, + target_filter={"name": action_rule}, + ), + subclass_2=dict( + aci_class="rtctrlSetASPath", + aci_rn="saspath-prepend", + module_object="prepend", + target_filter={"criteria": "prepend"}, + ), + subclass_3=dict( + aci_class="rtctrlSetASPathASN", + aci_rn="asn-{0}".format(order), + module_object=order, + target_filter={"asn": order}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlSetASPathASN", + class_config=dict( + asn=asn, + order=order, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlSetASPathASN") + + 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 index aa77d8f4f..04ecf58f6 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_aep.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aep.py @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_aep -short_description: Manage attachable Access Entity Profile (AEP) objects (infra:AttEntityP, infra:ProvAcc) +short_description: Manage attachable Access Entity Profile (AEP) objects (infra:AttEntityP and infra:ProvAcc) description: - Connect to external virtual and physical domains by using attachable Access Entity Profiles (AEP) on Cisco ACI fabrics. 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 index c77417073..2bba28c5b 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_epg.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_aep_to_epg.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_aep_to_epg -short_description: Bind EPG to AEP (infra:RsFuncToEpg). +short_description: Bind EPG to AEP (infra:RsFuncToEpg) description: - Bind EPG to AEP. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bd.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd.py index 13d9d1938..d01304cb7 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_bd.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd.py @@ -1,6 +1,8 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2017, Jacob McGill (@jmcgill298) +# Copyright: (c) 2024, Samita Bhattacharjee (@samitab) <samitab@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 @@ -147,6 +149,103 @@ options: description: - The L3 Out that contains the associated Route Profile. type: str + host_based_routing: + description: + - Enables advertising host routes (/32 prefixes) out of the L3OUT(s) that are associated to this BD. + - The APIC defaults to C(false) when unset during creation. + type: bool + aliases: [ advertise_host_routes ] + enable_rogue_except_mac: + description: + - Rogue exception MAC wildcard support for Bridge Domains. + - Only available in APIC version 6.0 or later. + - The APIC defaults to C(false) when unset during creation. + type: bool + allow_intersite_bum_traffic: + description: + - Control whether BUM traffic is allowed between sites. + - The APIC defaults to C(false) when unset during creation. + type: bool + aliases: [allow_bum_traffic] + allow_intersite_l2_stretch: + description: + - Allow L2 Stretch between sites. + - The APIC defaults to C(false) when unset during creation. + type: bool + aliases: [allow_l2_stretch] + allow_ipv6_multicast: + description: + - Flag to indicate if ipv6 multicast is enabled. + - The APIC defaults to C(false) when unset during creation. + type: bool + aliases: [ ipv6_multicast, ipv6_mcast, allow_ipv6_mcast] + link_local_address: + description: + - The override of the system generated IPv6 link-local address. + type: str + aliases: [ ll_addr_ipv6, ll_addr, link_local] + multicast_arp_drop: + description: + - Enable BD rogue multicast ARP packet drop. + - Only available in APIC version 6.0 or later. + - The APIC defaults to C(true) when unset during creation. + type: bool + aliases: [ mcast_arp_drop ] + vmac: + description: + - Virtual MAC address of the BD/SVI. This is used when the BD is extended to multiple sites using L2 Outside. + type: str + optimize_wan_bandwidth: + description: + - Optimize WAN Bandwidth improves the network application experience at the branch and makes better use of limited network resources. + - The APIC defaults to C(false) when unset during creation. + type: bool + aliases: [wan_optimization, opt_bandwidth] + mld_snoop_policy: + description: + - The name of the Multicast Listener Discovery (MLD) Snooping Policy the Bridge Domain should use when overriding the default MLD Snooping Policy. + - To delete this attribute, pass an empty string. + type: str + aliases: [mld_snoop, mld_policy] + igmp_policy: + description: + - The name of the IGMP Interface Policy the Bridge Domain should use when overriding the default IGMP Interface Policy. + - To delete this attribute, pass an empty string. + type: str + aliases: [igmp] + vlan: + description: + - The selected VLAN for bridge domain access port encapsulation. + - To delete this attribute, pass an empty string. + type: str + aliases: [encap] + monitoring_policy: + description: + - The name of the Monitoring Policy to apply to the Bridge Domain. + - To delete this attribute, pass an empty string. + type: str + aliases: [mon_pol, monitoring_pol] + first_hop_security_policy: + description: + - The name of the First Hop Security Policy to apply to the Bridge Domain. + - To delete this attribute, pass an empty string. + type: str + aliases: [fhsp, fhs_pol, fhsp_name] + pim_source_filter: + description: + - The name of the PIM Source Filter to apply to the Bridge Domain. + - To delete this attribute, pass an empty string. + - Only available in APIC version 5.2 or later. + type: str + aliases: [pim_source] + pim_destination_filter: + description: + - The name of the PIM Destination Filter to apply to the Bridge Domain. + - To delete this attribute, pass an empty string. + - Only available in APIC version 5.2 or later. + type: str + aliases: [pim_dest, pim_destination] + extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation @@ -162,6 +261,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Jacob McGill (@jmcgill298) +- Samita Bhattacharjee (@samitab) """ EXAMPLES = r""" @@ -175,6 +275,19 @@ EXAMPLES = r""" bd: web_servers mac_address: 00:22:BD:F8:19:FE vrf: prod_vrf + host_based_routing: true + allow_intersite_bum_traffic: true + allow_intersite_l2_stretch: true + allow_ipv6_mcast: true + ll_addr: "fe80::1322:33ff:fe44:5566" + vmac: "00:AA:BB:CC:DD:03" + optimize_wan_bandwidth: true + vlan: vlan-101 + igmp_policy: web_servers_igmp_pol + monitoring_policy: web_servers_monitoring_pol + igmp_snoop_policy: web_servers_igmp_snoop + mld_snoop_policy: web_servers_mld_snoop + first_hop_security_policy: web_servers_fhs state: present delegate_to: localhost @@ -205,6 +318,21 @@ EXAMPLES = r""" state: present delegate_to: localhost +- name: Modify a Bridge Domain to remove mld_snoop_policy and first_hop_security_policy + 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 + mld_snoop_policy: "" + first_hop_security_policy: "" + state: present + delegate_to: localhost + - name: Query All Bridge Domains cisco.aci.aci_bd: host: "{{ inventory_hostname }}" @@ -378,6 +506,22 @@ def main(): route_profile=dict(type="str"), route_profile_l3out=dict(type="str"), name_alias=dict(type="str"), + host_based_routing=dict(type="bool", aliases=["advertise_host_routes"]), + enable_rogue_except_mac=dict(type="bool"), + allow_intersite_bum_traffic=dict(type="bool", aliases=["allow_bum_traffic"]), + allow_intersite_l2_stretch=dict(type="bool", aliases=["allow_l2_stretch"]), + allow_ipv6_multicast=dict(type="bool", aliases=["ipv6_multicast", "ipv6_mcast", "allow_ipv6_mcast"]), + link_local_address=dict(type="str", aliases=["ll_addr_ipv6", "ll_addr", "link_local"]), + multicast_arp_drop=dict(type="bool", aliases=["mcast_arp_drop"]), + vmac=dict(type="str"), + optimize_wan_bandwidth=dict(type="bool", aliases=["wan_optimization", "opt_bandwidth"]), + mld_snoop_policy=dict(type="str", aliases=["mld_snoop", "mld_policy"]), + igmp_policy=dict(type="str", aliases=["igmp"]), + vlan=dict(type="str", aliases=["encap"]), + monitoring_policy=dict(type="str", aliases=["mon_pol", "monitoring_pol"]), + first_hop_security_policy=dict(type="str", aliases=["fhsp", "fhs_pol", "fhsp_name"]), + pim_source_filter=dict(type="str", aliases=["pim_source"]), + pim_destination_filter=dict(type="str", aliases=["pim_dest", "pim_destination"]), ) module = AnsibleModule( @@ -422,7 +566,47 @@ def main(): route_profile = module.params.get("route_profile") route_profile_l3out = module.params.get("route_profile_l3out") name_alias = module.params.get("name_alias") + host_based_routing = aci.boolean(module.params.get("host_based_routing")) + enable_rogue_except_mac = aci.boolean(module.params.get("enable_rogue_except_mac")) + allow_intersite_bum_traffic = aci.boolean(module.params.get("allow_intersite_bum_traffic")) + allow_intersite_l2_stretch = aci.boolean(module.params.get("allow_intersite_l2_stretch")) + allow_ipv6_multicast = aci.boolean(module.params.get("allow_ipv6_multicast")) + link_local_address = module.params.get("link_local_address") + multicast_arp_drop = aci.boolean(module.params.get("multicast_arp_drop")) + vmac = module.params.get("vmac") + optimize_wan_bandwidth = aci.boolean(module.params.get("optimize_wan_bandwidth")) + mld_snoop_policy = module.params.get("mld_snoop_policy") + igmp_policy = module.params.get("igmp_policy") + vlan = module.params.get("vlan") + monitoring_policy = module.params.get("monitoring_policy") + first_hop_security_policy = module.params.get("first_hop_security_policy") + pim_source_filter = module.params.get("pim_source_filter") + pim_destination_filter = module.params.get("pim_destination_filter") + child_classes = [ + "fvRsCtx", + "fvRsIgmpsn", + "fvRsBDToNdP", + "fvRsBdToEpRet", + "fvRsBDToProfile", + "fvRsMldsn", + "igmpIfP", + "igmpRsIfPol", + "fvAccP", + "fvRsABDPolMonPol", + "fvRsBDToFhs", + ] + if pim_source_filter is not None or pim_destination_filter is not None: + # Only valid for APIC verion 5.2+ + child_classes.extend( + [ + "pimBDP", + "pimBDFilterPol", + "pimBDSrcFilterPol", + "pimBDDestFilterPol", + "rtdmcRsFilterToRtMapPol", + ] + ) aci.construct_url( root_class=dict( aci_class="fvTenant", @@ -436,7 +620,7 @@ def main(): module_object=bd, target_filter={"name": bd}, ), - child_classes=["fvRsCtx", "fvRsIgmpsn", "fvRsBDToNdP", "fvRsBdToEpRet", "fvRsBDToProfile"], + child_classes=child_classes, ) aci.get_existing() @@ -458,22 +642,52 @@ def main(): unkMacUcastAct=l2_unknown_unicast, unkMcastAct=l3_unknown_multicast, nameAlias=name_alias, + enableRogueExceptMac=enable_rogue_except_mac, + hostBasedRouting=host_based_routing, + intersiteBumTrafficAllow=allow_intersite_bum_traffic, + intersiteL2Stretch=allow_intersite_l2_stretch, + ipv6McastAllow=allow_ipv6_multicast, + llAddr=link_local_address, + mcastARPDrop=multicast_arp_drop, + vmac=vmac, + OptimizeWanBandwidth=optimize_wan_bandwidth, ) 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}}}, - ], - ) + child_configs = [ + {"fvRsCtx": {"attributes": {"tnFvCtxName": vrf}}}, + {"fvRsIgmpsn": {"attributes": {"tnIgmpSnoopPolName": igmp_snoop_policy}}}, + {"fvRsMldsn": {"attributes": {"tnMldSnoopPolName": mld_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}}}, + {"fvRsBDToFhs": {"attributes": {"tnFhsBDPolName": first_hop_security_policy}}}, + {"fvAccP": {"attributes": {"encap": vlan}}}, + {"fvRsABDPolMonPol": {"attributes": {"tnMonEPGPolName": monitoring_policy}}}, + ] + + if igmp_policy is not None: + igmp_policy_tdn = "" if igmp_policy == "" else "uni/tn-{0}/igmpIfPol-{1}".format(tenant, igmp_policy) + child_configs.append({"igmpIfP": {"attributes": {}, "children": [{"igmpRsIfPol": {"attributes": {"tDn": igmp_policy_tdn}}}]}}) + if pim_source_filter is not None or pim_destination_filter is not None: + pim_bd = {"pimBDP": {"attributes": {}, "children": []}} + pim_filter_pol = {"pimBDFilterPol": {"attributes": {}, "children": []}} + if pim_source_filter is not None: + pim_source_filter_tdn = "" if pim_source_filter == "" else "uni/tn-{0}/rtmap-{1}".format(tenant, pim_source_filter) + pim_filter_pol["pimBDFilterPol"]["children"].append( + {"pimBDSrcFilterPol": {"attributes": {}, "children": [{"rtdmcRsFilterToRtMapPol": {"attributes": {"tDn": pim_source_filter_tdn}}}]}} + ) + if pim_destination_filter is not None: + pim_destination_filter_tdn = "" if pim_destination_filter == "" else "uni/tn-{0}/rtmap-{1}".format(tenant, pim_destination_filter) + pim_filter_pol["pimBDFilterPol"]["children"].append( + {"pimBDDestFilterPol": {"attributes": {}, "children": [{"rtdmcRsFilterToRtMapPol": {"attributes": {"tDn": pim_destination_filter_tdn}}}]}} + ) + pim_bd["pimBDP"]["children"].append(pim_filter_pol) + child_configs.append(pim_bd) + + aci.payload(aci_class="fvBD", class_config=class_config, child_configs=child_configs) aci.get_diff(aci_class="fvBD") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bd_rogue_exception_mac.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd_rogue_exception_mac.py new file mode 100644 index 000000000..8174f00db --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd_rogue_exception_mac.py @@ -0,0 +1,299 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Samita Bhattacharjee (@samitab) <samitab@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_bd_rogue_exception_mac +short_description: Manage Rogue Exception MAC (fv:RogueExceptionMac) +description: +- Manage Rogue Exception MACs in BD's on Cisco ACI fabrics. +- Only available in APIC version 5.2 or later. +options: + bd: + description: + - The name of the Bridge Domain. + type: str + aliases: [ bd_name, bridge_domain ] + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] + mac: + description: + - MAC address to except from Rogue processing. + type: str + description: + description: + - The description for the Rogue Exception MAC. + 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 + +notes: +- 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:RogueExceptionMac). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +EXAMPLES = r""" +- name: Create a Rogue Exception MAC + cisco.aci.aci_bd_rogue_exception_mac: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + mac: "AA:BB:CC:DD:EE:11" + description: 1st MAC + state: present + delegate_to: localhost + +- name: Get all Rogue Exception MACs + cisco.aci.aci_bd_rogue_exception_mac: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Get all Rogue Exception MACs in specified Tenant + cisco.aci.aci_bd_rogue_exception_mac: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + state: query + delegate_to: localhost + register: query_result + +- name: Get specific Rogue Exception MAC + cisco.aci.aci_bd_rogue_exception_mac: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + mac: "AA:BB:CC:DD:EE:11" + state: query + delegate_to: localhost + register: query_result + +- name: Remove a Rogue Exception MAC from a Bridge Domain + cisco.aci.aci_bd_rogue_exception_mac: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + bd: database + mac: "AA:BB:CC:DD:EE:11" + 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( + bd=dict(type="str", aliases=["bd_name", "bridge_domain"]), # Not required for querying all objects + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + mac=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"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["bd", "mac", "tenant"]], + ["state", "absent", ["bd", "mac", "tenant"]], + ], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + tenant = module.params.get("tenant") + bd = module.params.get("bd") + mac = module.params.get("mac") + 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="fvBD", + aci_rn="BD-{0}".format(bd), + module_object=bd, + target_filter={"name": bd}, + ), + subclass_2=dict( + aci_class="fvRogueExceptionMac", + aci_rn="rgexpmac-{0}".format(mac), + module_object=mac, + target_filter={"mac": mac}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvRogueExceptionMac", + class_config=dict( + descr=description, + mac=mac, + ), + ) + + aci.get_diff(aci_class="fvRogueExceptionMac") + + 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_netflow_monitor_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bd_to_netflow_monitor_policy.py new file mode 100644 index 000000000..5e79a072c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bd_to_netflow_monitor_policy.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Samita Bhattacharjee (@samitab) <samitab@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_bd_to_netflow_monitor_policy +short_description: Bind Bridge Domain to Netflow Monitor Policy (fv:RsBDToNetflowMonitorPol) +description: +- Bind Bridge Domain to Netflow Monitor Policy on Cisco ACI fabrics. +options: + bd: + description: + - The name of the Bridge Domain. + type: str + aliases: [ bd_name, bridge_domain ] + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] + netflow_monitor_policy: + description: + - The name of the Netflow Monitor Policy. + type: str + aliases: [ netflow_monitor, netflow_monitor_name, name ] + filter_type: + description: + - Choice of filter type while setting NetFlow Monitor Policies. + type: str + choices: [ce, ipv4, ipv6, unspecified] + aliases: [ filter, 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(bd) and C(netflow_monitor_policy) parameters should exist before using this module. + The M(cisco.aci.aci_bd) and C(aci_netflow_monitor_policy) can be used for this. +seealso: +- module: cisco.aci.aci_bd +- module: cisco.aci.aci_netflow_monitor_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:RsBDToNetflowMonitorPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +EXAMPLES = r""" +- name: Bind Bridge Domain to Netflow Monitor Policy + cisco.aci.aci_bd_to_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: false + bd: web_servers + netflow_monitor_policy: prod_netflow_monitor_policy + tenant: prod + filter_type: ipv4 + state: present + delegate_to: localhost + +- name: Query all Bridge Domains bound to Netflow Monitor Policy + cisco.aci.aci_bd_to_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: true + state: query + delegate_to: localhost + register: query_result + +- name: Query specific Bridge Domain(s) bound to an Netflow Monitor Policy + cisco.aci.aci_bd_to_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: true + bd: web_servers + netflow_monitor_policy: prod_netflow_monitor_policy + tenant: prod + state: query + delegate_to: localhost + register: query_result + +- name: Unbind Bridge Domain from Netflow Monitor Policy + cisco.aci.aci_bd_to_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + validate_certs: true + bd: web_servers + netflow_monitor_policy: prod_netflow_monitor_policy + tenant: prod + filter_type: ipv4 + 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( + bd=dict(type="str", aliases=["bd_name", "bridge_domain"]), # Not required for querying all objects + netflow_monitor_policy=dict(type="str", aliases=["netflow_monitor", "netflow_monitor_name", "name"]), # Not required for querying all objects + filter_type=dict(type="str", choices=["ce", "ipv4", "ipv6", "unspecified"], aliases=["filter", "type"]), # 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", "netflow_monitor_policy", "tenant", "filter_type"]], + ["state", "absent", ["bd", "netflow_monitor_policy", "tenant", "filter_type"]], + ], + ) + + bd = module.params.get("bd") + netflow_monitor_policy = module.params.get("netflow_monitor_policy") + filter_type = module.params.get("filter_type") + 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="fvRsBDToNetflowMonitorPol", + aci_rn="rsBDToNetflowMonitorPol-[{0}]-{1}".format(netflow_monitor_policy, filter_type), + module_object=netflow_monitor_policy, + target_filter={"tnNetflowMonitorPolName": netflow_monitor_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvRsBDToNetflowMonitorPol", + class_config=dict(tnNetflowMonitorPolName=netflow_monitor_policy, fltType=filter_type), + ) + + aci.get_diff(aci_class="fvRsBDToNetflowMonitorPol") + + 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_bfd_multihop_node_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bfd_multihop_node_policy.py new file mode 100644 index 000000000..f684ba15d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bfd_multihop_node_policy.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain (@anvjain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_bfd_multihop_node_policy +short_description: Manage BFD Multihop Node policies (bfd:MhNodePol) +description: +- Manage BFD Multihop Node policy configuration on Cisco ACI fabrics. +- Only available in APIC version 5.2 or later. +options: + tenant: + description: + - Name of an existing tenant + type: str + name: + description: + - Name of the BFD Multihop Node policy + type: str + aliases: [ bfd_multihop_node_policy ] + description: + description: + - Description of the BFD Multihop Node policy + type: str + admin_state: + description: + - Admin state of the BFD Multihop Node policy + - APIC sets the default value to enabled + type: str + choices: [ enabled, disabled ] + detection_multiplier: + description: + - Detection multiplier of the BFD Multihop Node policy + - APIC sets the default value to 3 + - Allowed range is 1-50 + type: int + min_transmit_interval: + description: + - Minimum transmit (Tx) interval of the BFD Multihop Node policy + - APIC sets the default value to 250 + - Allowed range is 250-999 + type: int + min_receive_interval: + description: + - Minimum receive (Rx) interval of the BFD Multihop Node policy + - APIC sets the default value to 250 + - Allowed range is 250-999 + 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(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(bfd:MhNodePol) + link: https://developer.cisco.com/docs/apic-mim-ref/ +- module: cisco.aci.aci_tenant +author: +- Anvitha Jain (@anvjain) +""" + +EXAMPLES = r""" +- name: Add a new BFD Multihop Node policy + cisco.aci.aci_bfd_multihop_node_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: ansible_bfd_multihop_node_policy + description: Ansible BFD Multihop Node Policy + state: present + delegate_to: localhost + +- name: Remove a BFD Multihop Node policy + cisco.aci.aci_bfd_multihop_node_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: ansible_bfd_multihop_node_policy + state: absent + delegate_to: localhost + +- name: Query a BFD Multihop Node policy + cisco.aci.aci_bfd_multihop_node_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: my_dhcp_relay + state: query + delegate_to: localhost + register: query_result + +- name: Query all BFD Multihop Node policies in a specific tenant + cisco.aci.aci_bfd_multihop_node_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_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 "/></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=["bfd_multihop_node_policy"]), + description=dict(type="str"), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + detection_multiplier=dict(type="int"), + min_transmit_interval=dict(type="int"), + min_receive_interval=dict(type="int"), + 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") + admin_state = module.params.get("admin_state") + detection_multiplier = module.params.get("detection_multiplier") + min_transmit_interval = module.params.get("min_transmit_interval") + min_receive_interval = module.params.get("min_receive_interval") + + 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="bfdMhNodePol", + aci_rn="bfdMhNodePol-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + name=name, + descr=description, + adminSt=admin_state, + ) + + if detection_multiplier and detection_multiplier not in range(1, 50): + aci.fail_json(msg='The "detection_multiplier" must be a value between 1 and 50') + else: + class_config["detectMult"] = detection_multiplier + if min_transmit_interval and min_transmit_interval not in range(250, 999): + aci.fail_json(msg='The "min_transmit_interval" must be a value between 250 and 999') + else: + class_config["minTxIntvl"] = min_transmit_interval + if min_receive_interval and min_receive_interval not in range(250, 999): + aci.fail_json(msg='The "min_receive_interval" must be a value between 250 and 999') + else: + class_config["minRxIntvl"] = min_receive_interval + + aci.payload( + aci_class="bfdMhNodePol", + class_config=class_config, + ) + + aci.get_diff(aci_class="bfdMhNodePol") + + 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_address_family_context_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_address_family_context_policy.py new file mode 100644 index 000000000..1d69de6f6 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_address_family_context_policy.py @@ -0,0 +1,382 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_bgp_address_family_context_policy +short_description: Manage BGP address family context policy (bgp:CtxAfPol) +description: +- Manage BGP address family context policies for the Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + address_family_context_policy: + description: + - The name of the BGP address family context policy. + type: str + aliases: [ address_family_context_name, name ] + host_route_leak: + description: + - The control state. + - The option to enable/disable host route leak. + - The APIC defaults to C(false) when unset during creation. + type: bool + ebgp_distance: + description: + - The administrative distance of eBGP routes. + - The APIC defaults to C(20) when unset during creation. + type: int + ibgp_distance: + description: + - The administrative distance of iBGP routes. + - The APIC defaults to C(200) when unset during creation. + type: int + local_distance: + description: + - The administrative distance of local routes. + - The APIC defaults to C(220) when unset during creation. + type: int + ebgp_max_ecmp: + description: + - The eBGP max-path. + - The APIC defaults to C(16) when unset during creation. + type: int + ibgp_max_ecmp: + description: + - The iBGP max-path. + - The APIC defaults to C(16) when unset during creation. + type: int + local_max_ecmp: + description: + - The maximum number of equal-cost local paths for redist. + - The APIC defaults to C(0) when unset during creation. + - Can not be configured for APIC version 4.2(7s) and prior. + type: int + bgp_add_path_capability: + description: + - The neighbor system capability. + - To delete this attribute, pass an empty string. + - Can not be configured for APIC version 6.0(2h) and prior. + type: str + choices: [ receive, send, "" ] + description: + description: + - Description for the BGP protocol profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(bgp:CtxAfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a BGP address family context policy + cisco.aci.aci_bgp_address_family_context_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_address_family_context_policy: my_bgp_address_family_context_policy + host_route_leak: true + ebgp_distance: 40 + ibgp_distance: 210 + local_distance: 215 + ebgp_max_ecmp: 32 + ibgp_max_ecmp: 32 + local_max_ecmp: 1 + bgp_add_path_capability: receive + tenant: production + state: present + delegate_to: localhost + +- name: Delete BGP address family context policy's child + cisco.aci.aci_bgp_address_family_context_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_address_family_context_policy: my_bgp_address_family_context_policy + bgp_add_path_capability: "" + tenant: production + state: absent + delegate_to: localhost + +- name: Delete a BGP address family context policy + cisco.aci.aci_bgp_address_family_context_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_address_family_context_policy: my_bgp_address_family_context_policy + tenant: production + state: absent + delegate_to: localhost + +- name: Query all BGP address family context policies + cisco.aci.aci_bgp_address_family_context_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific BGP address family context policy + cisco.aci.aci_bgp_address_family_context_policy: + host: apic + username: admin + password: SomeSecretPassword + bgp_address_family_context_policy: my_bgp_address_family_context_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 + address_family_context_policy=dict(type="str", aliases=["address_family_context_name", "name"]), # Not required for querying all objects + host_route_leak=dict(type="bool"), + ebgp_distance=dict(type="int"), + ibgp_distance=dict(type="int"), + local_distance=dict(type="int"), + ebgp_max_ecmp=dict(type="int"), + ibgp_max_ecmp=dict(type="int"), + local_max_ecmp=dict(type="int"), + bgp_add_path_capability=dict(type="str", choices=["receive", "send", ""]), + 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", ["address_family_context_policy", "tenant"]], + ["state", "present", ["address_family_context_policy", "tenant"]], + ], + ) + + aci = ACIModule(module) + + address_family_context_policy = module.params.get("address_family_context_policy") + host_route_leak = aci.boolean(module.params.get("host_route_leak"), "host-rt-leak", "") + ebgp_distance = module.params.get("ebgp_distance") + ibgp_distance = module.params.get("ibgp_distance") + local_distance = module.params.get("local_distance") + ebgp_max_ecmp = module.params.get("ebgp_max_ecmp") + ibgp_max_ecmp = module.params.get("ibgp_max_ecmp") + local_max_ecmp = module.params.get("local_max_ecmp") + bgp_add_path_capability = module.params.get("bgp_add_path_capability") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + + child_classes = [] + if bgp_add_path_capability is not None: + child_classes.append("bgpCtxAddlPathPol") + + 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="bgpCtxAfPol", + aci_rn="bgpCtxAfP-{0}".format(address_family_context_policy), + module_object=address_family_context_policy, + target_filter={"name": address_family_context_policy}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if bgp_add_path_capability is not None: + if bgp_add_path_capability == "" and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("bgpCtxAfPol", {}).get("children", {}): + if child.get("bgpCtxAddlPathPol"): + child_configs.append(dict(bgpCtxAddlPathPol=dict(attributes=dict(status="deleted")))) + elif bgp_add_path_capability != "": + child_configs.append(dict(bgpCtxAddlPathPol=dict(attributes=dict(capability=bgp_add_path_capability)))) + + aci.payload( + aci_class="bgpCtxAfPol", + class_config=dict( + name=address_family_context_policy, + ctrl=host_route_leak, + eDist=ebgp_distance, + iDist=ibgp_distance, + localDist=local_distance, + maxEcmp=ebgp_max_ecmp, + maxEcmpIbgp=ibgp_max_ecmp, + maxLocalEcmp=local_max_ecmp, + descr=description, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="bgpCtxAfPol") + + 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_best_path_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py index a8b5c0a6a..936442aaa 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_best_path_policy.py @@ -15,7 +15,7 @@ DOCUMENTATION = r""" module: aci_bgp_best_path_policy short_description: Manage BGP Best Path policy (bgp:BestPathCtrlPol) description: -- Manage BGP Best Path policies for Tenants on Cisco ACI fabrics. +- Manage BGP Best Path policies for the Tenants on Cisco ACI fabrics. options: tenant: description: @@ -24,7 +24,7 @@ options: aliases: [ tenant_name ] bgp_best_path_policy: description: - - The name of the best path policy. + - The name of the BGP best path policy. type: str aliases: [ bgp_best_path_policy_name, name ] best_path_control: @@ -37,7 +37,7 @@ options: aliases: [as_path_control] description: description: - - Description for the bgp protocol profile. + - Description for the BGP best path policy. type: str aliases: [ descr ] state: @@ -74,7 +74,7 @@ EXAMPLES = r""" host: apic username: admin password: SomeSecretPassword - bgp_protocol_profile: my_bgp_best_path_policy + bgp_best_path_policy: my_bgp_best_path_policy best_path_control: enable tenant: production state: present @@ -85,7 +85,7 @@ EXAMPLES = r""" host: apic username: admin password: SomeSecretPassword - bgp_protocol_profile: my_bgp_best_path_policy + bgp_best_path_policy: my_bgp_best_path_policy tenant: production state: absent delegate_to: localhost @@ -104,7 +104,7 @@ EXAMPLES = r""" host: apic username: admin password: SomeSecretPassword - bgp_protocol_profile: my_bgp_best_path_policy + bgp_best_path_policy: my_bgp_best_path_policy tenant: production state: query delegate_to: localhost diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_peer_prefix_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_peer_prefix_policy.py new file mode 100644 index 000000000..7719fff7a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_peer_prefix_policy.py @@ -0,0 +1,322 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_bgp_peer_prefix_policy +short_description: Manage BGP peer prefix policy (bgp:PeerPfxPol) +description: +- Manage BGP peer prefix policies for the Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + peer_prefix_policy: + description: + - The name of the BGP peer prefix policy. + type: str + aliases: [ peer_prefix_policy_name, name ] + action: + description: + - The action to be performed when the maximum prefix limit is reached. + - The APIC defaults to C(reject) when unset during creation. + type: str + choices: [ log, reject, restart, shut ] + maximum_number_prefix: + description: + - The maximum number of prefixes allowed from the peer. + - The APIC defaults to C(20000) when unset during creation. + type: int + aliases: [ max_prefix, max_num_prefix ] + restart_time: + description: + - The period of time in minutes before restarting the peer when the prefix limit is reached. + - Used only if C(action) is set to C(restart). + - The APIC defaults to C(infinite) when unset during creation. + type: str + threshold: + description: + - The threshold percentage of the maximum number of prefixes before a warning is issued. + - For example, if the maximum number of prefixes is 10 and the threshold is 70%, a warning is issued when the number of prefixes exceeds 7 (70%). + - The APIC defaults to C(75) when unset during creation. + type: int + aliases: [ thresh ] + description: + description: + - Description for the BGP peer prefix 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(bgp:PeerPfxPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a BGP peer prefix policy + cisco.aci.aci_bgp_peer_prefix_policy: + host: apic + username: admin + password: SomeSecretPassword + peer_prefix_policy: my_bgp_peer_prefix_policy + action: restart + restart_time: 10 + max_prefix: 10000 + threshold: 80 + tenant: production + state: present + delegate_to: localhost + +- name: Delete a BGP peer prefix policy + cisco.aci.aci_bgp_peer_prefix_policy: + host: apic + username: admin + password: SomeSecretPassword + peer_prefix_policy: my_bgp_peer_prefix_policy + tenant: production + state: absent + delegate_to: localhost + +- name: Query all BGP peer prefix policies + cisco.aci.aci_bgp_peer_prefix_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific BGP peer prefix policy + cisco.aci.aci_bgp_peer_prefix_policy: + host: apic + username: admin + password: SomeSecretPassword + peer_prefix_policy: my_bgp_peer_prefix_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 + peer_prefix_policy=dict(type="str", aliases=["peer_prefix_policy_name", "name"]), # Not required for querying all objects + action=dict(type="str", choices=["log", "reject", "restart", "shut"]), + maximum_number_prefix=dict(type="int", aliases=["max_prefix", "max_num_prefix"]), + restart_time=dict(type="str"), + threshold=dict(type="int", aliases=["thresh"]), + 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", ["peer_prefix_policy", "tenant"]], + ["state", "present", ["peer_prefix_policy", "tenant"]], + ], + ) + + peer_prefix_policy = module.params.get("peer_prefix_policy") + action = module.params.get("action") + maximum_number_prefix = module.params.get("maximum_number_prefix") + restart_time = module.params.get("restart_time") + threshold = module.params.get("threshold") + 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="bgpPeerPfxPol", + aci_rn="bgpPfxP-{0}".format(peer_prefix_policy), + module_object=peer_prefix_policy, + target_filter={"name": peer_prefix_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="bgpPeerPfxPol", + class_config=dict( + name=peer_prefix_policy, + action=action, + maxPfx=maximum_number_prefix, + restartTime=restart_time, + thresh=threshold, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="bgpPeerPfxPol") + + 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_route_summarization_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_route_summarization_policy.py new file mode 100644 index 000000000..6e10e92c6 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_route_summarization_policy.py @@ -0,0 +1,310 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_bgp_route_summarization_policy +short_description: Manage BGP route summarization policy (bgp:RtSummPol) +description: +- Manage BGP route summarization policies for the Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + route_summarization_policy: + description: + - The name of the BGP route summarization policy. + type: str + aliases: [ route_summarization_policy_name, name ] + address_type_af_control: + description: + - The Ucast/Mcast address type AF control. + - The APIC defaults to C(af-ucast) when unset during creation. + - Can not be configured for APIC version 4.2(7s) or prior. + type: list + elements: str + choices: [ af-label-ucast, af-ucast, af-mcast ] + aliases: [ address_type_control ] + control_state: + description: + - The summary control. + - The C(summary_only) option can not be configured for APIC version 4.2(7s) or prior. + type: list + elements: str + choices: [ as-set, summary-only ] + aliases: [ summary_control, control ] + description: + description: + - Description for the BGP route summarization 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(bgp:RtSummPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a BGP route summarization policy + cisco.aci.aci_bgp_route_summarization_policy: + host: apic + username: admin + password: SomeSecretPassword + route_summarization_policy: my_route_summarization_policy + address_type_af_control: [af-mcast, af-ucast] + control_state: [as-set, summary-only] + tenant: production + state: present + delegate_to: localhost + +- name: Delete a BGP route summarization policy + cisco.aci.aci_bgp_route_summarization_policy: + host: apic + username: admin + password: SomeSecretPassword + route_summarization_policy: my_route_summarization_policy + tenant: production + state: absent + delegate_to: localhost + +- name: Query all BGP route summarization policies + cisco.aci.aci_bgp_route_summarization_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific BGP route summarization policy + cisco.aci.aci_bgp_route_summarization_policy: + host: apic + username: admin + password: SomeSecretPassword + route_summarization_policy: my_route_summarization_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 + route_summarization_policy=dict(type="str", aliases=["route_summarization_policy_name", "name"]), # Not required for querying all objects + address_type_af_control=dict(type="list", elements="str", choices=["af-label-ucast", "af-ucast", "af-mcast"], aliases=["address_type_control"]), + control_state=dict(type="list", elements="str", choices=["as-set", "summary-only"], aliases=["summary_control", "control"]), + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["route_summarization_policy", "tenant"]], + ["state", "present", ["route_summarization_policy", "tenant"]], + ], + ) + + route_summarization_policy = module.params.get("route_summarization_policy") + address_type_af_control = ",".join(module.params.get("address_type_af_control")) if module.params.get("address_type_af_control") else None + control_state = ",".join(module.params.get("control_state")) if module.params.get("control_state") else None + 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="bgpRtSummPol", + aci_rn="bgprtsum-{0}".format(route_summarization_policy), + module_object=route_summarization_policy, + target_filter={"name": route_summarization_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + name=route_summarization_policy, + ctrl=control_state, + descr=description, + nameAlias=name_alias, + ) + + if address_type_af_control is not None: + class_config.update(dict(addrTCtrl=address_type_af_control)) + + aci.payload( + aci_class="bgpRtSummPol", + class_config=class_config, + ) + + aci.get_diff(aci_class="bgpRtSummPol") + + 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 index 4eae25e3d..e728e41b6 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_rr_asn.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_rr_asn.py @@ -13,9 +13,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_bgp_rr_asn -short_description: Manage BGP Route Reflector ASN. +short_description: Manage BGP Route Reflector ASN (bgp:AsP) description: -- Manage the BGP Autonomous System Number of the fabric (bgpAsP). +- Manage the BGP Autonomous System Number of the fabric. - This module is specifically for fabric BGP, for L3Out BGP use the aci_l3out_bgp_peer module options: asn: @@ -35,7 +35,7 @@ extends_documentation_fragment: seealso: - name: APIC Management Information Model reference - description: More information about the internal APIC class B(bgpAsP). + description: More information about the internal APIC class B(bgp:AsP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) 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 index 1f37b769c..90139b026 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_rr_node.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_rr_node.py @@ -13,9 +13,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_bgp_rr_node -short_description: Manage BGP Route Reflector objects. +short_description: Manage BGP Route Reflector objects (bgp:RRNodePEp) description: -- Manage ACI BGP Route Reflector Nodes (bgpRRNodePEp). +- Manage ACI BGP Route Reflector Nodes. options: node_id: description: @@ -42,7 +42,7 @@ extends_documentation_fragment: seealso: - name: APIC Management Information Model reference - description: More information about the internal APIC class B(bgpRRNodePEp). + description: More information about the internal APIC class B(bgp:RRNodePEp). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py index c8d6c54b0..9a62a8d4f 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_bgp_timers_policy.py @@ -15,7 +15,7 @@ DOCUMENTATION = r""" module: aci_bgp_timers_policy short_description: Manage BGP timers policy (bgp:CtxPol) description: -- Manage BGP timers policies for Tenants on Cisco ACI fabrics. +- Manage BGP timers policies for the Tenants on Cisco ACI fabrics. options: tenant: description: @@ -24,7 +24,7 @@ options: aliases: [ tenant_name ] bgp_timers_policy: description: - - The name of the bgp timers policy. + - The name of the BGP timers policy. type: str aliases: [ bgp_timers_policy_name, name ] graceful_restart_controls: @@ -57,7 +57,7 @@ options: type: int description: description: - - Description for the bgp protocol profile. + - Description for the BGP timers policy. type: str aliases: [ descr ] state: @@ -94,7 +94,7 @@ EXAMPLES = r""" host: apic username: admin password: SomeSecretPassword - bgp_protocol_profile: my_bgp_timers_policy + bgp_timers_policy: my_bgp_timers_policy graceful_restart_controls: complete hold_interval: 360 keepalive_interval: 120 @@ -109,7 +109,7 @@ EXAMPLES = r""" host: apic username: admin password: SomeSecretPassword - bgp_protocol_profile: my_bgp_timers_policy + bgp_timers_policy: my_bgp_timers_policy tenant: production state: absent delegate_to: localhost @@ -128,7 +128,7 @@ EXAMPLES = r""" host: apic username: admin password: SomeSecretPassword - bgp_protocol_profile: my_bgp_timers_policy + bgp_timers_policy: my_bgp_timers_policy tenant: production state: query delegate_to: localhost diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_cloud_subnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_subnet.py index 31845f629..bfdad0a33 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_cloud_subnet.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_cloud_subnet.py @@ -3,6 +3,7 @@ # Copyright: (c) 2020, nkatarmal-crest <nirav.katarmal@crestdatasys.com> # Copyright: (c) 2020, Cindy Zhao <cizhao@cisco.com> +# Copyright: (c) 2024, Samita Bhattacharjee <samitab@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 @@ -21,6 +22,7 @@ notes: author: - Nirav (@nirav) - Cindy Zhao (@cizhao) +- Samita Bhattacharjee (@samitab) options: name: description: @@ -54,17 +56,24 @@ options: - Address of cloud cidr. type: str required: true - availability_zone: + aws_availability_zone: description: - The cloud zone which is attached to the given cloud context profile. - - Only used when it is an aws cloud apic. + - Only used when it is an AWS Cloud APIC. type: str + aliases: [availability_zone, av_zone, zone] vnet_gateway: description: - Determine if a vNet Gateway Router will be deployed or not. - - Only used when it is an azure cloud apic. + - Only used when it is an Azure Cloud APIC. type: bool default: false + azure_region: + description: + - The Azure cloud region to attach this subnet to. + - Only used when it is an Azure Cloud APIC. + type: str + aliases: [az_region] state: description: - Use C(present) or C(absent) for adding or removing. @@ -78,7 +87,7 @@ extends_documentation_fragment: """ EXAMPLES = r""" -- name: Create aci cloud subnet +- name: Create AWS aci cloud subnet cisco.aci.aci_cloud_subnet: host: apic username: userName @@ -87,7 +96,21 @@ EXAMPLES = r""" tenant: anstest cloud_context_profile: aws_cloudCtxProfile cidr: '10.10.0.0/16' - availability_zone: us-west-1a + aws_availability_zone: us-west-1a + address: 10.10.0.1 + delegate_to: localhost + +- name: Create Azure aci cloud subnet + cisco.aci.aci_cloud_subnet: + host: apic + username: userName + password: somePassword + validate_certs: false + tenant: anstest + cloud_context_profile: azure_cloudCtxProfile + cidr: '10.10.0.0/16' + azure_region: westus2 + vnet_gateway: true address: 10.10.0.1 delegate_to: localhost @@ -239,16 +262,18 @@ def main(): 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"), + aws_availability_zone=dict(type="str", aliases=["availability_zone", "av_zone", "zone"]), + azure_region=dict(type="str", aliases=["az_region"]), ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, required_if=[ - ["state", "absent", ["address"]], - ["state", "present", ["address"]], + ["state", "absent", ["tenant", "cloud_context_profile", "cidr", "address"]], + ["state", "present", ["tenant", "cloud_context_profile", "cidr", "address"]], ], + mutually_exclusive=[("aws_availability_zone", "azure_region")], ) name = module.params.get("name") @@ -260,7 +285,8 @@ def main(): 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") + aws_availability_zone = module.params.get("aws_availability_zone") + azure_region = module.params.get("azure_region") child_configs = [] aci = ACIModule(module) @@ -283,11 +309,14 @@ def main(): 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) + if aws_availability_zone: + region = aws_availability_zone[:-1] + tDn = "uni/clouddomp/provp-aws/region-{0}/zone-{1}".format(region, aws_availability_zone) child_configs.append({"cloudRsZoneAttach": {"attributes": {"tDn": tDn}}}) # in azure cloud apic + if azure_region: + tDn = "uni/clouddomp/provp-azure/region-{0}/zone-default".format(azure_region) + child_configs.append({"cloudRsZoneAttach": {"attributes": {"tDn": tDn}}}) if vnet_gateway: usage = "gateway" else: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py b/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py index bc36a5e62..2022c2df9 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_config_snapshot.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_config_snapshot -short_description: Manage Config Snapshots (config:Snapshot, config:ExportP) +short_description: Manage Config Snapshots (config:Snapshot and config:ExportP) description: - Manage Config Snapshots on Cisco ACI fabrics. - Creating new Snapshots is done using the configExportP class. 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 index eb8f9fa32..f50790bb3 100644 --- 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 @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_contract_subject_to_service_graph -short_description: Bind contract subject to service graph (vz:RsSubjGraphAtt). +short_description: Bind contract subject to service graph (vz:RsSubjGraphAtt) description: - Bind contract subject to service graph. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_option.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_option.py new file mode 100644 index 000000000..1253f85d6 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_option.py @@ -0,0 +1,306 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_dhcp_option +short_description: Manage DHCP Option (dhcp:Option) +description: +- Manage DHCP Options for DHCP Option Policies on Cisco ACI fabrics. +- The DHCP option is used to supply DHCP clients with configuration parameters such as a domain, name server, subnet, and network address. + DHCP provides a framework for passing configuration information to clients on a TCP/IP network. + The configuration parameters, and other control information, are carried in tagged data items that are stored in the options field of a DHCP message. + The data items themselves are also called options. You can view, set, unset, and edit DHCP option values. + When you set an option value, the DHCP server replaces any existing value or creates a new one as needed for the given option name. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + dhcp_option_policy: + description: + - The name of an existing DHCP Option Policy. + type: str + aliases: [ dhcp_option_policy_name ] + dhcp_option: + description: + - The name of the DHCP Option. + type: str + aliases: [ dhcp_option_name, name ] + data: + description: + - The value of the DHCP Option. + type: str + id: + description: + - The DHCP Option ID. + 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 +- cisco.aci.owner + +notes: +- The C(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new DHCP Option + cisco.aci.aci_dhcp_option: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + dhcp_option_policy: my_dhcp_option_policy + dhcp_option: my_dhcp_option + id: 1 + data: 82 + state: present + delegate_to: localhost + +- name: Delete an DHCP Option + cisco.aci.aci_dhcp_option: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + dhcp_option_policy: my_dhcp_option_policy + dhcp_option: my_dhcp_option + state: absent + delegate_to: localhost + +- name: Query a DHCP Option + cisco.aci.aci_dhcp_option: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + dhcp_option_policy: my_dhcp_option_policy + dhcp_option: my_dhcp_option + state: query + delegate_to: localhost + register: query_result + +- name: Query all DHCP Options in my_dhcp_option_policy + cisco.aci.aci_dhcp_option: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + dhcp_option_policy: my_dhcp_option_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"]), + dhcp_option_policy=dict(type="str", aliases=["dhcp_option_policy_name"]), + dhcp_option=dict(type="str", aliases=["dhcp_option_name", "name"]), + data=dict(type="str"), + id=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", ["tenant", "dhcp_option_policy", "dhcp_option"]], + ["state", "present", ["tenant", "dhcp_option_policy", "dhcp_option"]], + ], + ) + + tenant = module.params.get("tenant") + dhcp_option_policy = module.params.get("dhcp_option_policy") + dhcp_option = module.params.get("dhcp_option") + data = module.params.get("data") + id = module.params.get("id") + 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="dhcpOptionPol", + aci_rn="dhcpoptpol-{0}".format(dhcp_option_policy), + module_object=dhcp_option_policy, + target_filter={"name": dhcp_option_policy}, + ), + subclass_2=dict( + aci_class="dhcpOption", + aci_rn="opt-{0}".format(dhcp_option), + module_object=dhcp_option, + target_filter={"name": dhcp_option}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="dhcpOption", + class_config=dict( + name=dhcp_option, + data=data, + id=id, + ), + ) + + aci.get_diff(aci_class="dhcpOption") + + 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_option_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_option_policy.py new file mode 100644 index 000000000..9e7bd7738 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_option_policy.py @@ -0,0 +1,276 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_dhcp_option_policy +short_description: Manage DHCP Option Policy (dhcp:OptionPol) +description: +- Manage DHCP Option Policy for tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + dhcp_option_policy: + description: + - The name of the DHCP Option Policy. + type: str + aliases: [ dhcp_option_policy_name, name ] + description: + description: + - The description for the DHCP Option 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 +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) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new DHCP Option Policy + cisco.aci.aci_dhcp_option_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + dhcp_option_policy: my_dhcp_option_policy + state: present + delegate_to: localhost + +- name: Delete an DHCP Option Policy + cisco.aci.aci_dhcp_option_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + dhcp_option_policy: my_dhcp_option_policy + state: absent + delegate_to: localhost + +- name: Query a DHCP Option Policy + cisco.aci.aci_dhcp_option_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + dhcp_option_policy: my_dhcp_option_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query all DHCP Option Policies in my_tenant + cisco.aci.aci_dhcp_option_policy: + host: apic + username: admin + password: SomeSecretPassword + 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( + tenant=dict(type="str", aliases=["tenant_name"]), + dhcp_option_policy=dict(type="str", aliases=["dhcp_option_policy_name", "name"]), + 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", ["tenant", "dhcp_option_policy"]], + ["state", "present", ["tenant", "dhcp_option_policy"]], + ], + ) + + tenant = module.params.get("tenant") + description = module.params.get("description") + dhcp_option_policy = module.params.get("dhcp_option_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="dhcpOptionPol", + aci_rn="dhcpoptpol-{0}".format(dhcp_option_policy), + module_object=dhcp_option_policy, + target_filter={"name": dhcp_option_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="dhcpOptionPol", + class_config=dict( + name=dhcp_option_policy, + descr=description, + ), + ) + + aci.get_diff(aci_class="dhcpOptionPol") + + 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 index 9bb8267dd..39351b4aa 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay.py @@ -14,9 +14,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_dhcp_relay -short_description: Manage DHCP relay policies. +short_description: Manage DHCP relay policies (dhcp:RelayP) description: -- Manage DHCP relay policy (dhcpRelayP) configuration on Cisco ACI fabrics. +- Manage DHCP relay policy configuration on Cisco ACI fabrics. options: tenant: description: @@ -48,7 +48,7 @@ notes: 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). + description: More information about the internal APIC class B(dhcp:RelayP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py index efb9e8e54..2d3dcaf50 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dhcp_relay_provider.py @@ -14,9 +14,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_dhcp_relay_provider -short_description: Manage DHCP relay policy providers. +short_description: Manage DHCP relay policy providers (dhcp:RsProv) description: -- Manage DHCP relay policy providers (dhcpRsProv) configuration on Cisco ACI fabrics. +- Manage DHCP relay policy providers configuration on Cisco ACI fabrics. options: tenant: description: @@ -91,7 +91,7 @@ notes: 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). + description: More information about the internal APIC class B(dhcp:RsProv). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dns_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_dns_domain.py index 4325c7513..11ef520fa 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_dns_domain.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dns_domain.py @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_dns_domain -short_description: Manage DNS Provider (dnsDomain) objects. +short_description: Manage DNS Provider objects (dns:Domain) description: - Manage DNS Domain configuration on Cisco ACI fabrics. options: @@ -49,7 +49,7 @@ notes: seealso: - name: APIC Management Information Model reference - description: More information about the internal APIC class B(dnsDomain). + description: More information about the internal APIC class B(dns:Domain). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dns_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_dns_profile.py index 47efb62c4..a4a535a73 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_dns_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dns_profile.py @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_dns_profile -short_description: Manage DNS Profile (dnsProfile) objects. +short_description: Manage DNS Profile objects (dns:Profile) description: - Manage DNS Profile configuration on Cisco ACI fabrics. options: @@ -35,7 +35,7 @@ extends_documentation_fragment: seealso: - name: APIC Management Information Model reference - description: More information about the internal APIC class B(dnsProfile). + description: More information about the internal APIC class B(dns:Profile). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_dns_provider.py b/ansible_collections/cisco/aci/plugins/modules/aci_dns_provider.py index 1fe2c2e60..7b74bd2b6 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_dns_provider.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_dns_provider.py @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_dns_provider -short_description: Manage DNS Provider (dnsProv) objects. +short_description: Manage DNS Provider objects (dns:Prov) description: - Manage DNS Provider configuration on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_domain.py b/ansible_collections/cisco/aci/plugins/modules/aci_domain.py index 75509b0ef..ea65805b8 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_domain.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_domain.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported 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) +short_description: Manage physical, virtual, bridged, routed or FC domain profiles (phys:DomP, vmm:DomP, l2ext:DomP, l3ext:DomP, and fc:DomP) description: - Manage physical, virtual, bridged, routed or FC domain profiles on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool.py index 5d5ed7833..360e84c3f 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_encap_pool -short_description: Manage encap pools (fvns:VlanInstP, fvns:VxlanInstP, fvns:VsanInstP) +short_description: Manage encap pools (fvns:VlanInstP, fvns:VxlanInstP, and fvns:VsanInstP) description: - Manage vlan, vxlan, and vsan pools on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py index 43d34e78d..29da16a7d 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_encap_pool_range.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_encap_pool_range -short_description: Manage encap ranges assigned to pools (fvns:EncapBlk, fvns:VsanEncapBlk) +short_description: Manage encap ranges assigned to pools (fvns:EncapBlk and fvns:VsanEncapBlk) description: - Manage vlan, vxlan, and vsan ranges that are assigned to pools on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_epg.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg.py index 9f0eb671a..b9faec64e 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_epg.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg.py @@ -1,6 +1,8 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2023, Christian Kolrep <christian.kolrep@dataport.de> +# Copyright: (c) 2024, 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 @@ -78,6 +80,17 @@ options: - Use C(yes) to create uSeg EPG and C(no) is used to create Application EPG. type: str choices: [ 'yes', 'no' ] + match: + description: + - The match type of the default Block Statement (fv:Crtrn). + - The APIC defaults to C(any) when unset during creation. + type: str + choices: [ any, all ] + precedence: + description: + - The Block Statement(fv:Crtrn) Precedence to resolve equal matches between micro segmented EPGs. + - The APIC defaults to C(0) when unset during creation. + type: int state: description: - Use C(present) or C(absent) for adding or removing. @@ -105,6 +118,8 @@ seealso: author: - Swetha Chunduri (@schunduri) - Shreyas Srish (@shrsr) +- Christian Kolrep (@Christian-Kolrep) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -123,25 +138,6 @@ EXAMPLES = r""" 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 @@ -158,17 +154,22 @@ EXAMPLES = r""" state: present delegate_to: localhost -- name: Remove an EPG +- name: Add a uSeg EPG with block statement match and precedence cisco.aci.aci_epg: host: apic username: admin password: SomeSecretPassword - validate_certs: false tenant: production - app_profile: intranet + ap: intranet epg: web_epg + description: Web Intranet EPG + bd: prod_bd monitoring_policy: default - state: absent + preferred_group: true + useg: 'yes' + match: all + precedence: 1 + state: present delegate_to: localhost - name: Query an EPG @@ -213,6 +214,19 @@ EXAMPLES = r""" state: query delegate_to: localhost register: query_result + +- 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 """ RETURN = r""" @@ -342,6 +356,8 @@ def main(): monitoring_policy=dict(type="str"), custom_qos_policy=dict(type="str"), useg=dict(type="str", choices=["yes", "no"]), + match=dict(type="str", choices=["all", "any"]), + precedence=dict(type="int"), ) module = AnsibleModule( @@ -369,6 +385,8 @@ def main(): monitoring_policy = module.params.get("monitoring_policy") custom_qos_policy = module.params.get("custom_qos_policy") useg = module.params.get("useg") + match = module.params.get("match") + precedence = module.params.get("precedence") child_configs = [dict(fvRsBd=dict(attributes=dict(tnFvBDName=bd))), dict(fvRsAEPgMonPol=dict(attributes=dict(tnMonEPGPolName=monitoring_policy)))] @@ -394,12 +412,17 @@ def main(): module_object=epg, target_filter={"name": epg}, ), - child_classes=["fvRsBd", "fvRsAEPgMonPol", "fvRsCustQosPol"], + child_classes=["fvRsBd", "fvRsAEPgMonPol", "fvRsCustQosPol", "fvCrtrn"], ) aci.get_existing() if state == "present": + if useg is not None and aci.existing and aci.existing[0]["fvAEPg"]["attributes"]["isAttrBasedEPg"] != useg: + module.fail_json(msg="Changing attribute useg on existing EPG is not supported.") + if useg == "yes": + child_configs.append(dict(fvCrtrn=dict(attributes=dict(name="default", match=match, prec=precedence)))) + aci.payload( aci_class="fvAEPg", class_config=dict( 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 index fe09945a3..9faf882c8 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_contract.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_contract.py @@ -1,6 +1,8 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2017, Jacob McGill (@jmcgill298) +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -12,7 +14,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_epg_to_contract -short_description: Bind EPGs to Contracts (fv:RsCons, fv:RsProv) +short_description: Bind EPGs to Contracts (fv:RsCons, fv:RsProv, fv:RsProtBy, fv:RsConsIf, and fv:RsIntraEpg) description: - Bind EPGs to Contracts on Cisco ACI fabrics. notes: @@ -26,15 +28,15 @@ options: aliases: [ app_profile, app_profile_name ] contract: description: - - The name of the contract. + - The name of the contract or contract interface. type: str - aliases: [ contract_name ] + aliases: [ contract_name, contract_interface ] contract_type: description: - - Determines if the EPG should Provide or Consume the Contract. + - Determines the type of the Contract. type: str required: true - choices: [ consumer, provider ] + choices: [ consumer, provider, taboo, interface, intra_epg ] epg: description: - The name of the end point group. @@ -81,10 +83,11 @@ seealso: - 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). + description: More information about the internal APIC classes B(fv:RsCons), B(fv:RsProv), B(fv:RsProtBy), B(fv:RsConsIf), and B(fv:RsIntraEpg). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Jacob McGill (@jmcgill298) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -248,44 +251,17 @@ url: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec - -ACI_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", -) +from ansible_collections.cisco.aci.plugins.module_utils.constants import ACI_CLASS_MAPPING, CONTRACT_LABEL_MAPPING, PROVIDER_MATCH_MAPPING, SUBJ_LABEL_MAPPING 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"]), + contract_type=dict(type="str", required=True, choices=["consumer", "provider", "taboo", "interface", "intra_epg"]), 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 + contract=dict(type="str", aliases=["contract_name", "contract_interface"]), # 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"]), @@ -318,13 +294,19 @@ def main(): 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] + aci_name = ACI_CLASS_MAPPING[contract_type]["name"] + child_classes = [] - if contract_type == "consumer" and provider_match is not None: + if contract_type != "provider" 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] + if contract_type in ["taboo", "interface", "intra_epg"] and (contract_label is not None or subject_label is not None): + module.fail_json(msg="the 'contract_label' and 'subject_label' are not configurable for {0} contracts".format(contract_type)) + + if contract_type not in ["taboo", "interface", "intra_epg"]: + contract_label_class = CONTRACT_LABEL_MAPPING.get(contract_type) + subject_label_class = SUBJ_LABEL_MAPPING.get(contract_type) + child_classes = [subject_label_class, contract_label_class] aci = ACIModule(module) aci.construct_url( @@ -350,7 +332,7 @@ def main(): aci_class=aci_class, aci_rn="{0}{1}".format(aci_rn, contract), module_object=contract, - target_filter={"tnVzBrCPName": contract}, + target_filter={aci_name: contract}, ), child_classes=child_classes, ) @@ -359,17 +341,13 @@ def main(): if state == "present": child_configs = [] - if contract_label: + if contract_label is not None: child_configs.append({contract_label_class: {"attributes": {"name": contract_label}}}) - if subject_label: + if subject_label is not None: 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, - ), + class_config={"matchT": provider_match, "prio": priority, aci_name: contract}, child_configs=child_configs, ) 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 index bc0ed04fa..ff3a7908c 100644 --- 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 @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_epg_to_contract_interface -short_description: Bind EPGs to Consumed Contracts Interface (fv:RsConsIf). +short_description: Bind EPGs to Consumed Contracts Interface (fv:RsConsIf) description: - Bind EPGs to Consumed Contracts Interface on Cisco ACI fabrics. 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 index 90f34cc29..1b82f9361 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_domain.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_to_domain.py @@ -137,7 +137,7 @@ options: description: - The delimiter. type: str - choices: [ "|", "~", "!", "@", "^", "+", "=" ] + choices: [ "|", "~", "!", "@", "^", "+", "=", "_" ] untagged_vlan: description: - The access vlan is untagged. @@ -384,7 +384,7 @@ def main(): 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=["|", "~", "!", "@", "^", "+", "="]), + 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"]), diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_epg_useg_attribute_block_statement.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_useg_attribute_block_statement.py new file mode 100644 index 000000000..9664b4640 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_useg_attribute_block_statement.py @@ -0,0 +1,348 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, 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_epg_useg_attribute_block_statement +short_description: Manage EPG useg Attributes Block Statements (fv:SCrtrn) +description: +- Manage EPG useg Attributes Block Statements +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of an existing application network profile. + type: str + aliases: [ app_profile, app_profile_name ] + epg: + description: + - The name of an existing end point group. + type: str + aliases: [ epg_name ] + parent_block_statements: + description: + - The list of parent block statements. + - The order of the provided list matters, assuming the list ["A", "B"]. + - The block statement "A" will be the parent of "B" + - The block statement "A" will be a child of the default block statement. + - The maximum amount of parent block statements is 2. + type: list + elements: str + aliases: [ blocks, parent_blocks ] + name: + description: + - The name of the block statement. + type: str + aliases: [ block_statement, block_statement_name ] + match: + description: + - The match type of the Block Statement. + - The APIC defaults to C(any) when unset during creation. + type: str + choices: [ any, all ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The I(tenant), I(ap) and I(epg) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_ap) and 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:SCrtrn). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross)) +""" + +EXAMPLES = r""" +- name: Add a new block statement + cisco.aci.aci_epg_useg_attribute_block_statement: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + name: block_a + state: present + delegate_to: localhost + +- name: Add a new nested block statement + cisco.aci.aci_epg_useg_attribute_block_statement: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + parent_block_statements: + - block_a + - block_b + name: block_c + match: any + state: present + delegate_to: localhost + +- name: Query a block statement + cisco.aci.aci_epg_useg_attribute_block_statement: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + name: block_a + state: query + delegate_to: localhost + register: query_result + +- name: Query all block statements + cisco.aci.aci_epg_useg_attribute_block_statement: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove an existing block statement + cisco.aci.aci_epg_useg_attribute_block_statement: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + name: block_a + 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( + 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 + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + parent_block_statements=dict(type="list", elements="str", aliases=["parent_blocks", "blocks"]), + name=dict(type="str", aliases=["block_statement", "block_statement_name"]), + match=dict(type="str", choices=["any", "all"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["ap", "epg", "tenant", "name"]], + ["state", "present", ["ap", "epg", "tenant", "name"]], + ], + ) + + aci = ACIModule(module) + + ap = module.params.get("ap") + epg = module.params.get("epg") + state = module.params.get("state") + tenant = module.params.get("tenant") + blocks = module.params.get("parent_block_statements") + name = module.params.get("name") + match = module.params.get("match") + + block_statement_class = "fvSCrtrn" + + if blocks: + if len(blocks) > 2: + module.fail_json(msg="{0} block statements are provided but the maximum amount of parent_block_statements is 2".format(len(blocks))) + parent_blocks_class = block_statement_class + parent_blocks_rn = "crtrn/crtrn-{0}".format("/crtrn-".join(blocks)) + parent_blocks_name = blocks[-1] + else: + parent_blocks_class = "fvCrtrn" + parent_blocks_rn = "crtrn" + parent_blocks_name = "default" + + 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=parent_blocks_class, + aci_rn=parent_blocks_rn, + module_object=parent_blocks_name, + target_filter={"name": parent_blocks_name}, + ), + subclass_4=dict( + aci_class=block_statement_class, + aci_rn="crtrn-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class=block_statement_class, class_config=dict(name=name, match=match)) + + aci.get_diff(aci_class=block_statement_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_useg_attribute_simple_statement.py b/ansible_collections/cisco/aci/plugins/modules/aci_epg_useg_attribute_simple_statement.py new file mode 100644 index 000000000..462a7fcaa --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_epg_useg_attribute_simple_statement.py @@ -0,0 +1,436 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Christian Kolrep <christian.kolrep@dataport.de> +# Copyright: (c) 2024, 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_epg_useg_attribute_simple_statement +short_description: Manage EPG useg Attributes Simple Statements (fv:DnsAttr, fv:IdGroupAttr, fv:IpAttr, fv:MacAttr, and fv:VmAttr) +description: +- Manage EPG useg Attributes Simple Statements +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + ap: + description: + - The name of an existing application network profile. + type: str + aliases: [ app_profile, app_profile_name ] + epg: + description: + - The name of an existing end point group. + type: str + aliases: [ epg_name ] + parent_block_statements: + description: + - The list of parent block statements. + - The order of the provided list matters, assuming the list ["A", "B", "C"]. + - The block statement "A" will be the parent of "B" + - The block statement "A" will be a child of the default block statement. + - The maximum amount of parent block statements is 3. + type: list + elements: str + aliases: [ blocks, parent_blocks ] + name: + description: + - The name of the EPG useg attribute. + type: str + aliases: [ useg_attribute_name ] + type: + description: + - The type of the EPG useg attribute + type: str + required: true + choices: + - ip + - mac + - dns + - ad_group + - vm_custom_attr + - vm_vmm_domain + - vm_operating_system + - vm_hypervisor_id + - vm_datacenter + - vm_id + - vm_name + - vm_folder + - vm_folder_path + - vm_vnic + - vm_tag + aliases: [ useg_attribute_type ] + operator: + description: + - The operator of the EPG useg attribute. + type: str + choices: [ equals, contains, starts_with, ends_with ] + category: + description: + - The name of the vmware tag category or vmware custom attribute. + type: str + aliases: [ custom_attribute ] + use_subnet: + description: + - Whether to use the EPG subnet definition for ip. + type: bool + value: + description: + - The value of the EPG useg attribute. + type: str + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The I(tenant), I(ap) and I(epg) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_ap) and 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:DnsAttr), B(fv:IdGroupAttr), B(fv:IpAttr), B(fv:MacAttr), and B(fv:VmAttr). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Christian Kolrep (@Christian-Kolrep) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a new vmtag useg attribute in default block statement + cisco.aci.aci_epg_useg_attribute_simple_statement: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + name: vmtagprod + type: vmtag + category: Environment + operator: equals + value: Production + state: present + delegate_to: localhost + +- name: Add a new vmtag useg attribute in nested block statement + cisco.aci.aci_epg_useg_attribute_simple_statement: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + name: vmtagprod + parent_block_statements: + - block_a + - block_b + type: vmtag + category: Environment + operator: equals + value: Production + state: present + delegate_to: localhost + +- name: Query a specific vmtag useg attribute in default block statement + cisco.aci.aci_epg_useg_attribute_simple_statement: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + name: vmtagprod + type: vmtag + state: query + delegate_to: localhost + register: query_result + +- name: Query all vmtag useg attributes + cisco.aci.aci_epg_useg_attribute_simple_statement: + host: apic + username: admin + password: SomeSecretPassword + state: query + type: vmtag + delegate_to: localhost + register: query_result + +- name: Remove an existing vmtag useg attribute from default block statement + cisco.aci.aci_epg_useg_attribute_simple_statement: + host: apic + username: admin + password: SomeSecretPassword + tenant: anstest + ap: anstest + epg: anstest + name: vmtagprod + type: vmtag + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import USEG_ATTRIBUTE_MAPPING, OPERATOR_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + 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 + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + parent_block_statements=dict(type="list", elements="str", aliases=["parent_blocks", "blocks"]), + name=dict(type="str", aliases=["useg_attribute_name"]), + type=dict(type="str", required=True, choices=list(USEG_ATTRIBUTE_MAPPING.keys()), aliases=["useg_attribute_type"]), + operator=dict(type="str", choices=list(OPERATOR_MAPPING.keys())), + category=dict(type="str", aliases=["custom_attribute"]), + value=dict(type="str"), + use_subnet=dict(type="bool"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["ap", "epg", "tenant", "name"]], + ["state", "present", ["ap", "epg", "tenant", "name"]], + ], + ) + + aci = ACIModule(module) + + ap = module.params.get("ap") + epg = module.params.get("epg") + state = module.params.get("state") + tenant = module.params.get("tenant") + blocks = module.params.get("parent_block_statements") + name = module.params.get("name") + attribute_type = module.params.get("type") + value = module.params.get("value") + operator = module.params.get("operator") + category = module.params.get("category") + use_subnet = aci.boolean(module.params.get("use_subnet")) + + # Excluding below classes from the module: + # fvProtoAttr: + # Was used in AVS, but it is not longer in use. + # fvUsegBDCont: + # Was part of a feature that allowed uSeg attributes to be applied at VRF (instead of BD) level. + # It has been since deprecated and we no longer allow setting the scope at fvCtrn to scope-vrf. + # This type of functionality has been replaced by the ESG feature. + attribute_class = USEG_ATTRIBUTE_MAPPING[attribute_type]["attribute_class"] + attribute_rn = USEG_ATTRIBUTE_MAPPING[attribute_type]["rn_format"].format(name) + attribute_type = USEG_ATTRIBUTE_MAPPING[attribute_type]["attribute_type"] + + if blocks: + if len(blocks) > 3: + module.fail_json(msg="{0} block statements are provided but the maximum amount of parent_block_statements is 3".format(len(blocks))) + parent_blocks_class = "fvSCrtrn" + parent_blocks_rn = "crtrn/crtrn-{0}".format("/crtrn-".join(blocks)) + parent_blocks_name = blocks[-1] + else: + parent_blocks_class = "fvCrtrn" + parent_blocks_rn = "crtrn" + parent_blocks_name = "default" + + 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=parent_blocks_class, + aci_rn=parent_blocks_rn, + module_object=parent_blocks_name, + target_filter={"name": parent_blocks_name}, + ), + subclass_4=dict( + aci_class=attribute_class, + aci_rn=attribute_rn, + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + class_config = dict(name=name) + + if attribute_class == "fvVmAttr": + class_config.update(type=attribute_type) + class_config.update(operator=OPERATOR_MAPPING.get(operator)) + class_config.update(value=value) + if attribute_type == "tag": + class_config.update(category=category) + elif attribute_type == "custom-label": + class_config.update(labelName=category) + + elif attribute_class == "fvIpAttr": + class_config.update(usefvSubnet=use_subnet) + class_config.update(ip=value) + + elif attribute_class == "fvMacAttr": + class_config.update(mac=value.upper()) + + elif attribute_class == "fvDnsAttr": + class_config.update(filter=value) + + elif attribute_class == "fvIdGroupAttr": + class_config.update(selector=value) + + aci.payload(aci_class=attribute_class, class_config=class_config) + + aci.get_diff(aci_class=attribute_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_esg_ip_subnet_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_esg_ip_subnet_selector.py index 368f9e6dc..b8632ec6a 100644 --- 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 @@ -14,7 +14,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_esg_ip_subnet_selector -short_description: Manage ESG IP Subnet selector(fv:EPSelector) +short_description: Manage ESG IP Subnet selector (fv:EPSelector) description: - Manage Endpoint Security Groups (ESG) IP Subnet selector on Cisco ACI fabrics. diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_external_connection_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_external_connection_profile.py new file mode 100644 index 000000000..efea05338 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_external_connection_profile.py @@ -0,0 +1,311 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2024, Samita Bhattacharjee (@samitab) <samitab.cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_external_connection_profile +short_description: Manage Fabric External Connection Profiles (fv:FabricExtConnP). +description: +- Manage Fabric External Connection Profiles (Intrasite/Intersite profiles) on Cisco ACI fabrics. +options: + description: + description: + - Specifies a description of the profile definition. + type: str + aliases: [ descr ] + fabric_id: + description: + - The fabric identifier of the Fabric External Connection Profile. + type: int + aliases: [ id, fabric ] + name: + description: + - The name of the Fabric External Connection Profile. + type: str + aliases: [ profile_name ] + community: + description: + - Global EVPN Route Target of the Fabric External Connection Profile. + - eg. extended:as2-nn4:5:16 + type: str + aliases: [ rt, route_target ] + site_id: + description: + - The site identifier of the Fabric External Connection Profile. + type: int + aliases: [ sid, site, s_id ] + peering_type: + description: + - The BGP EVPN Peering Type. Use either C(automatic_with_full_mesh) or C(automatic_with_rr). + type: str + choices: [ automatic_with_full_mesh, automatic_with_rr ] + aliases: [ p_type, peer, peer_t ] + peering_password: + description: + - The BGP EVPN Peering Password. Used for setting automatic peering sessions. + type: str + aliases: [ peer_password, peer_pwd ] + 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(fv:FabricExtConnP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +EXAMPLES = r""" +- name: Add a new Fabric External Connection Profile + cisco.aci.aci_fabric_external_connection_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: 1 + name: ansible_fabric_ext_conn_profile + description: Fabric External Connection Profile + community: extended:as2-nn4:5:16 + site_id: 1 + peering_type: automatic_with_rr + peering_password: SomeSecretPeeringPassword + state: present + delegate_to: localhost + +- name: Query a Fabric External Connection Profile + cisco.aci.aci_fabric_external_connection_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Fabric External Connection Profiles + cisco.aci.aci_fabric_external_connection_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove a Fabric External Connection Profile + cisco.aci.aci_fabric_external_connection_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: 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 + + +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"]), + fabric_id=dict(type="int", aliases=["id", "fabric"]), + name=dict(type="str", aliases=["profile_name"]), + community=dict(type="str", aliases=["rt", "route_target"]), + site_id=dict(type="int", aliases=["sid", "site", "s_id"]), + peering_type=dict(type="str", aliases=["p_type", "peer", "peer_t"], choices=["automatic_with_full_mesh", "automatic_with_rr"]), + peering_password=dict(type="str", aliases=["peer_password", "peer_pwd"], 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", ["fabric_id"]], + ["state", "present", ["fabric_id"]], + ], + ) + + aci = ACIModule(module) + + description = module.params.get("description") + fabric_id = module.params.get("fabric_id") + name = module.params.get("name") + community = module.params.get("community") + peering_type = module.params.get("peering_type") + peering_password = module.params.get("peering_password") + site_id = module.params.get("site_id") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="fvFabricExtConnP", + aci_rn="tn-infra/fabricExtConnP-{0}".format(fabric_id), + module_object=fabric_id, + target_filter={"id": fabric_id}, + ), + child_classes=["fvPeeringP"], + ) + + aci.get_existing() + + if state == "present": + child_configs = None + if peering_type is not None or peering_password is not None: + peering_p = {"fvPeeringP": {"attributes": {}}} + if peering_type is not None: + peering_p["fvPeeringP"]["attributes"]["type"] = peering_type + if peering_password is not None: + peering_p["fvPeeringP"]["attributes"]["password"] = peering_password + child_configs = [peering_p] + + aci.payload( + aci_class="fvFabricExtConnP", + class_config=dict( + descr=description, + id=fabric_id, + name=name, + rt=community, + siteId=site_id, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fvFabricExtConnP") + + 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_external_routing_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_external_routing_profile.py new file mode 100644 index 000000000..539705def --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_external_routing_profile.py @@ -0,0 +1,315 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2024, Samita Bhattacharjee (@samitab) <samitab.cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_external_routing_profile +short_description: Manage Fabric External Routing Profiles (l3ext:FabricExtRoutingP) +description: +- Manage Fabric External Routing Profiles on Cisco ACI fabrics. +options: + name: + description: + - The name of the Fabric External Routing Profile. + type: str + aliases: [ routing_profile, profile ] + fabric_id: + description: + - The Fabric ID associated with the Fabric External Routing Profile. + type: int + aliases: [ fabric, fid] + description: + description: + - The description of the Fabric External Routing Profile. + type: str + aliases: [ descr ] + subnets: + description: + - The list of external subnet IP addresses. + - Duplicate subnet IP addresses are not valid and would be ignored. + type: list + elements: str + aliases: [ ip_addresses, ips ] + 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: +- This module requires an existing I(fabric_external_connection_profile). + The module M(cisco.aci.aci_fabric_external_connection_profile) can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(l3ext:FabricExtRoutingP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +# TODO EXAMPLES +EXAMPLES = r""" +- name: Add an External Routing Profile + cisco.aci.aci_fabric_external_routing_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: "1" + description: "Fabric external routing profile" + name: "ansible_fabric_ext_routing_profile" + subnets: + - 1.2.3.4/24 + - 5.6.7.8/24 + state: present + delegate_to: localhost + +- name: Query an External Routing Profile + cisco.aci.aci_fabric_external_routing_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: 1 + name: ansible_fabric_ext_routing_profile + state: query + delegate_to: localhost + register: query_result + +- name: Query all External Routing Profiles + cisco.aci.aci_fabric_external_routing_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove an External Routing Profile + cisco.aci.aci_fabric_external_routing_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: 1 + name: ansible_fabric_ext_routing_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=["routing_profile", "profile"]), + fabric_id=dict(type="int", aliases=["fabric", "fid"]), + description=dict(type="str", aliases=["descr"]), + subnets=dict(type="list", elements="str", aliases=["ip_addresses", "ips"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["fabric_id", "name"]], + ["state", "present", ["fabric_id", "name"]], + ], + ) + + aci = ACIModule(module) + + name = module.params.get("name") + fabric_id = module.params.get("fabric_id") + description = module.params.get("description") + subnets = module.params.get("subnets") + state = module.params.get("state") + + # Remove duplicate subnets + if isinstance(subnets, list): + subnets = list(dict.fromkeys(subnets)) + + aci.construct_url( + root_class=dict( + aci_class="fvFabricExtConnP", + aci_rn="tn-infra/fabricExtConnP-{0}".format(fabric_id), + module_object=fabric_id, + target_filter={"id": fabric_id}, + ), + subclass_1=dict( + aci_class="l3extFabricExtRoutingP", + aci_rn="fabricExtRoutingP-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["l3extSubnet"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + + # Validate if existing and remove subnet objects when the config does not match the provided config. + if isinstance(aci.existing, list) and len(aci.existing) > 0: + subnets = [] if subnets is None else subnets + for child in aci.existing[0].get("l3extFabricExtRoutingP", {}).get("children", {}): + if child.get("l3extSubnet") and child.get("l3extSubnet").get("attributes").get("ip") not in subnets: + child_configs.append( + { + "l3extSubnet": { + "attributes": { + "ip": child.get("l3extSubnet").get("attributes").get("ip"), + "status": "deleted", + } + } + } + ) + + if subnets is not None: + for subnet in subnets: + child_configs.append({"l3extSubnet": {"attributes": {"ip": subnet}}}) + + aci.payload( + aci_class="l3extFabricExtRoutingP", + class_config=dict( + name=name, + descr=description, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="l3extFabricExtRoutingP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py index 4048187db..a6a62b3da 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_interface_policy_group.py @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_fabric_interface_policy_group -short_description: Manage Fabric Interface Policy Groups (fabric:LePortPGrp, fabric:SpPortPGrp) +short_description: Manage Fabric Interface Policy Groups (fabric:LePortPGrp and fabric:SpPortPGrp) description: - Manage Fabric Interface Policy Groups on Cisco ACI fabrics. options: 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 index 09c87a387..590fbd7ba 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_leaf_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_leaf_profile.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_fabric_leaf_profile -short_description: Manage fabric leaf profiles (fabric:LeafP). +short_description: Manage fabric leaf profiles (fabric:LeafP) description: - Manage fabric leaf switch profiles in an ACI fabric. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_management_access.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_management_access.py new file mode 100644 index 000000000..493de3947 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_management_access.py @@ -0,0 +1,700 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_management_access +short_description: Manage Fabric Management Access (comm:Pol) +description: +- Manage Fabric Management Access on Cisco ACI fabrics. +options: + name: + description: + - The name of the Fabric Management Access policy. + type: str + aliases: [ fabric_management_access_policy_name ] + description: + description: + - The description of the Fabric Management Access policy. + type: str + aliases: [ descr ] + name_alias: + description: + - The name alias of the Fabric Management Access policy. + - This relates to the nameAlias property in ACI. + type: str + http: + description: + - Parameters for HTTP configuration (comm:Http). + type: dict + suboptions: + admin_state: + description: + - The admin state of the HTTP connection. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + port: + description: + - The port for the HTTP connection. + - The APIC defaults to C(80) when unset during creation. + type: int + redirect: + description: + - The state of the HTTP to HTTPS redirect service. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled, tested ] + allow_origins: + description: + - The allowed origins for the HTTP connection. + - 'Example format: http://127.0.0.1:8000' + type: str + allow_credentials: + description: + - The state of the allow credential for the HTTP connection. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + throttle: + description: + - The state of the request throttle for the HTTP connection. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + throttle_rate: + description: + - The rate of the request throttle. + - The APIC defaults to C(10000) when unset during creation. + type: int + throttle_unit: + description: + - The unit of the request throttle rate. + - The APIC defaults to C(requests_per_second) when unset during creation. + type: str + choices: [ requests_per_second, requests_per_minute ] + https: + description: + - Parameters for HTTPS configuration (comm:Https). + type: dict + suboptions: + admin_state: + description: + - The admin state of the HTTPS connection. + - The APIC defaults to C(enabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + port: + description: + - The port for the HTTPS connection. + - The APIC defaults to C(443) when unset during creation. + type: int + allow_origins: + description: + - The allowed origins for the HTTPS connection. + - 'Example format: http://127.0.0.1:8000' + type: str + allow_credentials: + description: + - The state of the allow credential for the HTTPS connection. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + ssl: + description: + - The SSL protocol(s) for the HTTPS connection. + - The APIC defaults to C(tls_v1.1) and C(tls_v1.2) set when unset during creation. + type: list + elements: str + choices: [ tls_v1.0, tls_v1.1, tls_v1.2, tls_v1.3 ] + aliases: [ ssl_protocols ] + dh_param: + description: + - The Diffie-Hellman parameter for the HTTPS connection. + - The APIC defaults to C(none) when unset during creation. + type: str + choices: [ '1024', '2048', '4096', none ] + throttle: + description: + - The state of the request throttle for the HTTPS connection. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + throttle_rate: + description: + - The rate of the request throttle. + - The APIC defaults to C(10000) when unset during creation. + type: int + throttle_unit: + description: + - The unit of the request throttle rate. + - The APIC defaults to C(requests_per_second) when unset during creation. + type: str + choices: [ requests_per_second, requests_per_minute ] + admin_key_ring: + description: + - The admin key ring for the HTTPS connection. + - The APIC defaults to C(default) when unset during creation. + type: str + client_certificate_trustpoint: + description: + - The client certificate trustpoint for the HTTPS connection. + type: str + aliases: [ trustpoint ] + client_certificate_authentication_state: + description: + - The client certificate authentication state for the HTTPS connection. + - The APIC defaults to C(disabled) when unset during creation. + - The C(enabled) state requires a C(client_certificate_trustpoint) to be set. + type: str + choices: [ enabled, disabled ] + aliases: [ client_certificate_auth_state, auth_state, authentication_state ] + telnet: + description: + - Parameters for telnet configuration (comm:Telnet). + type: dict + suboptions: + admin_state: + description: + - The admin state of the telnet connection. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + port: + description: + - The port for the telnet connection. + - The APIC defaults to C(23) when unset during creation. + type: int + ssh: + description: + - Parameters for SSH configuration (comm:Ssh). + type: dict + suboptions: + admin_state: + description: + - The admin state of the SSH connection. + - The APIC defaults to C(enabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + password_auth_state: + description: + - The password authentication state of the SSH connection. + - The APIC defaults to C(enabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + port: + description: + - The port for the SSH connection. + - The APIC defaults to C(22) when unset during creation. + type: int + ciphers: + description: + - The ciphers of the SSH connection. + - The APIC defaults to all options set when unset during creation. + type: list + elements: str + choices: [ aes128_ctr, aes192_ctr, aes256_ctr, aes128_gcm, aes256_gcm, chacha20 ] + kex: + description: + - The KEX algorithms of the SSH connection. + - The APIC defaults to all options set when unset during creation. + type: list + elements: str + choices: [ dh_sha1, dh_sha256, dh_sha512, curve_sha256, curve_sha256_libssh, ecdh_256, ecdh_384, ecdh_521 ] + macs: + description: + - The MACs of the SSH connection. + - The APIC defaults to all options set when unset during creation. + type: list + elements: str + choices: [ sha1, sha2_256, sha2_512, sha2_256_etm, sha2_512_etm ] + ssh_web: + description: + - Parameters for SSH access via WEB configuration (comm:Shellinabox). + type: dict + suboptions: + admin_state: + description: + - The admin state of the SSH access via WEB connection. + - The APIC defaults to C(disabled) when unset during creation. + type: str + choices: [ enabled, disabled ] + state: + description: + - Use C(present) for updating configuration. + - Use C(query) for showing current configuration. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(comm:Pol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Fabric Management Access policy + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + name: fabric_management_access_policy_1 + description: "This is a example Fabric Management Access policy." + state: present + delegate_to: localhost + +- name: Create a Fabric Management Access policy with telnet enabled + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + name: fabric_management_access_policy_1 + description: "This is a example Fabric Management Access policy." + telnet: + admin_state: enabled + state: present + delegate_to: localhost + +- name: Create a Fabric Management Access policy with SSH access via WEB enabled + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + name: fabric_management_access_policy_1 + description: "This is a example Fabric Management Access policy." + ssh_web: + admin_state: enabled + state: present + delegate_to: localhost + +- name: Create a Fabric Management Access policy with SSH enabled and ciphers set + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + name: fabric_management_access_policy_1 + description: "This is a example Fabric Management Access policy." + ssh: + admin_state: enabled + ciphers: + - aes128_ctr + - aes192_ctr + - aes256_ctr + state: present + delegate_to: localhost + +- name: Create a Fabric Management Access policy with HTTP enabled + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + name: fabric_management_access_policy_1 + description: "This is a example Fabric Management Access policy." + http: + admin_state: enabled + allow_origins: http://127.0.0.1:8000 + throttle: enabled + throttle_rate: 7500 + throttle_unit: requests_per_minute + state: present + delegate_to: localhost + +- name: Create a Fabric Management Access policy with HTTPS enabled + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + name: fabric_management_access_policy_1 + description: "This is a example Fabric Management Access policy." + https: + admin_state: enabled + port: 445 + allow_origins: http://127.0.0.1:8000 + allow_credentials: enabled + ssl: + - tls_v1.2 + dh_param: 4096 + throttle: enabled + throttle_rate: 7500 + throttle_unit: requests_per_minute + admin_key_ring: default + client_certificate_trustpoint: ansible_trustpoint + client_certificate_authentication_state: enabled + state: present + delegate_to: localhost + +- name: Query a Fabric Management Access policy + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + name: fabric_management_access_policy_1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Fabric Management Access policies + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Fabric Management Access policy + cisco.aci.aci_fabric_management_access: + host: apic + username: admin + password: SomeSecretPassword + name: fabric_management_access_policy_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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import THROTTLE_UNIT, SSH_CIPHERS, KEX_ALGORITHMS, SSH_MACS, HTTP_TLS_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + name=dict(type="str", aliases=["fabric_management_access_policy_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + http=dict( + type="dict", + options=dict( + admin_state=dict(type="str", choices=["enabled", "disabled"]), + port=dict(type="int"), + redirect=dict(type="str", choices=["enabled", "disabled", "tested"]), + allow_origins=dict(type="str"), + allow_credentials=dict(type="str", choices=["enabled", "disabled"]), + throttle=dict(type="str", choices=["enabled", "disabled"]), + throttle_rate=dict(type="int"), + throttle_unit=dict(type="str", choices=["requests_per_second", "requests_per_minute"]), + ), + ), + https=dict( + type="dict", + options=dict( + admin_state=dict(type="str", choices=["enabled", "disabled"]), + port=dict(type="int"), + allow_origins=dict(type="str"), + allow_credentials=dict(type="str", choices=["enabled", "disabled"]), + ssl=dict( + type="list", + elements="str", + choices=list(HTTP_TLS_MAPPING.keys()), + aliases=["ssl_protocols"], + ), + dh_param=dict(type="str", choices=["1024", "2048", "4096", "none"]), + throttle=dict(type="str", choices=["enabled", "disabled"]), + throttle_rate=dict(type="int"), + throttle_unit=dict(type="str", choices=["requests_per_second", "requests_per_minute"]), + admin_key_ring=dict(type="str", no_log=False), + client_certificate_trustpoint=dict(type="str", aliases=["trustpoint"]), + client_certificate_authentication_state=dict( + type="str", + choices=["enabled", "disabled"], + aliases=["client_certificate_auth_state", "auth_state", "authentication_state"], + ), + ), + ), + telnet=dict( + type="dict", + options=dict( + admin_state=dict(type="str", choices=["enabled", "disabled"]), + port=dict(type="int"), + ), + ), + ssh=dict( + type="dict", + options=dict( + admin_state=dict(type="str", choices=["enabled", "disabled"]), + password_auth_state=dict(type="str", choices=["enabled", "disabled"]), + port=dict(type="int"), + ciphers=dict(type="list", elements="str", choices=list(SSH_CIPHERS.keys())), + kex=dict(type="list", elements="str", choices=list(KEX_ALGORITHMS.keys())), + macs=dict(type="list", elements="str", choices=list(SSH_MACS.keys())), + ), + ), + ssh_web=dict( + type="dict", + options=dict( + admin_state=dict(type="str", choices=["enabled", "disabled"]), + ), + ), + 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) + aci_class = "commPol" + aci_child_classes = ["commSsh", "commHttp", "commHttps", "commTelnet", "commShellinabox"] + + name = module.params.get("name") + description = module.params.get("description") + name_alias = module.params.get("name_alias") + http = module.params.get("http") + https = module.params.get("https") + telnet = module.params.get("telnet") + ssh = module.params.get("ssh") + ssh_web = module.params.get("ssh_web") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn="fabric/comm-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=aci_child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + + if ssh: + child_configs.append( + dict( + commSsh=dict( + attributes=dict( + adminSt=ssh.get("admin_state"), + passwordAuth=ssh.get("password_auth_state"), + port=ssh.get("port"), + sshCiphers=",".join(sorted(SSH_CIPHERS.get(v) for v in set(ssh.get("ciphers")))) if ssh.get("ciphers") else None, + kexAlgos=",".join(sorted(KEX_ALGORITHMS.get(v) for v in set(ssh.get("kex")))) if ssh.get("kex") else None, + sshMacs=",".join(sorted(SSH_MACS.get(v) for v in set(ssh.get("macs")))) if ssh.get("macs") else None, + ) + ) + ) + ) + + if http: + child_configs.append( + dict( + commHttp=dict( + attributes=dict( + adminSt=http.get("admin_state"), + port=http.get("port"), + redirectSt=http.get("redirect"), + accessControlAllowOrigins=http.get("allow_origins"), + accessControlAllowCredential=http.get("allow_credentials"), + globalThrottleSt=http.get("throttle"), + globalThrottleRate=http.get("throttle_rate"), + globalThrottleUnit=THROTTLE_UNIT.get(http.get("throttle_unit")), + ) + ) + ) + ) + + if https: + https_config = dict( + commHttps=dict( + attributes=dict( + adminSt=https.get("admin_state"), + port=https.get("port"), + accessControlAllowOrigins=https.get("allow_origins"), + accessControlAllowCredential=https.get("allow_credentials"), + sslProtocols=",".join(sorted(HTTP_TLS_MAPPING.get(v) for v in set(https.get("ssl")))) if https.get("ssl") else None, + dhParam=https.get("dh_param"), + globalThrottleSt=https.get("throttle"), + globalThrottleRate=https.get("throttle_rate"), + globalThrottleUnit=THROTTLE_UNIT.get(https.get("throttle_unit")), + clientCertAuthState=https.get("client_certificate_authentication_state"), + ), + children=[], + ) + ) + + if https.get("admin_key_ring"): + https_config["commHttps"]["children"].append(dict(commRsKeyRing=dict(attributes=dict(tnPkiKeyRingName=https.get("admin_key_ring"))))) + + if https.get("client_certificate_trustpoint"): + https_config["commHttps"]["children"].append( + dict(commRsClientCertCA=dict(attributes=dict(tDn="uni/userext/pkiext/tp-{0}".format(https.get("client_certificate_trustpoint"))))) + ) + + child_configs.append(https_config) + + if telnet: + child_configs.append( + dict( + commTelnet=dict( + attributes=dict( + adminSt=telnet.get("admin_state"), + port=telnet.get("port"), + ) + ) + ) + ) + + if ssh_web: + child_configs.append( + dict( + commShellinabox=dict( + attributes=dict( + adminSt=ssh_web.get("admin_state"), + ) + ) + ) + ) + + aci.payload( + aci_class=aci_class, + class_config=dict( + name=name, + descr=description, + nameAlias=name_alias, + ), + 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_fabric_management_access_https_cipher.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_management_access_https_cipher.py new file mode 100644 index 000000000..96952f263 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_management_access_https_cipher.py @@ -0,0 +1,282 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_management_access_https_cipher +short_description: Manage Fabric Management Access HTTPS SSL Cipher Configuration (comm:Cipher) +description: +- Manage Fabric Management Access HTTPS SSL Cipher Configuration on Cisco ACI fabrics. +options: + fabric_management_access_policy_name: + description: + - The name of the Fabric Management Access policy. + type: str + aliases: [ name ] + id: + description: + - The ID of the SSL Cipher Configuration. + type: str + cipher_state: + description: + - The state of the SSL Cipher Configuration. + type: str + choices: [ enabled, disabled ] + name_alias: + description: + - The name alias of the Fabric Management Access HTTPS SSL Cipher Configuration. + - This relates to the nameAlias property in ACI. + type: str + state: + description: + - Use C(present) for updating configuration. + - Use C(query) for showing current configuration. + type: str + choices: [ absent, present, query ] + default: present +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(fabric_management_access_policy_name) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_management_access) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(comm:Cipher). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Create a Fabric Management Access HTTPS SSL Cipher + cisco.aci.aci_fabric_management_access_https_cipher: + host: apic + username: admin + password: SomeSecretPassword + fabric_management_access_policy_name: fabric_management_access_policy_1 + id: DHE-RSA-AES128-SHA + cipher_state: enabled + state: present + delegate_to: localhost + +- name: Query a Fabric Management Access HTTPS SSL Cipher + cisco.aci.aci_fabric_management_access_https_cipher: + host: apic + username: admin + password: SomeSecretPassword + fabric_management_access_policy_name: fabric_management_access_policy_1 + id: DHE-RSA-AES128-SHA + state: query + delegate_to: localhost + register: query_result + +- name: Query all Fabric Management Access policies + cisco.aci.aci_fabric_management_access_https_cipher: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Fabric Management Access HTTPS SSL Cipher + cisco.aci.aci_fabric_management_access_https_cipher: + host: apic + username: admin + password: SomeSecretPassword + fabric_management_access_policy_name: fabric_management_access_policy_1 + id: DHE-RSA-AES128-SHA + 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( + fabric_management_access_policy_name=dict(type="str", aliases=["name"]), # Not required for querying all objects + id=dict(type="str"), + cipher_state=dict(type="str", choices=["enabled", "disabled"]), + 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", "present", ["fabric_management_access_policy_name", "id", "cipher_state"]], + ["state", "absent", ["fabric_management_access_policy_name", "id"]], + ], + ) + + aci = ACIModule(module) + aci_class = "commCipher" + + fabric_management_access_policy_name = module.params.get("fabric_management_access_policy_name") + id_value = module.params.get("id") + cipher_state = module.params.get("cipher_state") + name_alias = module.params.get("name_alias") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class=aci_class, + aci_rn="fabric/comm-{0}".format(fabric_management_access_policy_name), + module_object=fabric_management_access_policy_name, + target_filter={"name": fabric_management_access_policy_name}, + ), + subclass_1=dict( + aci_class=aci_class, + aci_rn="https/cph-{0}".format(id_value), + module_object=id_value, + target_filter={"id": id_value}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_class, + class_config=dict( + id=id_value, + state=cipher_state, + 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_fabric_pod.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod.py new file mode 100644 index 000000000..b9adc1d27 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod.py @@ -0,0 +1,292 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Samita Bhattacharjee (@samitab) <samitab@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_pod +short_description: Manage Fabric Pod Setup Policy (fabric:SetupP) +description: +- Manage Fabric Pod Setup Policy on Cisco ACI fabrics. +options: + pod_id: + description: + - The Pod ID for the Fabric Pod Setup Policy. + - Accepted value range between C(1) and C(254). + type: int + aliases: [ pod, id ] + pod_type: + description: + - The type of the Pod. Use C(physical) or C(virtual). + - The APIC defaults to C(physical) when unset during creation. + type: str + choices: [ physical, virtual ] + aliases: [ type ] + tep_pool: + description: + - The TEP address pool for the Fabric Pod Setup Policy. + - Must be valid IPv4 and include the subnet mask. + - Example 192.168.1.0/24 + type: str + aliases: [ tep, pool ] + description: + description: + - The description for the Fabric Pod Setup 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 + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:SetupP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +EXAMPLES = r""" +- name: Add a fabric pod setup policy + cisco.aci.aci_fabric_pod: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 1 + tep_pool: 10.0.0.0/16 + state: present + delegate_to: localhost + +- name: Query the fabric pod setup policy + cisco.aci.aci_fabric_pod: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all fabric pod setup policies + cisco.aci.aci_fabric_pod: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove a fabric pod setup policy + cisco.aci.aci_fabric_pod: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 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, +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + pod_id=dict(type="int", aliases=["pod", "id"]), + pod_type=dict(type="str", choices=["physical", "virtual"], aliases=["type"]), + tep_pool=dict(type="str", aliases=["tep", "pool"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["pod_id"]], + ["state", "present", ["pod_id"]], + ], + ) + + aci = ACIModule(module) + + name_alias = module.params.get("name_alias") + pod_id = module.params.get("pod_id") + pod_type = module.params.get("pod_type") + tep_pool = module.params.get("tep_pool") + description = module.params.get("description") + state = module.params.get("state") + + if pod_id is not None and int(pod_id) not in range(1, 254): + aci.fail_json(msg="Pod ID: {0} is invalid; it must be in the range of 1 to 254.".format(pod_id)) + + aci.construct_url( + root_class=dict( + aci_class="fabricSetupP", + aci_rn="controller/setuppol/setupp-{0}".format(pod_id), + module_object=pod_id, + target_filter={"podId": pod_id}, + ), + child_classes=["fabricExtRoutablePodSubnet", "fabricExtSetupP"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricSetupP", + class_config=dict( + podId=pod_id, + podType=pod_type, + tepPool=tep_pool, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fabricSetupP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_connectivity_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_connectivity_profile.py new file mode 100644 index 000000000..ed601a358 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_connectivity_profile.py @@ -0,0 +1,336 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright: (c) 2024, Samita Bhattacharjee (@samitab) <samitab.cisco.com> + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_pod_connectivity_profile +short_description: Manage Fabric External Pod Connectivity Profiles (fv:PodConnP) +description: +- Manage Fabric External Pod Connectivity Profiles on Cisco ACI fabrics. +options: + pod_id: + description: + - The Pod ID associated with the Pod Connectivity Profile. + type: int + aliases: [ pod, pid ] + fabric_id: + description: + - The Fabric ID associated with the Pod Connectivity Profile. + type: int + aliases: [ fabric, fid ] + virtual_pod_id: + description: + - The Pod ID in the main fabric to which this I(pod_id) is associated. This property is valid only if this pod is a virtual pod. + type: int + aliases: [ vpod, vpod_id ] + description: + description: + - The description of the Pod Connectivity Profile. + type: str + aliases: [ descr ] + data_plane_tep: + description: + - The Data Plane TEP IPv4 address and prefix. + - eg. 10.1.1.1/32 + type: str + aliases: [ dp_tep ] + unicast_tep: + description: + - The Unicast TEP IPv4 address and prefix. + - eg. 10.1.1.2/32 + type: str + aliases: [ u_tep ] + 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: +- This module requires an existing I(fabric_external_connection_profile). + The module M(cisco.aci.aci_fabric_external_connection_profile) can be used for this. +seealso: +- module: cisco.aci.aci_fabric_external_connection_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fv:PodConnP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +EXAMPLES = r""" +- name: Add a Pod Connectivity Profile + cisco.aci.aci_fabric_pod_connectivity_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: 1 + pod_id: 1 + description: First pod connectivity profile + data_plane_tep: 10.1.1.1/32 + unicast_tep: 10.1.1.2/32 + state: present + delegate_to: localhost + +- name: Query a Pod Connectivity Profile + cisco.aci.aci_fabric_pod_connectivity_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: 1 + pod_id: 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all Pod Connectivity Profiles + cisco.aci.aci_fabric_pod_connectivity_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove a Pod Connectivity Profile + cisco.aci.aci_fabric_pod_connectivity_profile: + host: apic + username: admin + password: SomeSecretPassword + fabric_id: 1 + pod_id: 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 + + +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", "pid"]), + fabric_id=dict(type="int", aliases=["fabric", "fid"]), + virtual_pod_id=dict(type="int", aliases=["vpod", "vpod_id"]), + description=dict(type="str", aliases=["descr"]), + data_plane_tep=dict(type="str", aliases=["dp_tep"]), + unicast_tep=dict(type="str", aliases=["u_tep"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["fabric_id", "pod_id"]], + ["state", "present", ["fabric_id", "pod_id"]], + ], + ) + + aci = ACIModule(module) + + pod_id = module.params.get("pod_id") + fabric_id = module.params.get("fabric_id") + virtual_pod_id = module.params.get("virtual_pod_id") + description = module.params.get("description") + data_plane_tep = module.params.get("data_plane_tep") + unicast_tep = module.params.get("unicast_tep") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="fvFabricExtConnP", + aci_rn="tn-infra/fabricExtConnP-{0}".format(fabric_id), + module_object=fabric_id, + target_filter={"id": fabric_id}, + ), + subclass_1=dict( + aci_class="fvPodConnP", + aci_rn="podConnP-{0}".format(pod_id), + module_object=pod_id, + target_filter={"id": pod_id}, + ), + child_classes=["fvIp", "fvExtRoutableUcastConnP"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + + # Validate if existing and remove child objects when the config does not match the provided config. + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("fvPodConnP", {}).get("children", {}): + if child.get("fvExtRoutableUcastConnP") and child.get("fvExtRoutableUcastConnP").get("attributes").get("addr") != unicast_tep: + child_configs.append( + { + "fvExtRoutableUcastConnP": { + "attributes": { + "addr": child.get("fvExtRoutableUcastConnP").get("attributes").get("addr"), + "status": "deleted", + } + } + } + ) + if child.get("fvIp") and child.get("fvIp").get("attributes").get("addr") != data_plane_tep: + child_configs.append( + { + "fvIp": { + "attributes": { + "addr": child.get("fvIp").get("attributes").get("addr"), + "status": "deleted", + } + } + } + ) + + if unicast_tep is not None: + child_configs.append({"fvExtRoutableUcastConnP": {"attributes": {"addr": unicast_tep}}}) + if data_plane_tep is not None: + child_configs.append({"fvIp": {"attributes": {"addr": data_plane_tep}}}) + + aci.payload( + aci_class="fvPodConnP", + class_config=dict( + id=pod_id, + assocIntersitePodId=virtual_pod_id, + descr=description, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="fvPodConnP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_external_tep.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_external_tep.py new file mode 100644 index 000000000..511a5aa93 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_external_tep.py @@ -0,0 +1,321 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Samita Bhattacharjee (@samitab) <samitab@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_pod_external_tep +short_description: Manage Fabric Pod External TEP (fabric:ExtRoutablePodSubnet) +description: +- Manage Fabric Pod External TEP Subnets. +options: + pod_id: + description: + - The Pod ID for the External TEP. + type: int + aliases: [ pod ] + description: + description: + - The description for the External TEP. + type: str + aliases: [ descr ] + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str + external_tep_pool: + description: + - The subnet IP address pool for the External TEP. + - Must be valid IPv4 and include the subnet mask. + - Example 192.168.1.0/24 + type: str + aliases: [ ip, ip_address, tep_pool, pool ] + reserve_address_count: + description: + - Indicates the number of IP addresses that are reserved from the start of the subnet. + type: int + aliases: [ address_count ] + status: + description: + - State of the External TEP C(active) or C(inactive). + - An External TEP can only be deleted when the state is inactive. + - The APIC defaults to C(active) when unset during creation. + type: str + choices: [ active, inactive ] + 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(Fabric Pod Setup Policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_pod) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:ExtRoutablePodSubnet). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +EXAMPLES = r""" +- name: Add an External TEP to a fabric pod setup policy + cisco.aci.aci_fabric_pod_external_tep: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 2 + external_tep_pool: 10.6.1.0/24 + reserve_address_count: 5 + status: active + state: present + delegate_to: localhost + +- name: Change an External TEP state on a fabric pod setup policy to inactive + cisco.aci.aci_fabric_pod_external_tep: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 2 + external_tep_pool: 10.6.1.0/24 + status: inactive + state: present + delegate_to: localhost + +- name: Query the External TEP on a fabric pod setup policy + cisco.aci.aci_fabric_pod_external_tep: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 2 + external_tep_pool: 10.6.1.0/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query External TEPs on all fabric pod setup policies + cisco.aci.aci_fabric_pod_external_tep: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete an External TEP on a fabric pod setup policy + cisco.aci.aci_fabric_pod_external_tep: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 2 + external_tep_pool: 10.6.1.0/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, + 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", aliases=["descr"]), + name_alias=dict(type="str"), + pod_id=dict(type="int", aliases=["pod"]), + external_tep_pool=dict(type="str", aliases=["ip", "ip_address", "tep_pool", "pool"]), + reserve_address_count=dict(type="int", aliases=["address_count"]), + status=dict(type="str", choices=["active", "inactive"]), + 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", "external_tep_pool"]], + ["state", "present", ["pod_id", "external_tep_pool"]], + ], + ) + + aci = ACIModule(module) + + pod_id = module.params.get("pod_id") + descr = module.params.get("descr") + name_alias = module.params.get("name_alias") + external_tep_pool = module.params.get("external_tep_pool") + reserve_address_count = module.params.get("reserve_address_count") + status = module.params.get("status") + state = module.params.get("state") + + if pod_id is not None and int(pod_id) not in range(1, 254): + aci.fail_json(msg="Pod ID: {0} is invalid; it must be in the range of 1 to 254.".format(pod_id)) + + aci.construct_url( + root_class=dict( + aci_class="fabricSetupP", + aci_rn="controller/setuppol/setupp-{0}".format(pod_id), + module_object=pod_id, + target_filter={"podId": pod_id}, + ), + subclass_1=dict( + aci_class="fabricExtRoutablePodSubnet", + aci_rn="extrtpodsubnet-[{0}]".format(external_tep_pool), + module_object=external_tep_pool, + target_filter={"pool": external_tep_pool}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricExtRoutablePodSubnet", + class_config=dict( + descr=descr, + nameAlias=name_alias, + pool=external_tep_pool, + reserveAddressCount=reserve_address_count, + state=status, + ), + ) + + aci.get_diff(aci_class="fabricExtRoutablePodSubnet") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_profile.py new file mode 100644 index 000000000..e77114848 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_profile.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Samita Bhattacharjee (@samitab) <samitab@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_pod_profile +short_description: Manage Fabric Pod Profile (fabric:PodP) +description: +- Manage Fabric Pod Profile on Cisco ACI fabrics. +options: + name: + description: + - The name of the Pod Profile. + type: str + aliases: [ profile, pod_profile ] + description: + description: + - The description for the Fabric Pod 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: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:PodP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +EXAMPLES = r""" +- name: Add a new pod profile + cisco.aci.aci_fabric_pod_profile: + host: apic + username: admin + password: SomeSecretPassword + name: ans_pod_profile + state: present + delegate_to: localhost + +- name: Query a pod profile + cisco.aci.aci_fabric_pod_profile: + host: apic + username: admin + password: SomeSecretPassword + name: ans_pod_profile + state: query + delegate_to: localhost + register: query_result + +- name: Query all pod profiles + cisco.aci.aci_fabric_pod_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove a pod profile + cisco.aci.aci_fabric_pod_profile: + host: apic + username: admin + password: SomeSecretPassword + name: ans_pod_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( + description=dict(type="str", aliases=["descr"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + name_alias=dict(type="str"), + name=dict(type="str", aliases=["profile", "pod_profile"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["name"]], + ["state", "present", ["name"]], + ], + ) + + aci = ACIModule(module) + + name_alias = module.params.get("name_alias") + name = module.params.get("name") + description = module.params.get("description") + state = module.params.get("state") + + aci.construct_url( + root_class=dict( + aci_class="fabricPodP", + aci_rn="fabric/podprof-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + child_classes=["fabricPodS"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricPodP", + class_config=dict( + name=name, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="fabricPodP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_remote_pool.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_remote_pool.py new file mode 100644 index 000000000..189a0273d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_remote_pool.py @@ -0,0 +1,294 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Samita Bhattacharjee (@samitab) <samitab@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_fabric_pod_remote_pool +short_description: Manage Fabric Pod Remote Pool (fabric:ExtSetupP) +description: +- Manage Remote Pools on Fabric Pod Subnets. +options: + pod_id: + description: + - The Pod ID for the Remote Pool. + type: int + aliases: [ pod ] + description: + description: + - The description for the Remote Pool. + type: str + aliases: [desc] + remote_id: + description: + - The Identifier for the Remote Pool. + type: int + aliases: [ id ] + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str + remote_pool: + description: + - The subnet IP address pool for the Remote Pool. + - Must be valid IPv4 and include the subnet mask. + - Example 192.168.1.0/24 + type: str + aliases: [ pool ] + 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(Fabric Pod Setup Policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_fabric_pod) module can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(fabric:ExtSetupP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Samita Bhattacharjee (@samitab) +""" + +EXAMPLES = r""" +- name: Add a Remote Pool to a fabric pod setup policy + cisco.aci.aci_fabric_pod_remote_pool: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 2 + remote_id: 1 + remote_pool: 10.6.2.0/24 + state: present + delegate_to: localhost + +- name: Query the Remote Pool on a fabric pod setup policy + cisco.aci.aci_fabric_pod_remote_pool: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 2 + remote_id: 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query Remote Pools on all fabric pod setup policies + cisco.aci.aci_fabric_pod_remote_pool: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Delete a Remote Pool from a fabric pod setup policy + cisco.aci.aci_fabric_pod_external_tep: + host: apic + username: admin + password: SomeSecretPassword + pod_id: 2 + remote_id: 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 + + +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"]), + description=dict(type="str", aliases=["desc"]), + remote_id=dict(type="int", aliases=["id"]), + name_alias=dict(type="str"), + remote_pool=dict(type="str", aliases=["pool"]), + 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", "remote_id"]], + ["state", "present", ["pod_id", "remote_id"]], + ], + ) + + aci = ACIModule(module) + + pod_id = module.params.get("pod_id") + description = module.params.get("description") + remote_id = module.params.get("remote_id") + name_alias = module.params.get("name_alias") + remote_pool = module.params.get("remote_pool") + state = module.params.get("state") + + if pod_id is not None and int(pod_id) not in range(1, 254): + aci.fail_json(msg="Pod ID: {0} is invalid; it must be in the range of 1 to 254.".format(pod_id)) + + aci.construct_url( + root_class=dict( + aci_class="fabricSetupP", + aci_rn="controller/setuppol/setupp-{0}".format(pod_id), + module_object=pod_id, + target_filter={"podId": pod_id}, + ), + subclass_1=dict( + aci_class="fabricExtSetupP", + aci_rn="extsetupp-{0}".format(remote_id), + module_object=remote_id, + target_filter={"extPoolId": remote_id}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fabricExtSetupP", + class_config=dict( + descr=description, + extPoolId=remote_id, + nameAlias=name_alias, + tepPool=remote_pool, + ), + ) + + aci.get_diff(aci_class="fabricExtSetupP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py index 6c95f32df..28d95eae0 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_pod_selector.py @@ -67,6 +67,7 @@ extends_documentation_fragment: notes: - The C(pod_profile) must exist before using this module in your playbook. +- The M(cisco.aci.aci_fabric_pod_profile) module can be used to create the C(pod_profile). seealso: - name: APIC Management Information Model reference description: More information about the internal APIC class B(fabric:PodS). @@ -76,9 +77,6 @@ author: - Akini Ross (@akinross) """ -# TODO add to notes section when cisco.aci.aci_pod_profile is implemented: -# The M(cisco.aci.aci_pod_profile) module can be used for this. - EXAMPLES = r""" - name: Add a new pod selector with type all cisco.aci.aci_fabric_pod_selector: 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 index 364b613ff..af80ee415 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_spine_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_spine_profile.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_fabric_spine_profile -short_description: Manage fabric spine profiles (fabric:SpineP). +short_description: Manage fabric spine profiles (fabric:SpineP) description: - Manage fabric spine switch profiles in an ACI fabric. options: 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 index ce0a00ce8..5884cd1e1 100644 --- 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 @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_fabric_spine_switch_assoc -short_description: Manage spine switch bindings to profiles and policy groups (fabric:SpineS and fabric:RsSpNodePGrp). +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 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 index 57bd59874..ded3b56ec 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_switch_block.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_switch_block.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_fabric_switch_block -short_description: Manage switch blocks (fabric:NodeBlk). +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) 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 index 1d766a34e..a3ba0a033 100644 --- 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 @@ -13,72 +13,72 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_fabric_switch_policy_group -short_description: Manage Fabric Switch Policy Group objects. +short_description: Manage Fabric Switch Policy Group objects (fabric:LeNodePGrp and fabric:SpNodePGrp) description: -- Manage Fabric Switch Policy Group (fabricLeNodePGrp and fabricSpNodePGrp) configuration on Cisco ACI fabrics. +- Manage Fabric Switch Policy Group configuration on Cisco ACI fabrics. options: name: description: - - The name of the Leaf Policy Group. + - The name of the Fabric Switch Policy Group. type: str aliases: [ 'policy_group', 'policy_group_name' ] description: description: - - Description for the Leaf Policy Group. + - Description for the Fabric Switch Policy Group. type: str switch_type: description: - - Whether this is a leaf or spine policy group + - Whether this is a leaf or spine Fabric Switch Policy Group. type: str choices: [ leaf, spine ] required: true monitoring_policy: description: - - Monitoring Policy to attach to this Policy Group + - Monitoring Policy to attach to this Fabric Switch Policy Group. type: str aliases: [ 'monitoring', 'fabricRsMonInstFabricPol' ] tech_support_export_policy: description: - - Tech Support Export Policy to attach to this Policy Group + - Tech Support Export Policy to attach to this Fabric Switch Policy Group. type: str aliases: [ 'tech_support', 'tech_support_export', 'fabricRsNodeTechSupP'] core_export_policy: description: - - Core Export Policy to attach to this Policy Group + - Core Export Policy to attach to this Fabric Switch Policy Group. type: str aliases: [ 'core', 'core_export', 'fabricRsNodeCoreP' ] inventory_policy: description: - - Inventory Policy to attach to this Policy Group + - Inventory Policy to attach to this Fabric Switch Policy Group. type: str aliases: [ 'inventory', 'fabricRsCallhomeInvPol' ] power_redundancy_policy: description: - - Power Redundancy Policy to atttach to this Policy Group + - Power Redundancy Policy to atttach to this Fabric Switch Policy Group. type: str aliases: [ 'power_redundancy', 'fabricRsPsuInstPol' ] twamp_server_policy: description: - - TWAMP Server Policy to attach to this Policy Group + - TWAMP Server Policy to attach to this Fabric Switch Policy Group. type: str aliases: [ 'twamp_server', 'fabricRsTwampServerPol' ] twamp_responder_policy: description: - - TWAMP Responder Policy to attach to this Policy Group + - TWAMP Responder Policy to attach to this Fabric Switch Policy Group. type: str aliases: [ 'twamp_responder', 'fabricRsTwampResponderPol' ] node_control_policy: description: - - Node Control Policy to attach to this Policy Group + - Node Control Policy to attach to this Fabric Switch Policy Group. type: str aliases: [ 'node_control', 'fabricRsNodeCtrl' ] analytics_cluster: description: - - Name of the analytics cluster. Requires analytics_name to be present + - 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 + - Name of the analytics policy. Requires analytics_cluster to be present. type: str state: description: @@ -93,7 +93,7 @@ extends_documentation_fragment: seealso: - name: APIC Management Information Model reference - description: More information about the internal APIC classes B(fabricLeNodePGrp and fabricSpNodePGrp). + description: More information about the internal APIC classes B(fabric:LeNodePGrp) and B(fabric:SpNodePGrp). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py index 1bc7c28eb..2f8a879a9 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_fabric_wide_settings.py @@ -235,7 +235,7 @@ url: from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec -from ansible_collections.cisco.aci.plugins.module_utils.constants import TLS_MAPPING +from ansible_collections.cisco.aci.plugins.module_utils.constants import OPFLEX_TLS_MAPPING def main(): @@ -251,7 +251,7 @@ def main(): leaf_opflex_client_auth=dict(type="bool"), spine_ssl_opflex=dict(type="bool"), leaf_ssl_opflex=dict(type="bool"), - opflex_ssl_versions=dict(type="list", choices=list(TLS_MAPPING.keys()), elements="str"), + opflex_ssl_versions=dict(type="list", choices=list(OPFLEX_TLS_MAPPING.keys()), elements="str"), reallocate_gipo=dict(type="bool"), restrict_infra_vlan_traffic=dict(type="bool"), state=dict(type="str", default="present", choices=["present", "query"]), @@ -300,7 +300,7 @@ def main(): restrictInfraVLANTraffic=restrict_infra_vlan_traffic, ) if opflex_ssl_versions is not None: - class_config["opflexpSslProtocols"] = ",".join([TLS_MAPPING.get(tls) for tls in sorted(opflex_ssl_versions)]) + class_config["opflexpSslProtocols"] = ",".join([OPFLEX_TLS_MAPPING.get(tls) for tls in sorted(opflex_ssl_versions)]) aci.payload( aci_class="infraSetPol", 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 index 7f537871a..78369df89 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_igmp_interface_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_igmp_interface_policy.py @@ -294,8 +294,8 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, required_if=[ - ["state", "present", ["name"]], - ["state", "absent", ["name"]], + ["state", "present", ["name", "tenant"]], + ["state", "absent", ["name", "tenant"]], ], required_together=[ ["allow_v3_asm", "fast_leave", "report_link_local_groups"], diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_blacklist.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_blacklist.py index 61d5ef44d..aeba32988 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_blacklist.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_blacklist.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_interface_blacklist -short_description: Enabling or Disabling physical interfaces. +short_description: Enabling or Disabling physical interfaces (fabric:RsOosPath) description: - Enables or Disables physical interfaces on Cisco ACI fabrics. options: @@ -49,6 +49,10 @@ 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:RsOosPath). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Akini Ross (@akinross) """ diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_description.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_description.py index c4a204c89..387acd54a 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_description.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_description.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_interface_description -short_description: Setting and removing description on physical interfaces. +short_description: Setting and removing description on physical interfaces (infra:HPathS, infra:RsHPathAtt, infra:SHPathS, and infra:RsSHPathAtt) description: - Setting and removing description on physical interfaces on Cisco ACI fabrics. options: @@ -59,6 +59,10 @@ extends_documentation_fragment: - cisco.aci.annotation - cisco.aci.owner +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:HPathS), B(infra:RsHPathAtt), B(infra:SHPathS), and B(infra:RsSHPathAtt). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Akini Ross (@akinross) """ diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_bfd.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_bfd.py new file mode 100644 index 000000000..ec651f360 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_bfd.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain (@anvjain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_bfd +short_description: Manage BFD Interface policies (bfd:IfPol) +description: +- Manage BFD Interface policy configuration on Cisco ACI fabrics +- Only available in APIC version 5.2 or later +options: + tenant: + description: + - Name of an existing tenant + type: str + name: + description: + - Name of the BFD Interface policy + type: str + aliases: [ bfd_interface_policy ] + description: + description: + - Description of the BFD Interface policy + type: str + admin_state: + description: + - Admin state of the BFD Interface policy + - APIC sets the default value to enabled + type: str + choices: [ enabled, disabled ] + detection_multiplier: + description: + - Detection multiplier of the BFD Interface policy + - APIC sets the default value to 3 + - Allowed range is 1-50 + type: int + min_transmit_interval: + description: + - Minimum transmit (Tx) interval of the BFD Interface policy + - APIC sets the default value to 50 + - Allowed range is 250-999 + type: int + min_receive_interval: + description: + - Minimum receive (Rx) interval of the BFD Interface policy + - APIC sets the default value to 50 + - Allowed range is 250-999 + 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(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(bfd:IfPol) + link: https://developer.cisco.com/docs/apic-mim-ref/ +- module: cisco.aci.aci_tenant +author: +- Anvitha Jain (@anvjain) +""" + +EXAMPLES = r""" +- name: Add a new BFD Interface policy + cisco.aci.aci_interface_policy_bfd: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: ansible_bfd_interface_policy + description: Ansible BFD Interface Policy + state: present + delegate_to: localhost + +- name: Remove a BFD Interface policy + cisco.aci.aci_interface_policy_bfd: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: ansible_bfd_interface_policy + state: absent + delegate_to: localhost + +- name: Query a BFD Interface policy + cisco.aci.aci_interface_policy_bfd: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: ansible_bfd_interface_policy + state: query + delegate_to: localhost + +- name: Query all BFD Interface policies in a specific tenant + cisco.aci.aci_interface_policy_bfd: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + 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: 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=["bfd_interface_policy"]), + description=dict(type="str"), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + detection_multiplier=dict(type="int"), + min_transmit_interval=dict(type="int"), + min_receive_interval=dict(type="int"), + 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") + admin_state = module.params.get("admin_state") + detection_multiplier = module.params.get("detection_multiplier") + min_transmit_interval = module.params.get("min_transmit_interval") + min_receive_interval = module.params.get("min_receive_interval") + + 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="bfdIfPol", + aci_rn="bfdIfPol-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + name=name, + descr=description, + adminSt=admin_state, + ) + + if detection_multiplier and detection_multiplier not in range(1, 50): + module.fail_json(msg='The "detection_multiplier" must be a value between 1 and 50') + else: + class_config["detectMult"] = detection_multiplier + if min_transmit_interval and min_transmit_interval not in range(50, 999): + module.fail_json(msg='The "min_transmit_interval" must be a value between 50 and 999') + else: + class_config["minTxIntvl"] = min_transmit_interval + if min_receive_interval and min_receive_interval not in range(50, 999): + module.fail_json(msg='The "min_receive_interval" must be a value between 50 and 999') + else: + class_config["minRxIntvl"] = min_receive_interval + + aci.payload( + aci_class="bfdIfPol", + class_config=class_config, + ) + + aci.get_diff(aci_class="bfdIfPol") + + 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_bfd_multihop.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_bfd_multihop.py new file mode 100644 index 000000000..31cc9321c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_bfd_multihop.py @@ -0,0 +1,318 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain (@anvjain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_bfd_multihop +short_description: Manage BFD Multihop Interface policies (bfd:MhIfPol) +description: +- Manage BFD Multihop Interface policy configuration on Cisco ACI fabrics +- Only available in APIC version 5.2 or later +options: + tenant: + description: + - Name of an existing tenant + type: str + name: + description: + - Name of the BFD Multihop Interface policy + type: str + aliases: [ bfd_multihop_interface_policy ] + description: + description: + - Description of the BFD Multihop Interface policy + type: str + admin_state: + description: + - Admin state of the BFD Multihop Interface policy + - APIC sets the default value to enabled. + type: str + choices: [ enabled, disabled ] + detection_multiplier: + description: + - Detection multiplier of the BFD Multihop Interface policy + - APIC sets the default value to 3. + - Allowed range is 1-50. + type: int + min_transmit_interval: + description: + - Minimum transmit (Tx) interval of the BFD Multihop Interface policy + - APIC sets the default value to 250 + - Allowed range is 250-999 + type: int + min_receive_interval: + description: + - Minimum receive (Rx) interval of the BFD Multihop Interface policy + - APIC sets the default value to 250 + - Allowed range is 250-999 + 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(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(bfd:MhIfPol) + link: https://developer.cisco.com/docs/apic-mim-ref/ +- module: cisco.aci.aci_tenant +author: +- Anvitha Jain (@anvjain) +""" + +EXAMPLES = r""" +- name: Add a new BFD Multihop Interface policy + cisco.aci.aci_interface_policy_bfd_multihop: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: ansible_bfd_multihop_interface_policy + description: Ansible BFD Multihop Interface Policy + state: present + delegate_to: localhost + +- name: Remove a BFD Multihop Interface policy + cisco.aci.aci_interface_policy_bfd_multihop: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: ansible_bfd_multihop_interface_policy + state: absent + delegate_to: localhost + +- name: Query a BFD Multihop Interface policy + cisco.aci.aci_interface_policy_bfd_multihop: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + name: ansible_bfd_multihop_interface_policy + state: query + delegate_to: localhost + +- name: Query all BFD Multihop Interface policies in a specific tenant + cisco.aci.aci_interface_policy_bfd_multihop: + host: apic + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + 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: 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=["bfd_multihop_interface_policy"]), + description=dict(type="str"), + admin_state=dict(type="str", choices=["enabled", "disabled"]), + detection_multiplier=dict(type="int"), + min_transmit_interval=dict(type="int"), + min_receive_interval=dict(type="int"), + 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") + admin_state = module.params.get("admin_state") + detection_multiplier = module.params.get("detection_multiplier") + min_transmit_interval = module.params.get("min_transmit_interval") + min_receive_interval = module.params.get("min_receive_interval") + + 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="bfdMhIfPol", + aci_rn="bfdMhIfPol-{0}".format(name), + module_object=name, + target_filter={"name": name}, + ), + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + name=name, + descr=description, + adminSt=admin_state, + ) + + if detection_multiplier and detection_multiplier not in range(1, 50): + module.fail_json(msg='The "detection_multiplier" must be a value between 1 and 50') + else: + class_config["detectMult"] = detection_multiplier + if min_transmit_interval and min_transmit_interval not in range(250, 999): + module.fail_json(msg='The "min_transmit_interval" must be a value between 250 and 999') + else: + class_config["minTxIntvl"] = min_transmit_interval + if min_receive_interval and min_receive_interval not in range(250, 999): + module.fail_json(msg='The "min_receive_interval" must be a value between 250 and 999') + else: + class_config["minRxIntvl"] = min_receive_interval + + aci.payload( + aci_class="bfdMhIfPol", + class_config=class_config, + ) + + aci.get_diff(aci_class="bfdMhIfPol") + + 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_eigrp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_eigrp.py new file mode 100644 index 000000000..7e0e64d53 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_eigrp.py @@ -0,0 +1,371 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Lukas Holub (@lukasholub) +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_eigrp +short_description: Manage EIGRP interface policies (eigrp:IfPol) +description: +- Manage EIGRP interface policies for Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing Tenant. + type: str + aliases: [ tenant_name ] + eigrp: + description: + - The EIGRP interface policy name. + - Note that you cannot change this name after the object has been created. + type: str + aliases: [ eigrp_interface, name ] + bandwidth: + description: + - The EIGRP bandwidth in kbps, overrides the bandwidth configured on an interface. + - This is used to influence path selection. + - Accepted values range between C(0) and C(2560000000). + - The APIC defaults to C(0) when unset during creation. + type: int + control_state: + description: + - The interface policy control state. + - 'This is a list of one or more of the following controls:' + - C(bfd) -- Enable Bidirectional Forwarding Detection. + - C(nexthop_self) -- Nexthop Self. + - C(split_horizon) -- Split Horizon. + - C(passive) -- The interface does not participate in the EIGRP protocol and + will not establish adjacencies or send routing updates. + - The APIC defaults to C([split_horizon, nexthop_self]) when unset during creation. + type: list + elements: str + choices: [ bfd, nexthop_self, passive, split_horizon ] + delay: + description: + - The EIGRP throughput delay, overrides the delay configured on an interface. + - This is used to influence path selection. + - The APIC defaults to C(0) when unset during creation. + type: int + delay_unit: + description: + - The EIGRP delay units, Wide metrics can use picoseconds accuracy for delay. + - The APIC defaults to C(tens_of_microseconds) when unset during creation. + type: str + choices: [ picoseconds, tens_of_microseconds ] + hello_interval: + description: + - The time interval in seconds between hello packets that EIGRP sends on the interface. + - The smaller the hello interval, the faster topological changes will be detected, but more routing traffic will ensue. + - Accepted values range between C(1) and C(65535). + - The APIC defaults to C(5) when unset during creation. + type: int + hold_interval: + description: + - The time period of time in seconds before declaring that the neighbor is down. + - Accepted values range between C(1) and C(65535). + - The APIC defaults to C(15) when unset during creation. + type: int + description: + description: + - The description of the EIGRP interface 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(eigrp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Lukas Holub (@lukasholub) +""" + +EXAMPLES = r""" +- name: Create an EIGRP interface policy + cisco.aci.aci_interface_policy_eigrp: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + eigrp: eigrp1 + bandwidth: 10000 + control_state: [split-horizon, nh-self] + delay: 10 + delay_unit: tens_of_micro + hello_interval: 5 + hold_interval: 15 + state: present + delegate_to: localhost + +- name: Delete EIGRRP interface policy + cisco.aci.aci_interface_policy_eigrp: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + eigrp: eigrp1 + state: present + delegate_to: localhost + +- name: Query an EIGRRP interface policy + cisco.aci.aci_interface_policy_eigrp: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + eigrp: eigrp1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all EIGRP interface policies in tenant production + cisco.aci.aci_interface_policy_eigrp: + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import ( + MATCH_EIGRP_INTERFACE_POLICY_DELAY_UNIT_MAPPING, + MATCH_EIGRP_INTERFACE_POLICY_CONTROL_STATE_MAPPING, +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + eigrp=dict(type="str", aliases=["eigrp_interface", "name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + bandwidth=dict(type="int"), + control_state=dict(type="list", elements="str", choices=["bfd", "nexthop_self", "passive", "split_horizon"]), + delay=dict(type="int"), + delay_unit=dict(type="str", choices=["picoseconds", "tens_of_microseconds"]), + hello_interval=dict(type="int"), + hold_interval=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", ["eigrp", "tenant"]], + ["state", "present", ["eigrp", "tenant"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + eigrp = module.params.get("eigrp") + delay = module.params.get("delay") + delay_unit = MATCH_EIGRP_INTERFACE_POLICY_DELAY_UNIT_MAPPING.get(module.params.get("delay_unit")) + description = module.params.get("description") + name_alias = module.params.get("name_alias") + state = module.params.get("state") + + bandwidth = module.params.get("bandwidth") + if bandwidth is not None and bandwidth not in range(2560000001): + module.fail_json(msg="Parameter 'bandwidth' is only valid in range between 0 and 2560000000.") + + 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 65535.") + + hold_interval = module.params.get("hold_interval") + if hold_interval is not None and hold_interval not in range(1, 65536): + module.fail_json(msg="Parameter 'hold_interval' is only valid in range between 1 and 65535.") + + if module.params.get("control_state"): + control_state = ",".join([MATCH_EIGRP_INTERFACE_POLICY_CONTROL_STATE_MAPPING.get(v) for v in module.params.get("control_state")]) + else: + control_state = 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="eigrpIfPol", + aci_rn="eigrpIfPol-{0}".format(eigrp), + module_object=eigrp, + target_filter={"name": eigrp}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="eigrpIfPol", + class_config=dict( + name=eigrp, + descr=description, + bw=bandwidth, + ctrl=control_state, + delay=delay, + delayUnit=delay_unit, + helloIntvl=hello_interval, + holdIntvl=hold_interval, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="eigrpIfPol") + + 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_hsrp.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_hsrp.py new file mode 100644 index 000000000..c94b5af42 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_hsrp.py @@ -0,0 +1,310 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Shreyas Srish (@shrsr) <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_hsrp +short_description: Manage HSRP interface policies (hsrp:IfPol) +description: +- Manage HSRP interface policies on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the Tenant the HSRP interface policy should belong to. + type: str + aliases: [ tenant_name ] + hsrp: + description: + - The HSRP interface policy name. + type: str + aliases: [ hsrp_interface, name ] + description: + description: + - The description of the HSRP interface. + type: str + aliases: [ descr ] + controls: + description: + - The interface policy controls. + type: list + elements: str + choices: [ bfd, bia ] + delay: + description: + - The administrative port delay of HSRP interface policy. + - This is only valid in range between 1 and 10000. + type: int + reload_delay: + description: + - The option for reload delay of HSRP interface policy. + - This is only valid in range between 1 and 10000. + 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 +- cisco.aci.owner + +notes: +- The C(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) can be used for this. +seealso: +- module: aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(hsrp:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a HSRP interface policy + cisco.aci.aci_interface_policy_hsrp: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + hsrp: hsrp1 + controls: bfd + delay: 50 + reload_delay: 100 + state: present + delegate_to: localhost + +- name: Delete a HSRP interface policy + cisco.aci.aci_interface_policy_hsrp: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + hsrp: hsrp1 + state: absent + delegate_to: localhost + +- name: Query a HSRP interface policy + cisco.aci.aci_interface_policy_hsrp: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + hsrp: hsrp1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all HSRP interface policies in tenant production + cisco.aci.aci_interface_policy_hsrp: + 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 + hsrp=dict(type="str", aliases=["hsrp_interface", "name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + controls=dict(type="list", elements="str", choices=["bfd", "bia"]), + reload_delay=dict(type="int"), + delay=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", ["tenant", "hsrp"]], + ["state", "present", ["tenant", "hsrp"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + hsrp = module.params.get("hsrp") + description = module.params.get("description") + controls = ",".join(module.params.get("controls")) if module.params.get("controls") else None + + reload_delay = module.params.get("reload_delay") + if reload_delay is not None and reload_delay not in range(1, 10000): + module.fail_json(msg="Parameter 'reload_delay' is only valid in range between 1 and 10000.") + + delay = module.params.get("delay") + if delay is not None and delay not in range(1, 10000): + module.fail_json(msg="Parameter 'delay' is only valid in range between 1 and 10000.") + + 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="hsrpIfPol", + aci_rn="hsrpIfPol-{0}".format(hsrp), + module_object=hsrp, + target_filter={"name": hsrp}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="hsrpIfPol", + class_config=dict( + name=hsrp, + descr=description, + ctrl=controls, + reloadDelay=reload_delay, + delay=delay, + ), + ) + + aci.get_diff(aci_class="hsrpIfPol") + + 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_fc_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py index 6c3436459..b77eb16fe 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_fc_policy_group.py @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_interface_policy_leaf_fc_policy_group -short_description: Manage Fibre Channel (FC) interface policy groups (infra:FcAccBndlGrp, infra:FcAccPortGrp) +short_description: Manage Fibre Channel (FC) interface policy groups (infra:FcAccBndlGrp and infra:FcAccPortGrp) description: - Manage Fibre Channel (FC) interface policy groups on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py index da2e90adf..d106df250 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_leaf_policy_group.py @@ -14,7 +14,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_interface_policy_leaf_policy_group -short_description: Manage fabric interface policy leaf policy groups (infra:AccBndlGrp, infra:AccPortGrp) +short_description: Manage fabric interface policy leaf policy groups (infra:AccBndlGrp and infra:AccPortGrp) description: - Manage fabric interface policy leaf policy groups on Cisco ACI fabrics. options: @@ -624,34 +624,6 @@ def main(): ), ), ), - dict( - infraRsLinkFlapPol=dict( - attributes=dict( - tnFabricLinkFlapPolName=link_flap_policy, - ), - ), - ), - dict( - infraRsQosLlfcIfPol=dict( - attributes=dict( - tnQosLlfcIfPolName=link_level_flow_control, - ), - ), - ), - dict( - infraRsMacsecIfPol=dict( - attributes=dict( - tnMacsecIfPolName=mac_sec_interface_policy, - ), - ), - ), - dict( - infraRsCoppIfPol=dict( - attributes=dict( - tnCoppIfPolName=copp_policy, - ), - ), - ), ] child_classes = [ @@ -671,10 +643,6 @@ def main(): "infraRsQosSdIfPol", "infraRsStormctrlIfPol", "infraRsStpIfPol", - "infraRsLinkFlapPol", - "infraRsQosLlfcIfPol", - "infraRsMacsecIfPol", - "infraRsCoppIfPol", ] # Add infraRsattEntP binding only when aep is defined @@ -728,6 +696,7 @@ def main(): ) ) child_classes.append("infraRsOpticsIfPol") + if dwdm is not None: child_configs.append( dict( @@ -739,6 +708,7 @@ def main(): ) ) child_classes.append("infraRsDwdmIfPol") + if port_authentication is not None: child_configs.append( dict( @@ -750,6 +720,7 @@ def main(): ) ) child_classes.append("infraRsL2PortAuthPol") + if poe_interface_policy is not None: child_configs.append( dict( @@ -762,6 +733,54 @@ def main(): ) child_classes.append("infraRsPoeIfPol") + if link_flap_policy is not None: + child_configs.append( + dict( + infraRsLinkFlapPol=dict( + attributes=dict( + tnFabricLinkFlapPolName=link_flap_policy, + ), + ), + ), + ) + child_classes.append("infraRsLinkFlapPol") + + if link_level_flow_control is not None: + child_configs.append( + dict( + infraRsQosLlfcIfPol=dict( + attributes=dict( + tnQosLlfcIfPolName=link_level_flow_control, + ), + ), + ), + ) + child_classes.append("infraRsQosLlfcIfPol") + + if mac_sec_interface_policy is not None: + child_configs.append( + dict( + infraRsMacsecIfPol=dict( + attributes=dict( + tnMacsecIfPolName=mac_sec_interface_policy, + ), + ), + ), + ) + child_classes.append("infraRsMacsecIfPol") + + if copp_policy is not None: + child_configs.append( + dict( + infraRsCoppIfPol=dict( + attributes=dict( + tnCoppIfPolName=copp_policy, + ), + ), + ), + ) + child_classes.append("infraRsCoppIfPol") + aci.construct_url( root_class=dict( aci_class=aci_class_name, 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 index 5c0120d4d..1980721cc 100644 --- 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 @@ -36,9 +36,10 @@ options: speed: description: - Determines the interface policy administrative port speed. + - The C(auto) option is only supported in APIC version 5.2 or later. - The APIC defaults to C(inherit) when unset during creation. type: str - choices: [ 100M, 1G, 10G, 25G, 40G, 50G, 100G, 200G, 400G, inherit ] + choices: [ 100M, 1G, 10G, 25G, 40G, 50G, 100G, 200G, 400G, auto, inherit ] default: inherit link_debounce_interval: description: @@ -231,7 +232,7 @@ def main(): 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"]), + speed=dict(type="str", default="inherit", choices=["100M", "1G", "10G", "25G", "40G", "50G", "100G", "200G", "400G", "auto", "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"] diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_pim.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_pim.py new file mode 100644 index 000000000..ad47fb836 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_pim.py @@ -0,0 +1,444 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_pim +short_description: Manage Protocol-Independent Multicast (PIM) interface policies (pim:IfPol) +description: +- Manage Protocol Independent Multicast interface policies for Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing Tenant. + type: str + aliases: [ tenant_name ] + pim: + description: + - The PIM interface policy name. + - The name cannot be changed after the object has been created. + type: str + aliases: [ pim_interface_policy, name ] + authentication_key: + description: + - The authentication key. + type: str + aliases: [ auth_key ] + authentication_type: + description: + - The authentication type. + type: str + choices: [ none, md5_hmac ] + aliases: [ auth_type ] + control_state: + description: + - The PIM interface policy control state. + - 'This is a list of one or more of the following controls:' + - C(multicast_domain_boundary) -- Boundary of Multicast domain. + - C(strict_rfc_compliant) -- Only listen to PIM protocol packets. + - C(passive) -- Do not send/receive PIM protocol packets. + type: list + elements: str + choices: [ multicast_domain_boundary, strict_rfc_compliant, passive ] + designated_router_delay: + description: + - The PIM designated router delay. + - Accepted values range between C(1) and C(65535). + - The APIC defaults to C(3) when unset during creation. + type: int + aliases: [ delay ] + designated_router_priority: + description: + - The PIM designated router priority. + - Accepted values range between C(1) and C(4294967295). + - The APIC defaults to C(1) when unset during creation. + type: int + aliases: [ prio ] + hello_interval: + description: + - The time interval in seconds between hello packets that PIM sends on the interface. + - The smaller the hello interval, the faster topological changes will be detected, but more routing traffic will ensue. + - Accepted values range between C(1) and C(18724286). + - The APIC defaults to C(30000) when unset during creation. + type: int + join_prune_interval: + description: + - The join prune interval in seconds. + - Accepted values range between C(60) and C(65520). + - The APIC defaults to C(60) when unset during creation. + type: int + aliases: [ jp_interval ] + inbound_join_prune_filter_policy: + description: + - The interface-level inbound join/prune filter policy. + - The M(cisco.aci.aci_pim_route_map_policy) can be used for this. + - To delete it, pass an empty string. + type: str + aliases: [ inbound_filter ] + outbound_join_prune_filter_policy: + description: + - The interface-level outbound join/prune filter policy. + - The M(cisco.aci.aci_pim_route_map_policy) can be used for this. + - To delete it, pass an empty string. + type: str + aliases: [ outbound_filter ] + neighbor_filter_policy: + description: + - The Interface-level neighbor filter policy. + - The M(cisco.aci.aci_pim_route_map_policy) can be used for this. + - To delete it, pass an empty string. + type: str + aliases: [ neighbor_filter ] + description: + description: + - The description of the PIM interface 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 +- module: cisco.aci.aci_pim_route_map_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(pim:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a PIM interface policy + cisco.aci.aci_interface_policy_pim: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + pim: my_pim_policy + control_state: [multicast_domain_boundary, strict_rfc_compliant] + designated_router_delay: 10 + designated_router_priority: tens_of_micro + hello_interval: 5 + join_prune_interval: 15 + inbound_join_prune_filter_policy: my_pim_route_map_policy_1 + outbound_join_prune_filter_policy: my_pim_route_map_policy_2 + neighbor_filter_policy: my_pim_route_map_policy_3 + state: present + delegate_to: localhost + +- name: Query a PIM interface policy + cisco.aci.aci_interface_policy_pim: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + pim: my_pim_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query all PIM interface policies in tenant production + cisco.aci.aci_interface_policy_pim: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + state: query + delegate_to: localhost + register: query_result + +- name: Delete a PIM interface policy + cisco.aci.aci_interface_policy_pim: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + pim: my_pim_policy + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import ( + MATCH_PIM_INTERFACE_POLICY_CONTROL_STATE_MAPPING, + MATCH_PIM_INTERFACE_POLICY_AUTHENTICATION_TYPE_MAPPING, +) + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + pim=dict(type="str", aliases=["pim_interface_policy", "name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + authentication_key=dict(type="str", aliases=["auth_key"], no_log=True), + authentication_type=dict(type="str", choices=["none", "md5_hmac"], aliases=["auth_type"]), + control_state=dict(type="list", elements="str", choices=["multicast_domain_boundary", "strict_rfc_compliant", "passive"]), + designated_router_delay=dict(type="int", aliases=["delay"]), + designated_router_priority=dict(type="int", aliases=["prio"]), + hello_interval=dict(type="int"), + join_prune_interval=dict(type="int", aliases=["jp_interval"]), + inbound_join_prune_filter_policy=dict(type="str", aliases=["inbound_filter"]), + outbound_join_prune_filter_policy=dict(type="str", aliases=["outbound_filter"]), + neighbor_filter_policy=dict(type="str", aliases=["neighbor_filter"]), + 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", ["pim", "tenant"]], + ["state", "present", ["pim", "tenant"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + pim = module.params.get("pim") + authentication_key = module.params.get("authentication_key") + authentication_type = MATCH_PIM_INTERFACE_POLICY_AUTHENTICATION_TYPE_MAPPING.get(module.params.get("authentication_type")) + description = module.params.get("description") + name_alias = module.params.get("name_alias") + state = module.params.get("state") + + designated_router_delay = module.params.get("designated_router_delay") + if designated_router_delay is not None and designated_router_delay not in range(1, 65536): + module.fail_json(msg="Parameter 'designated_router_delay' is only valid in range between 1 and 65535.") + + designated_router_priority = module.params.get("designated_router_priority") + if designated_router_priority is not None and designated_router_priority not in range(1, 4294967296): + module.fail_json(msg="Parameter 'designated_router_priority' is only valid in range between 1 and 4294967295.") + + hello_interval = module.params.get("hello_interval") + if hello_interval is not None and hello_interval not in range(1, 18724287): + module.fail_json(msg="Parameter 'hello_interval' is only valid in range between 1 and 18724286.") + + join_prune_interval = module.params.get("join_prune_interval") + if join_prune_interval is not None and join_prune_interval not in range(60, 65521): + module.fail_json(msg="Parameter 'join_prune_interval' is only valid in range between 60 and 65520.") + + if module.params.get("control_state"): + control_state = ",".join([MATCH_PIM_INTERFACE_POLICY_CONTROL_STATE_MAPPING.get(v) for v in module.params.get("control_state")]) + else: + control_state = None + + child_classes = dict( + pimJPInbFilterPol=module.params.get("inbound_join_prune_filter_policy"), + pimJPOutbFilterPol=module.params.get("outbound_join_prune_filter_policy"), + pimNbrFilterPol=module.params.get("neighbor_filter_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="pimIfPol", + aci_rn="pimifpol-{0}".format(pim), + module_object=pim, + target_filter={"name": pim}, + ), + child_classes=list(child_classes.keys()), + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + for class_name, class_input in child_classes.items(): + if class_input is not None: + if class_input == "" and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("pimIfPol", {}).get("children", {}): + if child.get(class_name): + child_configs.append(dict([(class_name, dict(attributes=dict(status="deleted")))])) + elif class_input != "": + child_configs.append( + dict( + [ + ( + class_name, + dict( + attributes=dict(), + children=[ + dict( + rtdmcRsFilterToRtMapPol=dict( + attributes=dict( + tDn="uni/tn-{0}/rtmap-{1}".format(tenant, class_input), + ), + ) + ) + ], + ), + ) + ] + ) + ) + + aci.payload( + aci_class="pimIfPol", + class_config=dict( + name=pim, + descr=description, + authKey=authentication_key, + authT=authentication_type, + ctrl=control_state, + drDelay=designated_router_delay, + drPrio=designated_router_priority, + helloItvl=hello_interval, + jpInterval=join_prune_interval, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="pimIfPol") + + 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_storm_control.py b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_storm_control.py new file mode 100644 index 000000000..19b67070d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_interface_policy_storm_control.py @@ -0,0 +1,434 @@ +#!/usr/bin/python + +# Copyright: (c) 2023, Eric Girard <@netgirard> +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_interface_policy_storm_control +short_description: Manage Storm Control interface policies (stormctrl:IfPol) +description: +- Manage Storm Control interface policies on Cisco ACI fabrics. +options: + storm_control_policy: + description: + - The Storm Control interface policy name. + type: str + aliases: [ storm_control, storm_control_name, name ] + description: + description: + - The description for the Storm interface policy name. + 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 + storm_control_action: + description: + - The storm control action to take when triggered. + type: str + choices: [drop, shutdown] + storm_control_soak_action: + description: + - The number of instances before triggering shutdown. + type: int + all_types_configuration: + description: + - The rates configuration for all packets type. + type: dict + aliases: [ all_types ] + suboptions: + rate: + description: + - The rate for all packet types. + type: str + burst_rate: + description: + - The burst rate of all packet types. + type: str + rate_type: + description: + - The type of rate of all packet types. + - Choice between percentage of the bandiwth C(percentage) or packet per second C(pps) + type: str + choices: [ percentage, pps ] + required: true + broadcast_configuration: + description: + - The rates configuration of broadcast packets. + type: dict + aliases: [ broadcast ] + suboptions: + rate: + description: + - The rate for broadcast packets. + type: str + burst_rate: + description: + - The burst rate of broadcast packets. + type: str + rate_type: + description: + - The type of rate of all packet types. + - Choice between percentage of the bandiwth C(percentage) or packet per second C(pps) + type: str + choices: [ percentage, pps ] + required: true + multicast_configuration: + description: + - The rates configuration of multicast packets. + type: dict + aliases: [ multicast ] + suboptions: + rate: + description: + - The rate for multicast packets. + type: str + burst_rate: + description: + - The burst rate of multicast packets. + type: str + rate_type: + description: + - The type of rate of all packet types. + - Choice between percentage of the bandiwth C(percentage) or packet per second C(pps) + type: str + choices: [ percentage, pps ] + required: true + unicast_configuration: + description: + - The rates configuration of unicast packets. + type: dict + aliases: [ unicast ] + suboptions: + rate: + description: + - The rate for unicast packets. + type: str + burst_rate: + description: + - The burst rate of unicast packets. + type: str + rate_type: + description: + - The type of rate of all packet types. + - Choice between percentage of the bandiwth C(percentage) or packet per second C(pps) + type: str + choices: [ percentage, pps ] + required: true +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(stormctrl:IfPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Eric Girard (@netgirard) +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new Storm Control Interface Policy + cisco.aci.aci_interface_policy_storm_control: + host: apic + username: admin + password: SomeSecretPassword + storm_control_policy: my_storm_control_policy + description: My Storm Control Policy + all_types_configuration: + rate: 80 + burst_rate: 100 + rate_type: percentage + storm_control_action: shutdown + storm_control_soak_action: 5 + state: present + delegate_to: localhost + +- name: Query a Storm Control Interface Policy + cisco.aci.aci_interface_policy_storm_control: + host: apic + username: admin + password: SomeSecretPassword + storm_control_policy: my_storm_control_policy + state: query + delegate_to: localhost + +- name: Query all Storm Control Interface Policies + cisco.aci.aci_interface_policy_storm_control: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete a Storm Control Interface Policy + cisco.aci.aci_interface_policy_storm_control: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + storm_control_policy: my_storm_control_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: + [ + { + "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.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, + storm_control_policy_rate_spec, +) + + +def get_rates_configuration(module, configuration, percentage, pps, burst_percentage, burst_pps): + if configuration is None: + return {} + rate = configuration.get("rate") + burst_rate = configuration.get("burst_rate") + rate_type = configuration.get("rate_type") + + if rate_type == "percentage": + for rate_name, rate_value in dict(rate=rate, burst_rate=burst_rate).items(): + if rate_value is None or not (0 <= float(rate_value) <= 100): + module.fail_json( + msg="If argument rate_type is percentage, the {0} needs to be a value between 0 and 100 inclusive, got {1}".format( + rate_name, + rate_value, + ) + ) + return { + percentage: "{0:.6f}".format(float(rate)), + pps: "unspecified", + burst_percentage: "{0:.6f}".format(float(burst_rate)), + burst_pps: "unspecified", + } + elif rate_type == "pps": + return {pps: rate, burst_pps: burst_rate} + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + storm_control_policy=dict( + type="str", required=False, aliases=["name", "storm_control", "storm_control_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"), + all_types_configuration=dict(type="dict", options=storm_control_policy_rate_spec(), aliases=["all_types"]), + broadcast_configuration=dict(type="dict", options=storm_control_policy_rate_spec(), aliases=["broadcast"]), + multicast_configuration=dict(type="dict", options=storm_control_policy_rate_spec(), aliases=["multicast"]), + unicast_configuration=dict(type="dict", options=storm_control_policy_rate_spec(), aliases=["unicast"]), + storm_control_action=dict(type="str", choices=["drop", "shutdown"]), + storm_control_soak_action=dict(type="int"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["storm_control_policy"]], + ["state", "present", ["storm_control_policy"]], + ], + mutually_exclusive=[ + ("all_types_configuration", "broadcast_configuration"), + ("all_types_configuration", "multicast_configuration"), + ("all_types_configuration", "unicast_configuration"), + ], + ) + + aci = ACIModule(module) + + storm_control_policy = module.params.get("storm_control_policy") + description = module.params.get("description") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + storm_control_action = module.params.get("storm_control_action") + storm_control_soak_action = module.params.get("storm_control_soak_action") + all_types_configuration = module.params.get("all_types_configuration") + broadcast_configuration = module.params.get("broadcast_configuration") + multicast_configuration = module.params.get("multicast_configuration") + unicast_configuration = module.params.get("unicast_configuration") + + rates_input = {} + + if all_types_configuration is not None: + rates_input.update(get_rates_configuration(module, all_types_configuration, "rate", "ratePps", "burstRate", "burstPps")) + storm_control_types = "Invalid" + elif any([broadcast_configuration, multicast_configuration, unicast_configuration]): + rates_input.update(get_rates_configuration(module, broadcast_configuration, "bcRate", "bcRatePps", "bcBurstRate", "bcBurstPps")) + rates_input.update(get_rates_configuration(module, multicast_configuration, "mcRate", "mcRatePps", "mcBurstRate", "mcBurstPps")) + rates_input.update(get_rates_configuration(module, unicast_configuration, "uucRate", "uucRatePps", "uucBurstRate", "uucBurstPps")) + storm_control_types = "Valid" + + aci.construct_url( + root_class=dict( + aci_class="infraInfra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="stormctrlIfPol", + aci_rn="stormctrlifp-{0}".format(storm_control_policy), + module_object=storm_control_policy, + target_filter={"name": storm_control_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + name=storm_control_policy, + descr=description, + nameAlias=name_alias, + isUcMcBcStormPktCfgValid=storm_control_types, + stormCtrlAction=storm_control_action, + stormCtrlSoakInstCount=storm_control_soak_action, + ) + class_config.update(rates_input) + aci.payload( + aci_class="stormctrlIfPol", + class_config=class_config, + ) + + aci.get_diff(aci_class="stormctrlIfPol") + + 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_key_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_key_policy.py new file mode 100644 index 000000000..27ebe292a --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_key_policy.py @@ -0,0 +1,325 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_key_policy +short_description: Manage Key Policy (fv:KeyPol) +description: +- Manage Key Policies for KeyChain Policies on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + keychain_policy: + description: + - The name of an existing keychain policy. + type: str + aliases: [ keychain_policy_name ] + id: + description: + - The object identifier. + type: int + start_time: + description: + - The start time of the key policy. + - The APIC defaults to C(now) when unset during creation. + - The format is YYYY-MM-DD HH:MM:SS + type: str + end_time: + description: + - The end time of the key policy. + - The APIC defaults to C(infinite) when unset during creation. + - The format is YYYY-MM-DD HH:MM:SS + type: str + pre_shared_key: + description: + - The pre-shared authentifcation key. + - When using I(pre_shared_key) this module will always show as C(changed) as the module cannot know what the currently configured key is. + type: str + description: + description: + - The description for the keychain 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and C(keychain_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_keychain_policy) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_keychain_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new key policy + cisco.aci.aci_key_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + keychain_policy: my_keychain_policy + id: 1 + start_time: now + end_time: infinite + pre_shared_key: my_password + state: present + delegate_to: localhost + +- name: Delete an key policy + cisco.aci.aci_key_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + keychain_policy: my_keychain_policy + id: 1 + state: absent + delegate_to: localhost + +- name: Query an key policy + cisco.aci.aci_key_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + keychain_policy: my_keychain_policy + id: 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all key policies in my_keychain_policy + cisco.aci.aci_key_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + keychain_policy: my_keychain_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"]), + keychain_policy=dict(type="str", aliases=["keychain_policy_name"], no_log=False), + id=dict(type="int"), + start_time=dict(type="str"), + end_time=dict(type="str"), + pre_shared_key=dict(type="str", no_log=True), + 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", ["tenant", "keychain_policy", "id"]], + ["state", "present", ["tenant", "keychain_policy", "id"]], + ], + ) + + tenant = module.params.get("tenant") + keychain_policy = module.params.get("keychain_policy") + id = module.params.get("id") + start_time = module.params.get("start_time") + end_time = module.params.get("end_time") + pre_shared_key = module.params.get("pre_shared_key") + description = module.params.get("description") + 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="fvKeyChainPol", + aci_rn="keychainp-{0}".format(keychain_policy), + module_object=keychain_policy, + target_filter={"name": keychain_policy}, + ), + subclass_2=dict( + aci_class="fvKeyPol", + aci_rn="keyp-{0}".format(id), + module_object=id, + target_filter={"id": id}, + ), + ) + + aci.get_existing() + + if state == "present": + class_config = dict( + id=id, + startTime=start_time, + endTime=end_time, + descr=description, + ) + if pre_shared_key is not None: + class_config.update(preSharedKey=pre_shared_key) + + aci.payload( + aci_class="fvKeyPol", + class_config=class_config, + ) + + aci.get_diff(aci_class="fvKeyPol") + + 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_keychain_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_keychain_policy.py new file mode 100644 index 000000000..a9776cf59 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_keychain_policy.py @@ -0,0 +1,276 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_keychain_policy +short_description: Manage KeyChain Policy (fv:KeyChainPol) +description: +- Manage KeyChain Policies for tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + keychain_policy: + description: + - The name of the keychain policy. + type: str + aliases: [ keychain_policy_name, name ] + description: + description: + - The description for the keychain 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 +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) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new keychain policy + cisco.aci.aci_keychain_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + keychain_policy: my_keychain_policy + state: present + delegate_to: localhost + +- name: Delete an keychain policy + cisco.aci.aci_keychain_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + keychain_policy: my_keychain_policy + state: absent + delegate_to: localhost + +- name: Query an keychain policy + cisco.aci.aci_keychain_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + keychain_policy: my_keychain_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query an keychain policy in my_tenant + cisco.aci.aci_keychain_policy: + host: apic + username: admin + password: SomeSecretPassword + 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( + tenant=dict(type="str", aliases=["tenant_name"]), + keychain_policy=dict(type="str", aliases=["keychain_policy_name", "name"], no_log=False), + 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", ["tenant", "keychain_policy"]], + ["state", "present", ["tenant", "keychain_policy"]], + ], + ) + + tenant = module.params.get("tenant") + description = module.params.get("description") + keychain_policy = module.params.get("keychain_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="fvKeyChainPol", + aci_rn="keychainp-{0}".format(keychain_policy), + module_object=keychain_policy, + target_filter={"name": keychain_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="fvKeyChainPol", + class_config=dict( + name=keychain_policy, + descr=description, + ), + ) + + aci.get_diff(aci_class="fvKeyChainPol") + + 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 index 974c3c8e9..fdba63ef5 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l2out.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l2out.py @@ -14,7 +14,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_l2out -short_description: Manage Layer2 Out (L2Out) objects. +short_description: Manage Layer2 Out (L2Out) objects (l2ext:Out) description: - Manage Layer2 Out configuration on Cisco ACI fabrics. options: @@ -64,7 +64,7 @@ notes: 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). + description: More information about the internal APIC class B(l2ext:Out). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Sudhakar Shet Kudtarkar (@kudtarkar1) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l2out_extepg.py b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_extepg.py index 3df19e9da..d633a7aae 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l2out_extepg.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l2out_extepg.py @@ -15,9 +15,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_l2out_extepg -short_description: Manage External Network Instance (L2Out External EPG) objects (l2extInstP). +short_description: Manage External Network Instance (L2Out External EPG) objects (l2ext:InstP). description: -- Manage External Network Instance (L2Out External EPG) objects (l2extInstP) on ACI fabrics. +- Manage External Network Instance (L2Out External EPG) objects on ACI fabrics. options: tenant: description: @@ -63,7 +63,7 @@ notes: 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). + description: More information about the internal APIC class B(l2ext:InstP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Sudhakar Shet Kudtarkar (@kudtarkar1) 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 index 205be3795..0ed57326f 100644 --- 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 @@ -15,7 +15,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_l2out_extepg_to_contract -short_description: Bind Contracts to L2 External End Point Groups (EPGs) +short_description: Bind Contracts to L2 External End Point Groups (EPGs) (fv:RsCons and fv:RsProv) description: - Bind Contracts to L2 External End Point Groups (EPGs) on ACI fabrics. options: @@ -69,7 +69,7 @@ notes: 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). + description: More information about the internal APIC classes B((fv:RsCons) B(fv:RsProv). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Sudhakar Shet Kudtarkar (@kudtarkar1) 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 index 6be588bcd..0d8130139 100644 --- 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 @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_l2out_logical_interface_path -short_description: Manage Layer 2 Outside (L2Out) logical interface path (l2extRsPathL2OutAtt) +short_description: Manage Layer 2 Outside (L2Out) logical interface path (l2ext:RsPathL2OutAtt) description: - Manage interface path entry of L2 outside node (BD extension) on Cisco ACI fabrics. options: @@ -79,7 +79,7 @@ seealso: - module: aci_l2out_logical_interface_profile - module: aci_l2out_extepg - name: APIC Management Information Model reference - description: More information about the internal APIC classes + description: More information about the internal APIC class B(l2ext:RsPathL2OutAtt). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Oleksandr Kreshchenko (@alexkross) 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 index 93e8fd195..c1d0b3d61 100644 --- 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 @@ -55,7 +55,7 @@ seealso: - module: aci_l2out_logical_interface_path - module: aci_l2out_extepg - name: APIC Management Information Model reference - description: More information about the internal APIC classes + description: More information about the internal APIC class B(l2ext:LIfP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Oleksandr Kreshchenko (@alexkross) 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 index 12a1aa6f5..6fa04db01 100644 --- 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 @@ -49,7 +49,7 @@ seealso: - module: aci_l2out_logical_interface_path - module: aci_l2out_extepg - name: APIC Management Information Model reference - description: More information about the internal APIC classes + description: More information about the internal APIC class B(l2ext:LNodeP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Oleksandr Kreshchenko (@alexkross) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py index 56f158b3d..73d51fc63 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -34,6 +35,7 @@ options: vrf: description: - Name of the VRF being associated with the L3Out. + - SR-MPLS Infra L3out requires the VRF to be 'overlay-1'. type: str aliases: [ vrf_name ] domain: @@ -60,6 +62,8 @@ options: description: - Indicate whether MPLS (Multi-Protocol Label Switching) is enabled or not. - The APIC defaults to C(no) when unset during creation. + - SR-MPLS is only supported in APIC v5.0 and above. + - The child classes C(mplsExtP), C(mplsRsLabelPol) and C(l3extProvLbl) will be displayed in output only when C(yes). type: str choices: [ "no", "yes" ] l3protocol: @@ -70,6 +74,7 @@ options: - First example, to add BGP protocol to an l3out with OSPF protocol, the user must enter C([ bgp, ospf ]) even though "ospf" was provided before. - Second example, to change the protocol from OSPF to EIGRP, the user must simply enter C([ eigrp ]) and the previous OSPF protocol will be deleted. - To remove all existing protocols, the user must enter C([ static ]). + - SR-MPLS Infra L3out requires the l3protocol to be 'bgp'. type: list elements: str choices: [ bgp, eigrp, ospf, pim, static ] @@ -165,6 +170,7 @@ seealso: author: - Rostyslav Davydenko (@rost-d) - Gaspard Micol (@gmicol) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -444,39 +450,17 @@ def main(): dict(l3extRsL3DomAtt=dict(attributes=dict(tDn="uni/l3dom-{0}".format(domain)))), dict(l3extRsEctx=dict(attributes=dict(tnFvCtxName=vrf))), ] - if l3protocol is not None: - l3protocol_child_configs = dict( - bgp=dict(bgpExtP=dict(attributes=dict(status="deleted"))), - eigrp=dict(eigrpExtP=dict(attributes=dict(status="deleted"))), - ospf=dict(ospfExtP=dict(attributes=dict(status="deleted"))), - pim=dict(pimExtP=dict(attributes=dict(status="deleted"))), + + if tenant == "infra" and mpls == "yes": + if l3protocol != ["bgp"] and state == "present": + module.fail_json(msg="The l3protocol parameter must be 'bgp' when tenant is 'infra' and mpls is 'yes'") + if vrf != "overlay-1" and state == "present": + module.fail_json(msg="The vrf parameter must be 'overlay-1' when tenant is 'infra' and mpls is 'yes'") + child_classes += ["mplsExtP", "mplsRsLabelPol", "l3extProvLbl"] + child_configs.append( + dict(mplsExtP=dict(attributes=dict(), children=[dict(mplsRsLabelPol=dict(attributes=dict(tDn="uni/tn-infra/mplslabelpol-default")))])) ) - for protocol in l3protocol: - if protocol == "bgp": - l3protocol_child_configs["bgp"] = dict(bgpExtP=dict(attributes=dict(descr=""))) - elif protocol == "eigrp": - l3protocol_child_configs["eigrp"] = dict(eigrpExtP=dict(attributes=dict(asn=asn))) - elif protocol == "ospf": - if isinstance(ospf, dict): - ospf["area_ctrl"] = ",".join(ospf.get("area_ctrl")) - l3protocol_child_configs["ospf"] = dict( - ospfExtP=dict( - attributes=dict( - areaCost=ospf.get("area_cost"), - areaCtrl=ospf.get("area_ctrl"), - areaId=ospf.get("area_id"), - areaType=ospf.get("area_type"), - descr=ospf.get("description"), - multipodInternal=ospf.get("multipod_internal"), - nameAlias=ospf.get("name_alias"), - ) - ) - ) - else: - l3protocol_child_configs["ospf"] = dict(ospfExtP=dict(attributes=dict(descr=""))) - elif protocol == "pim": - l3protocol_child_configs["pim"] = dict(pimExtP=dict(attributes=dict(descr=""))) - child_configs.extend(list(l3protocol_child_configs.values())) + child_configs.append(dict(l3extProvLbl=dict(attributes=dict(name=l3out)))) aci.construct_url( root_class=dict( @@ -497,6 +481,46 @@ def main(): aci.get_existing() if state == "present": + if l3protocol is not None: + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("l3extOut", {}).get("children", {}): + if child.get("bgpExtP") and "bgp" not in l3protocol: + child_configs.append(dict(bgpExtP=dict(attributes=dict(status="deleted")))) + if child.get("eigrpExtP") and "eigrp" not in l3protocol: + child_configs.append(dict(eigrpExtP=dict(attributes=dict(status="deleted")))) + if child.get("ospfExtP") and "ospf" not in l3protocol: + child_configs.append(dict(ospfExtP=dict(attributes=dict(status="deleted")))) + if child.get("pimExtP") and "pim" not in l3protocol: + child_configs.append(dict(pimExtP=dict(attributes=dict(status="deleted")))) + + for protocol in l3protocol: + if protocol == "bgp": + child_configs.append(dict(bgpExtP=dict(attributes=dict(descr="")))) + elif protocol == "eigrp": + child_configs.append(dict(eigrpExtP=dict(attributes=dict(asn=asn)))) + elif protocol == "ospf": + if isinstance(ospf, dict): + ospf["area_ctrl"] = ",".join(ospf.get("area_ctrl")) + child_configs.append( + dict( + ospfExtP=dict( + attributes=dict( + areaCost=ospf.get("area_cost"), + areaCtrl=ospf.get("area_ctrl"), + areaId=ospf.get("area_id"), + areaType=ospf.get("area_type"), + descr=ospf.get("description"), + multipodInternal=ospf.get("multipod_internal"), + nameAlias=ospf.get("name_alias"), + ) + ) + ) + ) + else: + child_configs.append(dict(ospfExtP=dict(attributes=dict(descr="")))) + elif protocol == "pim": + child_configs.append(dict(pimExtP=dict(attributes=dict(descr="")))) + aci.payload( aci_class="l3extOut", class_config=dict( diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bfd_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bfd_interface_profile.py new file mode 100644 index 000000000..193ff0c1d --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bfd_interface_profile.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain (@anvjain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_bfd_interface_profile +short_description: Manage L3Out BFD Interface profiles (bfd:IfP) +description: +- Manage L3Out BFD Interface profile configuration on Cisco ACI fabrics +- Only available in APIC version 5.2 or later and for non-cloud APICs +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 ] + l3out_logical_node_profile: + description: + - Name of an existing L3Out Logical Node profile + type: str + aliases: [ logical_node_profile, logical_node_profile_name ] + l3out_logical_interface_profile: + description: + - Name of an existing L3Out Logical Interface profile + type: str + aliases: [ logical_interface_profile, logical_interface_profile_name ] + name: + description: + - Name of the L3Out BFD Interface profile object + type: str + aliases: [ bfd_multihop_interface_profile ] + name_alias: + description: + - Name Alias of the L3Out BFD Interface profile object + type: str + description: + description: + - Description of the L3Out BFD Interface profile object + type: str + aliases: [ descr ] + authentication_type: + description: + - Authentication Type of the L3Out BFD Interface profile object + - APIC sets the default value to none + type: str + choices: [ none, sha1 ] + key: + description: + - Authentication Key of the L3Out BFD Interface profile object + type: str + key_id: + description: + - Authentication Key ID of the L3Out BFD Interface profile object + - APIC sets the default value to 3 + - Allowed range is 1-255 + type: int + bfd_interface_policy: + description: + - The name of the Interface policy + type: str + aliases: [ interface_policy, interface_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 + +notes: +- The C(tenant), c(l3out), C(l3out_logical_node_profile) and C(l3out_logical_interface_profile) 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(bfd:IfP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- module: cisco.aci.aci_interface_policy_bfd +author: +- Anvitha Jain (@anvjain) +""" + +EXAMPLES = r""" +- name: Add a new L3Out BFD Interface Profile + cisco.aci.aci_l3out_bfd_interface_profile: + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + l3out: ansible_l3out + l3out_logical_node_profile: ansible_node_profile + l3out_logical_interface_profile: ansible_interface_profile + state: present + delegate_to: localhost + +- name: Query a new L3Out BFD Interface Profile + cisco.aci.aci_l3out_bfd_interface_profile: + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + l3out: ansible_l3out + l3out_logical_node_profile: ansible_node_profile + l3out_logical_interface_profile: ansible_interface_profile + state: query + delegate_to: localhost + +- name: Query all L3Out BFD Interface Profile + cisco.aci.aci_l3out_bfd_interface_profile: + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete L3Out BFD Interface Profile + cisco.aci.aci_l3out_bfd_interface_profile: + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + l3out: ansible_l3out + l3out_logical_node_profile: ansible_node_profile + l3out_logical_interface_profile: ansible_interface_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 "/></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"]), + l3out=dict(type="str", aliases=["l3out_name"]), + l3out_logical_node_profile=dict(type="str", aliases=["logical_node_profile_name", "logical_node_profile"]), + l3out_logical_interface_profile=dict(type="str", aliases=["logical_interface_profile_name", "logical_interface_profile"]), + name=dict(type="str", aliases=["bfd_multihop_interface_profile"]), + name_alias=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + authentication_type=dict(type="str", choices=["none", "sha1"]), + key=dict(type="str", no_log=True), + key_id=dict(type="int"), + bfd_interface_policy=dict(type="str", aliases=["interface_policy", "interface_policy_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", ["tenant", "l3out", "l3out_logical_node_profile", "l3out_logical_interface_profile"]], + ["state", "present", ["tenant", "l3out", "l3out_logical_node_profile", "l3out_logical_interface_profile", "bfd_interface_policy"]], + ["authentication_type", "sha1", ["key"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + l3out_logical_node_profile = module.params.get("l3out_logical_node_profile") + l3out_logical_interface_profile = module.params.get("l3out_logical_interface_profile") + name = module.params.get("name") + name_alias = module.params.get("name_alias") + description = module.params.get("description") + authentication_type = module.params.get("authentication_type") + key = module.params.get("key") + key_id = module.params.get("key_id") + bfd_interface_policy = module.params.get("bfd_interface_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(l3out_logical_node_profile), + module_object=l3out_logical_node_profile, + target_filter={"name": l3out_logical_node_profile}, + ), + subclass_3=dict( + aci_class="l3extLIfP", + aci_rn="lifp-{0}".format(l3out_logical_interface_profile), + module_object=l3out_logical_interface_profile, + target_filter={"name": l3out_logical_interface_profile}, + ), + subclass_4=dict( + aci_class="bfdIfP", + aci_rn="bfdIfP", + module_object="bfdIfP", + target_filter={"name": name}, + ), + child_classes=["bfdRsIfPol"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + class_config = dict( + name=name, + nameAlias=name_alias, + descr=description, + key=key, + type=authentication_type, + ) + + if key_id and key_id not in range(1, 255): + module.fail_json(msg='The "key_id" must be a value between 1 and 255') + else: + class_config["keyId"] = key_id + + if bfd_interface_policy is not None: + child_configs.append(dict(bfdRsIfPol=dict(attributes=dict(tnBfdIfPolName=bfd_interface_policy)))) + + aci.payload( + aci_class="bfdIfP", + class_config=class_config, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="bfdIfP") + + 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_bfd_multihop_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bfd_multihop_interface_profile.py new file mode 100644 index 000000000..f7dd66c3e --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bfd_multihop_interface_profile.py @@ -0,0 +1,366 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Anvitha Jain (@anvjain) <anvjain@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_bfd_multihop_interface_profile +short_description: Manage BFD Multihop Interface profiles (bfd:MhIfP) +description: +- Manage BFD Multihop Interface profile configuration on Cisco ACI fabrics +- Only available in APIC version 5.2 or later and for non-cloud APICs +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 ] + l3out_logical_node_profile: + description: + - Name of an existing L3Out Logical Node profile + type: str + aliases: [ logical_node_profile, logical_node_profile_name ] + l3out_logical_interface_profile: + description: + - Name of an existing L3Out Logical Interface profile + type: str + aliases: [ logical_interface_profile, logical_interface_profile_name ] + name: + description: + - Name of the BFD Multihop Interface Profile object + type: str + aliases: [ bfd_multihop_interface_profile ] + name_alias: + description: + - Name Alias of the BFD Multihop Interface Profile object + type: str + description: + description: + - Description of the BFD Multihop Interface Profile object + type: str + aliases: [ descr ] + authentication_type: + description: + - Authentication Type of the BFD Multihop Interface Profile object + - APIC sets the default value to none. + type: str + choices: [ none, sha1 ] + key: + description: + - Authentication Key of the BFD Multihop Interface Profile object + type: str + key_id: + description: + - Authentication Key ID of the BFD Multihop Interface Profile object + - APIC sets the default value to 3 + - Allowed range is 1-255 + type: int + bfd_multihop_interface_policy: + description: + - The name of the BFD Multihop Interface policy + type: str + aliases: [ multihop_interface_policy, multihop_interface_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 + +notes: +- The C(tenant), c(l3out), C(l3out_logical_node_profile) and C(l3out_logical_interface_profile) 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(bfd:MhIfP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- module: cisco.aci.aci_interface_policy_bfd_multihop +author: +- Anvitha Jain (@anvjain) +""" + +EXAMPLES = r""" +- name: Add a new L3Out BFD Multihop Interface Profile + cisco.aci.aci_l3out_bfd_multihop_interface_profile: + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + l3out: ansible_l3out + l3out_logical_node_profile: ansible_node_profile + l3out_logical_interface_profile: ansible_interface_profile + state: present + delegate_to: localhost + +- name: Query a new L3Out BFD Multihop Interface Profile + cisco.aci.aci_l3out_bfd_multihop_interface_profile: + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + l3out: ansible_l3out + l3out_logical_node_profile: ansible_node_profile + l3out_logical_interface_profile: ansible_interface_profile + state: query + delegate_to: localhost + +- name: Query all L3Out BFD Multihop Interface Profile + cisco.aci.aci_l3out_bfd_multihop_interface_profile: + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete L3Out BFD Multihop Interface Profile + cisco.aci.aci_l3out_bfd_multihop_interface_profile: + username: admin + password: SomeSecretPassword + tenant: ansible_tenant + l3out: ansible_l3out + l3out_logical_node_profile: ansible_node_profile + l3out_logical_interface_profile: ansible_interface_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 "/></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"]), + l3out=dict(type="str", aliases=["l3out_name"]), + l3out_logical_node_profile=dict(type="str", aliases=["logical_node_profile_name", "logical_node_profile"]), + l3out_logical_interface_profile=dict(type="str", aliases=["logical_interface_profile_name", "logical_interface_profile"]), + name=dict(type="str", aliases=["bfd_multihop_interface_profile"]), + name_alias=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + authentication_type=dict(type="str", choices=["none", "sha1"]), + key=dict(type="str", no_log=True), + key_id=dict(type="int"), + bfd_multihop_interface_policy=dict(type="str", aliases=["multihop_interface_policy", "multihop_interface_policy_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", ["tenant", "l3out", "l3out_logical_node_profile", "l3out_logical_interface_profile"]], + ["state", "present", ["tenant", "l3out", "l3out_logical_node_profile", "l3out_logical_interface_profile", "bfd_multihop_interface_policy"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + l3out_logical_node_profile = module.params.get("l3out_logical_node_profile") + l3out_logical_interface_profile = module.params.get("l3out_logical_interface_profile") + name = module.params.get("name") + name_alias = module.params.get("name_alias") + description = module.params.get("description") + authentication_type = module.params.get("authentication_type") + key = module.params.get("key") + key_id = module.params.get("key_id") + bfd_multihop_interface_policy = module.params.get("bfd_multihop_interface_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(l3out_logical_node_profile), + module_object=l3out_logical_node_profile, + target_filter={"name": l3out_logical_node_profile}, + ), + subclass_3=dict( + aci_class="l3extLIfP", + aci_rn="lifp-{0}".format(l3out_logical_interface_profile), + module_object=l3out_logical_interface_profile, + target_filter={"name": l3out_logical_interface_profile}, + ), + subclass_4=dict( + aci_class="bfdMhIfP", + aci_rn="bfdMhIfP", + module_object="bfdMhIfP", + target_filter={"name": name}, + ), + child_classes=["bfdRsMhIfPol"], + ) + + aci.get_existing() + aci.stdout = str(aci.get_existing()) + if state == "present": + child_configs = [] + class_config = dict( + name=name, + nameAlias=name_alias, + descr=description, + type=authentication_type, + key=key, + ) + + if key_id and key_id not in range(1, 255): + module.fail_json(msg='The "key_id" must be a value between 1 and 255') + else: + class_config["keyId"] = key_id + + if bfd_multihop_interface_policy is not None: + child_configs.append(dict(bfdRsMhIfPol=dict(attributes=dict(tnBfdMhIfPolName=bfd_multihop_interface_policy)))) + + aci.payload( + aci_class="bfdMhIfP", + class_config=class_config, + child_configs=child_configs, + ) + + aci.get_diff(aci_class="bfdMhIfP") + + 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 index 4719c42ca..79776fdb6 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_bgp_peer.py @@ -16,7 +16,7 @@ ANSIBLE_METADATA = { DOCUMENTATION = r""" --- module: aci_l3out_bgp_peer -short_description: Manage Layer 3 Outside (L3Out) BGP Peers (bgp:PeerP) +short_description: Manage Layer 3 Outside (L3Out) BGP Peers (bgp:PeerP and bgp:InfraPeerP) description: - Manage L3Out BGP Peers on Cisco ACI fabrics. options: @@ -68,7 +68,7 @@ options: - BGP Controls type: list elements: str - choices: [ send-com, send-ext-com, allow-self-as, as-override, dis-peer-as-check, nh-self ] + choices: [ send-com, send-ext-com, allow-self-as, as-override, dis-peer-as-check, nh-self, send-domain-path ] peer_controls: description: - Peer Controls @@ -143,6 +143,36 @@ options: - The APIC defaults to 0 when unset during creation. type: int aliases: [ local_as_num ] + bgp_password: + description: + - Password for the BGP Peer. + type: str + description: + description: + - Description for the BGP Peer. + type: str + aliases: [ descr ] + transport_data_plane: + description: + - Transport Data Plane type. + type: str + choices: [ mpls, sr_mpls ] + bgp_peer_prefix_policy: + description: + - BGP Peer Prefix Policy. + - BGP Peer Prefix Policy is only allowed to be configured when I(bgp_infra_peer=true). + type: str + aliases: [ bgp_peer_prefix_policy_name ] + peer_type: + description: + - BGP Peer type. + type: str + choices: [ sr_mpls ] + bgp_infra_peer: + description: + - BGP Infra peer (bgp:InfraPeerP). + type: bool + aliases: [ infra ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -158,10 +188,11 @@ 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) + description: More information about the internal APIC classes B(bgp:peerP) and B(bgp:InfraPeerP) link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -215,6 +246,28 @@ EXAMPLES = r""" state: present delegate_to: localhost +- name: Create Infra BGP Peer with password + aci_l3out_bgp_peer: &infra_bgp_peer + host: apic + username: admin + password: SomeSecretPassword + tenant: infra + l3out: ansible_infra_l3out + node_profile: ansible_infra_l3out_node_profile + ttl: 2 + bgp_infra_peer: true + bgp_password: ansible_test_password + peer_ip: 192.168.50.2 + remote_asn: 65450 + local_as_number: 65460 + peer_type: sr_mpls + bgp_controls: + - send-domain-path + transport_data_plane: sr_mpls + bgp_peer_prefix_policy: ansible_peer_prefix_profile + state: present + delegate_to: localhost + - name: Shutdown a BGP peer cisco.aci.aci_l3out_bgp_peer: host: apic @@ -266,6 +319,7 @@ EXAMPLES = r""" direction: "export" l3out: "anstest_l3out" state: present + delegate_to: localhost - name: Query a BGP peer cisco.aci.aci_l3out_bgp_peer: @@ -293,6 +347,15 @@ EXAMPLES = r""" delegate_to: localhost register: query_all +- name: Query all BGP infra peer + cisco.aci.aci_l3out_bgp_peer: + host: apic + username: admin + password: SomeSecretPassword + bgp_infra_peer: true + state: query + delegate_to: localhost + register: query_all """ RETURN = r""" @@ -416,6 +479,7 @@ def main(): argument_spec.update( tenant=dict(type="str", aliases=["tenant_name"]), l3out=dict(type="str", aliases=["l3out_name"]), + description=dict(type="str", aliases=["descr"]), 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"]), @@ -434,6 +498,7 @@ def main(): "as-override", "dis-peer-as-check", "nh-self", + "send-domain-path", ], ), peer_controls=dict(type="list", elements="str", choices=["bfd", "dis-conn-check"]), @@ -454,6 +519,11 @@ def main(): ), local_as_number_config=dict(type="str", choices=["dual-as", "no-prepend", "none", "replace-as"], aliases=["local_as_num_config"]), local_as_number=dict(type="int", aliases=["local_as_num"]), + bgp_password=dict(type="str", no_log=True), + transport_data_plane=dict(type="str", choices=["mpls", "sr_mpls"]), + bgp_peer_prefix_policy=dict(type="str", aliases=["bgp_peer_prefix_policy_name"]), + peer_type=dict(type="str", choices=["sr_mpls"]), + bgp_infra_peer=dict(type="bool", aliases=["infra"]), ) module = AnsibleModule( @@ -468,6 +538,7 @@ def main(): tenant = module.params.get("tenant") l3out = module.params.get("l3out") + description = module.params.get("description") node_profile = module.params.get("node_profile") interface_profile = module.params.get("interface_profile") state = module.params.get("state") @@ -487,6 +558,11 @@ def main(): route_control_profiles = module.params.get("route_control_profiles") local_as_number_config = module.params.get("local_as_number_config") local_as_number = module.params.get("local_as_number") + bgp_password = module.params.get("bgp_password") + transport_data_plane = module.params.get("transport_data_plane") + peer_type = module.params.get("peer_type") + bgp_infra_peer = module.params.get("bgp_infra_peer") + bgp_peer_prefix_policy = module.params.get("bgp_peer_prefix_policy") aci = ACIModule(module) if node_id: @@ -499,24 +575,23 @@ def main(): child_configs = [] child_classes = ["bgpRsPeerPfxPol", "bgpAsP", "bgpLocalAsnP"] + aci_class = "bgpInfraPeerP" if bgp_infra_peer else "bgpPeerP" - if remote_asn: - child_configs.append( - dict( - bgpAsP=dict( - attributes=dict(asn=remote_asn), - ), - ) - ) + if remote_asn is not None: + bgp_as_p = dict(bgpAsP=dict(attributes=dict(asn=remote_asn))) + if remote_asn == 0: + bgp_as_p["bgpAsP"]["attributes"]["status"] = "deleted" + child_configs.append(bgp_as_p) - if local_as_number_config or local_as_number: - child_configs.append( - dict( - bgpLocalAsnP=dict( - attributes=dict(asnPropagate=local_as_number_config, localAsn=local_as_number), - ), - ) - ) + if local_as_number_config is not None or local_as_number is not None: + bgp_local_asn_p = dict(bgpLocalAsnP=dict(attributes=dict(asnPropagate=local_as_number_config, localAsn=local_as_number))) + if local_as_number == 0: + bgp_local_asn_p["bgpLocalAsnP"]["attributes"]["status"] = "deleted" + child_configs.append(bgp_local_asn_p) + + # BGP Peer Prefix Policy is ony configurable on Infra BGP Peer Profile + if bgp_peer_prefix_policy is not None: + child_configs.append(dict(bgpRsPeerPfxPol=dict(attributes=dict(tnBgpPeerPfxPolName=bgp_peer_prefix_policy)))) if route_control_profiles: child_classes.append("bgpRsPeerToProfile") @@ -541,8 +616,8 @@ def main(): ) bgp_peer_profile_dict = dict( - aci_class="bgpPeerP", - aci_rn="peerP-[{0}]".format(peer_ip), + aci_class=aci_class, + aci_rn="infraPeerP-[{0}]".format(peer_ip) if bgp_infra_peer else "peerP-{0}".format(peer_ip), module_object=peer_ip, target_filter={"addr": peer_ip}, ) @@ -594,32 +669,46 @@ def main(): aci.get_existing() if state == "present": - ctrl, peerCtrl, addrTCtrl, privateASctrl = None, None, None, None + ctrl, ctrl_ext, peerCtrl, addrTCtrl, privateASctrl = None, None, None, None, None if bgp_controls: + if transport_data_plane == "mpls": + bgp_controls.append("segment-routing-disable") + + if "send-domain-path" in bgp_controls: + ctrl_ext = "send-domain-path" + bgp_controls.remove("send-domain-path") + 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, + + class_config = dict( + descr=description, + addr=peer_ip, + ctrl=ctrl, + ctrlExt=ctrl_ext, + peerCtrl=peerCtrl, + addrTCtrl=addrTCtrl, + privateASctrl=privateASctrl, + ttl=ttl, + weight=weight, + adminSt=admin_state, + allowedSelfAsCnt=allow_self_as_count, + peerT=peer_type.replace("_", "-") if peer_type else None, ) - aci.get_diff(aci_class="bgpPeerP") + # Only add bgp_password if it is set to handle changed status properly because password is not part of existing config + if bgp_password: + class_config["password"] = bgp_password + + aci.payload(aci_class=aci_class, class_config=class_config, child_configs=child_configs) + + aci.get_diff(aci_class=aci_class) aci.post_config() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_dhcp_relay_label.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_dhcp_relay_label.py new file mode 100644 index 000000000..3b9885c23 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_dhcp_relay_label.py @@ -0,0 +1,354 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_dhcp_relay_label +short_description: Manage Layer 3 Outside (L3Out) DHCP Relay Label (dhcp:Lbl) +description: +- Manage DHCP Relay Labels for L3Out Logical Interface Profiles on Cisco ACI fabrics. +- A DHCP Relay Label contains the name of an existing DHCP Relay Policy for the label, + the scope, and a DHCP Option Policy. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - The name of an existing node profile. + type: str + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - The name of an existing interface profile. + type: str + aliases: [ interface_profile_name, logical_interface ] + dhcp_relay_label: + description: + - The name/label of an existing DHCP Relay Policy. + type: str + aliases: [ name, relay_policy ] + scope: + description: + - The scope is the owner of the relay server. + - The APIC defaults to C(infra) when unset during creation. + type: str + choices: [ infra, tenant ] + aliases: [ owner ] + dhcp_option_policy: + description: + - The name of an existing DHCP Option Policy to be associated with the DCHP Relay Policy. + - The DHCP option policy supplies DHCP clients with configuration parameters + such as domain, nameserver, and subnet router addresses. + - Passing an empty string will delete the current linked DHCP Option Policy. + However, this will associate the DHCP Relay Label to the default DHCP Option Policy + from the common Tenant. + type: str + aliases: [ dhcp_option_policy_name ] + description: + description: + - The description of the DHCP Relay Label. + 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(tenant), C(l3out), C(node_profile), C(interface_profile) and C(relay_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), M(cisco.aci.aci_l3out_logical_interface_profile) + and M(cisco.aci.aci_dhcp_relay) can be used for this. +- If C(dhcp_option_policy) is used, it must exist before using this module in your playbook. + The M(cisco.aci.aci_dhcp_option_policy) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- module: cisco.aci.aci_dhcp_relay +- module: cisco.aci.aci_dhcp_option_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new L3Out DHCP Relay Label + cisco.aci.aci_l3out_eigrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + dhcp_relay_label: my_dhcp_relay_label + scope: tenant + dhcp_option_policy: my_dhcp_option_policy + state: present + delegate_to: localhost + +- name: Delete an L3Out DHCP Relay Label + cisco.aci.aci_l3out_eigrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + dhcp_relay_label: my_dhcp_relay_label + state: absent + delegate_to: localhost + +- name: Query an L3Out DHCP Relay Label + cisco.aci.aci_l3out_eigrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + dhcp_relay_label: my_dhcp_relay_label + 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"]), + dhcp_relay_label=dict(type="str", aliases=["name", "relay_policy"]), + scope=dict(type="str", choices=["infra", "tenant"], aliases=["owner"]), + dhcp_option_policy=dict(type="str", aliases=["dhcp_option_policy_name"]), + 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", ["tenant", "l3out", "node_profile", "interface_profile", "dhcp_relay_label"]], + ["state", "present", ["tenant", "l3out", "node_profile", "interface_profile", "dhcp_relay_label"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + dhcp_relay_label = module.params.get("dhcp_relay_label") + scope = module.params.get("scope") + dhcp_option_policy = module.params.get("dhcp_option_policy") + description = module.params.get("description") + state = module.params.get("state") + + aci = ACIModule(module) + + child_classes = ["dhcpRsDhcpOptionPol"] + + 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="dhcpLbl", + aci_rn="dhcplbl-[{0}]".format(dhcp_relay_label), + module_object=dhcp_relay_label, + target_filter={"name": dhcp_relay_label}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [dict(dhcpRsDhcpOptionPol=dict(attributes=dict(tnDhcpOptionPolName=dhcp_option_policy)))] + + aci.payload( + aci_class="dhcpLbl", + class_config=dict( + descr=description, + name=dhcp_relay_label, + owner=scope, + ), + child_configs=child_configs, + ) + + 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_l3out_eigrp_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_eigrp_interface_profile.py new file mode 100644 index 000000000..d1359e500 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_eigrp_interface_profile.py @@ -0,0 +1,392 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_l3out_eigrp_interface_profile +short_description: Manage Layer 3 Outside (L3Out) EIGRP interface profile (eigrp:IfP) +description: +- Manage L3Out logical interface profile EIGRP policies on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of an existing L3Out. + type: str + aliases: [ l3out_name ] + node_profile: + description: + - The name of the node profile. + type: str + aliases: [ node_profile_name, logical_node ] + interface_profile: + description: + - The name of an existing interface profile. + type: str + aliases: [ interface_profile_name, logical_interface ] + eigrp_policy: + description: + - The name of an existing EIGRP interface policy. + type: str + aliases: [ name, eigrp_policy_name ] + eigrp_keychain_policy: + description: + - The name of an existing EIGRP keychain policy. + - Pass an empty string to disable Authentification. + type: str + aliases: [ keychain_policy, keychain_policy_name ] + description: + description: + - The description of the EIGRP 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant), C(l3out), C(node_profile), C(interface_profile) and C(eigrp_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), M(cisco.aci.aci_l3out_logical_interface_profile) + and M(cisco.aci.aci_interface_policy_eigrp) can be used for this. +- if C(eigrp_keychain_policy) is used, it must exist before using this module in your playbook. + The M(cisco.aci.aci_keychain_policy) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- module: cisco.aci.aci_interface_policy_eigrp +- module: cisco.aci.aci_keychain_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC classes + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new interface profile EIGRP policy + cisco.aci.aci_l3out_eigrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + eigrp_policy: my_eigrp_interface_policy + state: present + delegate_to: localhost + +- name: Add a new interface profile EIGRP policy with authentication + cisco.aci.aci_l3out_eigrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + eigrp_policy: my_eigrp_interface_policy + eigrp_keychain_policy: my_keychain_policy + state: present + delegate_to: localhost + +- name: Disable authentification from an interface profile EIGRP policy + cisco.aci.aci_l3out_eigrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + eigrp_policy: my_eigrp_interface_policy + eigrp_keychain_policy: "" + state: present + delegate_to: localhost + +- name: Delete an interface profile EIGRP policy + cisco.aci.aci_l3out_eigrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + eigrp_policy: my_eigrp_interface_policy + state: absent + delegate_to: localhost + +- name: Query an interface profile EIGRP policy + cisco.aci.aci_l3out_eigrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + eigrp_policy: my_eigrp_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"]), + eigrp_policy=dict(type="str", aliases=["name", "eigrp_policy_name"]), + eigrp_keychain_policy=dict(type="str", aliases=["keychain_policy", "keychain_policy_name"], no_log=False), + 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", ["tenant", "l3out", "node_profile", "interface_profile"]], + ["state", "present", ["tenant", "l3out", "node_profile", "interface_profile", "eigrp_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") + eigrp_policy = module.params.get("eigrp_policy") + eigrp_keychain_policy = module.params.get("eigrp_keychain_policy") + description = module.params.get("description") + state = module.params.get("state") + + aci = ACIModule(module) + + child_classes = ["eigrpRsIfPol", "eigrpAuthIfP"] + + 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="eigrpIfP", + aci_rn="eigrpIfP", + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [dict(eigrpRsIfPol=dict(attributes=dict(tnEigrpIfPolName=eigrp_policy)))] + + if eigrp_keychain_policy is not None: + if eigrp_keychain_policy == "" and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("eigrpIfP", {}).get("children", {}): + if child.get("eigrpAuthIfP"): + child_configs.append( + dict( + eigrpAuthIfP=dict( + attributes=dict(status="deleted"), + ), + ) + ) + elif eigrp_keychain_policy != "": + child_configs.append( + dict( + eigrpAuthIfP=dict( + attributes=dict(), + children=[ + dict( + eigrpRsKeyChainPol=dict( + attributes=dict( + tnFvKeyChainPolName=eigrp_keychain_policy, + ), + ) + ) + ], + ) + ) + ) + + aci.payload( + aci_class="eigrpIfP", + class_config=dict( + descr=description, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="eigrpIfP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py index acaa7ea9e..3180e6752 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extepg.py @@ -14,7 +14,7 @@ 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) +- Manage External Network Instance Profile (ExtEpg) objects. options: tenant: description: @@ -104,7 +104,7 @@ seealso: - 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). + description: More information about the internal APIC class B(l3extInstP:instP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Rostyslav Davydenko (@rost-d) 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 index ba5df29c5..eb9f6da17 100644 --- 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 @@ -3,6 +3,7 @@ # Copyright: (c) 2020, Sudhakar Shet Kudtarkar (@kudtarkar1) # Copyright: (c) 2020, Shreyas Srish <ssrish@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -14,7 +15,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_l3out_extepg_to_contract -short_description: Bind Contracts to External End Point Groups (EPGs) +short_description: Bind Contracts to External End Point Groups (EPGs) (fv:RsCons, fv:RsProv, fv:RsProtBy, fv:RsConsIf, and fv:RsIntraEpg) description: - Bind Contracts to External End Point Groups (EPGs) on ACI fabrics. options: @@ -26,32 +27,43 @@ options: description: - Name of the l3out. type: str - aliases: ['l3out_name'] + aliases: [ l3out_name ] extepg: description: - Name of the external end point group. type: str - aliases: ['extepg_name', 'external_epg'] + aliases: [ extepg_name, external_epg] contract: description: - - Name of the contract. + - The name of the contract or contract interface. type: str + aliases: [ contract_name, contract_interface ] contract_type: description: - - The type of contract. + - Determines the type of the Contract. type: str required: true - choices: ['consumer', 'provider'] + choices: [ consumer, provider, taboo, interface, intra_epg ] priority: description: - - This has four levels of priority. + - QoS class. + - The APIC defaults to C(unspecified) when unset during creation. type: str - choices: ['level1', 'level2', 'level3', 'unspecified'] + choices: [ level1, level2, level3, level4, level5, level6, unspecified ] provider_match: description: - - This is configurable for provided contracts. + - 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 - choices: ['all', 'at_least_one', 'at_most_one', 'none'] state: description: - Use C(present) or C(absent) for adding or removing. @@ -64,15 +76,16 @@ extends_documentation_fragment: - 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. +- The C(tenant), C(l3out), C(extepg), and C(Contract) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_extepg), and M(cisco.aci.aci_contract) 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). + description: More information about the internal APIC class B(fv:RsCons), B(fv:RsProv), B(fv:RsProtBy, B(fv:RsConsIf, and B(fv:RsIntraEpg). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Sudhakar Shet Kudtarkar (@kudtarkar1) - Shreyas Srish (@shrsr) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -234,38 +247,23 @@ RETURN = r""" 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", -) +from ansible_collections.cisco.aci.plugins.module_utils.constants import ACI_CLASS_MAPPING, CONTRACT_LABEL_MAPPING, PROVIDER_MATCH_MAPPING, SUBJ_LABEL_MAPPING 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"]), + contract_type=dict(type="str", required=True, choices=["consumer", "provider", "taboo", "interface", "intra_epg"]), l3out=dict(type="str", aliases=["l3out_name"]), - contract=dict(type="str"), - priority=dict(type="str", choices=["level1", "level2", "level3", "unspecified"]), + contract=dict(type="str", aliases=["contract_name", "contract_interface"]), # 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"), extepg=dict(type="str", aliases=["extepg_name", "external_epg"]), + contract_label=dict(type="str"), + subject_label=dict(type="str"), ) module = AnsibleModule( argument_spec=argument_spec, @@ -286,13 +284,25 @@ def main(): provider_match = PROVIDER_MATCH_MAPPING.get(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.get(contract_type)["class"] - aci_rn = ACI_CLASS_MAPPING.get(contract_type)["rn"] + aci_class = ACI_CLASS_MAPPING[contract_type]["class"] + aci_rn = ACI_CLASS_MAPPING[contract_type]["rn"] + aci_name = ACI_CLASS_MAPPING[contract_type]["name"] + child_classes = [] - if contract_type == "consumer" and provider_match is not None: + if contract_type != "provider" and provider_match is not None: module.fail_json(msg="the 'provider_match' is only configurable for Provided Contracts") + if contract_type in ["taboo", "interface", "intra_epg"] and (contract_label is not None or subject_label is not None): + module.fail_json(msg="the 'contract_label' and 'subject_label' are not configurable for {0} contracts".format(contract_type)) + + if contract_type not in ["taboo", "interface", "intra_epg"]: + contract_label_class = CONTRACT_LABEL_MAPPING.get(contract_type) + subject_label_class = SUBJ_LABEL_MAPPING.get(contract_type) + child_classes = [subject_label_class, contract_label_class] + aci = ACIModule(module) aci.construct_url( root_class=dict( @@ -317,20 +327,23 @@ def main(): aci_class=aci_class, aci_rn="{0}{1}".format(aci_rn, contract), module_object=contract, - target_filter={"tnVzBrCPName": contract}, + target_filter={aci_name: contract}, ), + child_classes=child_classes, ) aci.get_existing() if state == "present": + child_configs = [] + if contract_label is not None: + child_configs.append({contract_label_class: {"attributes": {"name": contract_label}}}) + if subject_label is not None: + 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, - ), + class_config={"matchT": provider_match, "prio": priority, aci_name: contract}, + child_configs=child_configs, ) aci.get_diff(aci_class=aci_class) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extsubnet.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extsubnet.py index 6ecb3b27c..9d94ee44f 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extsubnet.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_extsubnet.py @@ -12,9 +12,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_l3out_extsubnet -short_description: Manage External Subnet objects (l3extSubnet:extsubnet) +short_description: Manage External Subnet objects (l3ext:Subnet) description: -- Manage External Subnet objects (l3extSubnet:extsubnet) +- Manage External Subnet objects. options: tenant: description: @@ -99,7 +99,7 @@ seealso: - 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). + description: More information about the internal APIC class B(l3ext:Subnet). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Rostyslav Davydenko (@rost-d) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi.py new file mode 100644 index 000000000..b0e3ca7e3 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi.py @@ -0,0 +1,475 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, 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_floating_svi +short_description: Manage Layer 3 Outside (L3Out) interfaces (l3ext:VirtualLIfP) +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 ID to build the interface on. + type: str + node_id: + description: + - Node ID to build the interface on for Port-channels and single ports. + type: str + encap: + description: + - Encapsulation on the interface (e.g. "vlan-500") + type: str + encap_scope: + description: + - Encapsulation scope. + choices: [ vrf, local ] + type: str + address: + description: + - IP address. + type: str + aliases: [ addr, ip_address ] + mac_address: + description: + - The MAC address option of the interface. + type: str + link_local_address: + description: + - The link local address option of the interface. + type: str + mtu: + description: + - Interface MTU. + type: str + ipv6_dad: + description: + - IPv6 Duplicate Address Detection (DAD) feature. + type: str + choices: [ enabled, disabled] + mode: + description: + - The mode option for ext-svi interface. + type: str + choices: [ regular, native, untagged ] + 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 ] + external_bridge_group_profile: + description: + - The external bridge group profile. + - Pass "" as the value to remove an existing external bridge group profile (See Examples). + - This is only supported in APIC v5.0 and above. + 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 + auto_state: + description: + - SVI auto state. + type: str + choices: [ enabled, disabled ] +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(tenant), C(l3out), C(logical_node_profile) and C(logical_interface_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), M(cisco.aci.aci_l3out_logical_interface_profile) \ + modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(l3ext:VirtualLIfP) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Create a Floating SVI + cisco.aci.aci_l3out_floating_svi: + 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 + encap: vlan-1 + address: 23.45.67.90/24 + external_bridge_group_profile: bridge1 + state: present + delegate_to: localhost + +- name: Remove an external bridge group profile + cisco.aci.aci_l3out_floating_svi: + 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 + encap: vlan-1 + address: 23.45.67.90/24 + external_bridge_group_profile: "" + state: present + delegate_to: localhost + +- name: Remove a Floating SVI + cisco.aci.aci_l3out_floating_svi: + 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 + encap: vlan-1 + state: absent + delegate_to: localhost + +- name: Query a Floating SVI + cisco.aci.aci_l3out_floating_svi: + 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 + encap: vlan-1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all the Floating SVIs under an interface profile + cisco.aci.aci_l3out_floating_svi: + 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_results +""" + +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 + + +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"), + address=dict(type="str", aliases=["addr", "ip_address"]), + link_local_address=dict(type="str"), + mac_address=dict(type="str"), + mtu=dict(type="str"), + ipv6_dad=dict(type="str", choices=["enabled", "disabled"]), + mode=dict(type="str", choices=["regular", "native", "untagged"]), + encap=dict(type="str"), + encap_scope=dict(type="str", choices=["vrf", "local"]), + auto_state=dict(type="str", choices=["enabled", "disabled"]), + external_bridge_group_profile=dict(type="str"), + dscp=aci_contract_dscp_spec(direction="dscp"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["pod_id", "node_id", "encap", "address"]], + ["state", "absent", ["pod_id", "node_id", "encap"]], + ], + ) + + 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") + address = module.params.get("address") + mtu = module.params.get("mtu") + ipv6_dad = module.params.get("ipv6_dad") + link_local_address = module.params.get("link_local_address") + mac_address = module.params.get("mac_address") + mode = module.params.get("mode") + encap = module.params.get("encap") + encap_scope = "ctx" if module.params.get("encap_scope") == "vrf" else module.params.get("encap_scope") + auto_state = module.params.get("auto_state") + external_bridge_group_profile = module.params.get("external_bridge_group_profile") + + aci = ACIModule(module) + + node_dn = None + if pod_id and node_id: + node_dn = "topology/pod-{0}/node-{1}".format(pod_id, node_id) + + child_classes = [] + if external_bridge_group_profile is not None: + child_classes.append("l3extBdProfileCont") + + 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="l3extVirtualLIfP", aci_rn="vlifp-[{0}]-[{1}]".format(node_dn, encap), module_object=node_dn, target_filter={"nodeDn": node_dn} + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if external_bridge_group_profile is not None: + if external_bridge_group_profile == "" and isinstance(aci.existing, list) and len(aci.existing) > 0: + if aci.existing[0].get("l3extVirtualLIfP", {}).get("children") is not None: + child_configs.append( + dict( + l3extBdProfileCont=dict( + attributes=dict(status="deleted"), + ), + ) + ) + elif external_bridge_group_profile != "": + child_configs.append( + dict( + l3extBdProfileCont=dict( + attributes=dict(), + children=[ + dict( + l3extRsBdProfile=dict( + attributes=dict( + tDn="uni/tn-{0}/bdprofile-{1}".format(tenant, external_bridge_group_profile), + ), + ) + ) + ], + ) + ) + ) + + aci.payload( + aci_class="l3extVirtualLIfP", + class_config=dict( + addr=address, + ipv6Dad=ipv6_dad, + mtu=mtu, + ifInstT="ext-svi", + mode=mode, + encap=encap, + encapScope=encap_scope, + autostate=auto_state, + llAddr=link_local_address, + mac=mac_address, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="l3extVirtualLIfP") + + 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_floating_svi_path.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi_path.py new file mode 100644 index 000000000..3d7c45a33 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi_path.py @@ -0,0 +1,491 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, 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_floating_svi_path +short_description: Manage Layer 3 Outside (L3Out) Floating SVI Path Attributes (l3ext:RsDynPathAtt) +description: +- Manages L3Out Floating SVI path attributes 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 + required: true + node_id: + description: + - Node to build the interface on for Port-channels and single ports. + type: str + required: true + encap: + description: + - Encapsulation on the interface (e.g. "vlan-500") + type: str + required: true + domain: + description: + - This option allows virtual machines to send frames with a mac address. + type: str + domain_type: + description: + - The domain type of the path. + - The physical domain type is only supported in APIC v5.0 and above. + type: str + choices: [ physical, vmware ] + access_encap: + description: + - The port encapsulation option. + type: str + floating_ip: + description: + - The floating IP address. + type: str + aliases: [ floating_address ] + forged_transmit: + description: + - This option allows virtual machines to send frames with a mac address. + - This is only supported in APIC v5.0 and above. + type: str + choices: [ enabled, disabled ] + mac_change: + description: + - The status of the mac address change support for port groups in an external VMM controller. + - This is only supported in APIC v5.0 and above. + type: str + choices: [ enabled, disabled ] + promiscuous_mode: + description: + - The status of promiscuous mode for port groups in an external VMM controller. + - This is only supported in APIC v5.0 and above. + type: str + choices: [ enabled, disabled ] + enhanced_lag_policy: + description: + - The enhanced lag policy of the path. + - Pass "" as the value to remove an existing enhanced lag policy (See Examples). + 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), C(l3out), C(logical_node_profile), C(logical_interface_profile) and C(floating_svi) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), M(cisco.aci.aci_l3out_logical_interface_profile) and + M(cisco.aci.aci_l3out_floating_svi) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- module: cisco.aci.aci_l3out_floating_svi +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(l3ext:RsDynPathAtt)) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Create a Floating SVI path attribute + cisco.aci.aci_l3out_floating_svi_path: + 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 + encap: vlan-1 + floating_ip: 23.45.67.90/24 + domain_type: virtual + domain: anstest + enhanced_lag_policy: enhanced + state: present + delegate_to: localhost + +- name: Remove enhanced lag policy from the path + cisco.aci.aci_l3out_floating_svi_path: + 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 + encap: vlan-1 + floating_ip: 23.45.67.90/24 + domain_type: virtual + domain: anstest + enhanced_lag_policy: "" + state: present + delegate_to: localhost + +- name: Remove a Floating SVI path attribute + cisco.aci.aci_l3out_floating_svi_path: + 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 + encap: vlan-1 + domain_type: virtual + domain: anstest + state: absent + delegate_to: localhost + +- name: Query a Floating SVI path attribute + cisco.aci.aci_l3out_floating_svi_path: + 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 + encap: vlan-1 + domain_type: virtual + domain: anstest + state: query + delegate_to: localhost + register: query_result + +- name: Query all the Floating SVI path attributes + cisco.aci.aci_l3out_floating_svi_path: + 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 + encap: vlan-1 + state: query + delegate_to: localhost + register: query_results +""" + +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", required=True), + node_id=dict(type="str", required=True), + encap=dict(type="str", required=True), + floating_ip=dict(type="str", aliases=["floating_address"]), + forged_transmit=dict(type="str", choices=["enabled", "disabled"]), + mac_change=dict(type="str", choices=["enabled", "disabled"]), + promiscuous_mode=dict(type="str", choices=["enabled", "disabled"]), + domain_type=dict(type="str", choices=["physical", "vmware"]), + domain=dict(type="str"), + enhanced_lag_policy=dict(type="str"), + access_encap=dict(type="str"), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "present", ["domain_type", "domain", "floating_ip"]], + ["state", "absent", ["domain_type", "domain"]], + ], + ) + + 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") + floating_ip = module.params.get("floating_ip") + encap = module.params.get("encap") + forged_transmit = module.params.get("forged_transmit").capitalize() if module.params.get("forged_transmit") else None + mac_change = module.params.get("mac_change").capitalize() if module.params.get("mac_change") else None + promiscuous_mode = module.params.get("promiscuous_mode").capitalize() if module.params.get("promiscuous_mode") else None + domain_type = module.params.get("domain_type") + domain = module.params.get("domain") + enhanced_lag_policy = module.params.get("enhanced_lag_policy") + access_encap = module.params.get("access_encap") + + aci = ACIModule(module) + + node_dn = "topology/pod-{0}/node-{1}".format(pod_id, node_id) + + tDn = None + if domain_type == "physical": + tDn = "uni/phys-{0}".format(domain) + elif domain_type == "vmware": + tDn = "uni/vmmp-VMware/dom-{0}".format(domain) + + 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="l3extVirtualLIfP", aci_rn="vlifp-[{0}]-[{1}]".format(node_dn, encap), module_object=node_dn, target_filter={"nodeDn": node_dn} + ), + subclass_5=dict( + aci_class="l3extRsDynPathAtt", + aci_rn="rsdynPathAtt-[{0}]".format(tDn), + module_object=tDn, + target_filter={"tDn": tDn}, + ), + child_classes=["l3extVirtualLIfPLagPolAtt"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if enhanced_lag_policy is not None and domain_type == "vmware": + existing_enhanced_lag_policy = "" + if isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("l3extRsDynPathAtt", {}).get("children", {}): + if child.get("l3extVirtualLIfPLagPolAtt"): + try: + existing_enhanced_lag_policy = child["l3extVirtualLIfPLagPolAtt"]["children"][0]["l3extRsVSwitchEnhancedLagPol"]["attributes"][ + "tDn" + ].split("enlacplagp-")[1] + except (AttributeError, IndexError, KeyError): + existing_enhanced_lag_policy = "" + + if enhanced_lag_policy == "": + child_configs.append( + dict( + l3extVirtualLIfPLagPolAtt=dict( + attributes=dict(status="deleted"), + ), + ) + ) + + if enhanced_lag_policy != "": + child = [ + dict( + l3extRsVSwitchEnhancedLagPol=dict( + attributes=dict(tDn="{0}/vswitchpolcont/enlacplagp-{1}".format(tDn, enhanced_lag_policy)), + ) + ), + ] + if enhanced_lag_policy != existing_enhanced_lag_policy and existing_enhanced_lag_policy != "": + child.append( + dict( + l3extRsVSwitchEnhancedLagPol=dict( + attributes=dict(status="deleted", tDn="{0}/vswitchpolcont/enlacplagp-{1}".format(tDn, existing_enhanced_lag_policy)), + ) + ) + ) + child_configs.append(dict(l3extVirtualLIfPLagPolAtt=dict(attributes=dict(), children=child))) + + aci.payload( + aci_class="l3extRsDynPathAtt", + class_config=dict( + floatingAddr=floating_ip, + forgedTransmit=forged_transmit, + macChange=mac_change, + promMode=promiscuous_mode, + encap=access_encap, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="l3extRsDynPathAtt") + + 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_floating_svi_path_secondary_ip.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi_path_secondary_ip.py new file mode 100644 index 000000000..b49c18169 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi_path_secondary_ip.py @@ -0,0 +1,398 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, 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_floating_svi_path_secondary_ip +short_description: Manages Layer 3 Outside (L3Out) Floating SVI Path Attribute's Secondary IP addresses (l3ext:Ip) +description: +- Manages L3Out Floating SVI path attribute's secondary IP addresses 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 + required: true + node_id: + description: + - Node to build the interface on for Port-channels and single ports. + type: str + required: true + encap: + description: + - Encapsulation on the interface (e.g. "vlan-500") + type: str + required: true + domain: + description: + - This option allows virtual machines to send frames with a mac address. + type: str + required: true + domain_type: + description: + - The domain type of the path. + type: str + choices: [ physical, vmware ] + required: true + secondary_ip: + description: + - The secondary floating IP address. + type: str + aliases: [ secondary_floating_address ] + 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), C(logical_node_profile), C(logical_interface_profile) and C(floating_svi) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), M(cisco.aci.aci_l3out_logical_interface_profile), \ + M(cisco.aci.aci_l3out_floating_svi) and M(cisco.aci.aci_l3out_floating_svi_path) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- module: cisco.aci.aci_l3out_floating_svi +- module: cisco.aci.aci_l3out_floating_svi_path +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(l3ext:Ip) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Create a Floating SVI path attribute secondary IP + cisco.aci.aci_l3out_floating_svi_path_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 + encap: vlan-1 + secondary_ip: 23.45.67.90/24 + domain_type: virtual + domain: anstest + state: present + delegate_to: localhost + +- name: Remove a Floating SVI path attribute secondary IP + cisco.aci.aci_l3out_floating_svi_path_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 + encap: vlan-1 + secondary_ip: 23.45.67.90/24 + domain_type: virtual + domain: anstest + state: absent + delegate_to: localhost + +- name: Query a Floating SVI path attribute secondary IP + cisco.aci.aci_l3out_floating_svi_path_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 + encap: vlan-1 + domain_type: virtual + domain: anstest + secondary_ip: 23.45.67.90/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all the secondary IPs under a Floating SVI path attribute + cisco.aci.aci_l3out_floating_svi_path_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 + encap: vlan-1 + domain_type: virtual + domain: anstest + state: query + delegate_to: localhost + register: query_results +""" + +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"], 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", required=True), + node_id=dict(type="str", required=True), + encap=dict(type="str", required=True), + domain_type=dict(type="str", choices=["physical", "vmware"], required=True), + domain=dict(type="str", required=True), + secondary_ip=dict(type="str", aliases=["secondary_floating_address"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["secondary_ip"]], + ["state", "present", ["secondary_ip"]], + ], + ) + + 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") + encap = module.params.get("encap") + secondary_ip = module.params.get("secondary_ip") + domain_type = module.params.get("domain_type") + domain = module.params.get("domain") + state = module.params.get("state") + + aci = ACIModule(module) + + node_dn = "topology/pod-{0}/node-{1}".format(pod_id, node_id) + + tDn = None + if domain_type == "physical": + tDn = "uni/phys-{0}".format(domain) + else: + tDn = "uni/vmmp-VMware/dom-{0}".format(domain) + + 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="l3extVirtualLIfP", aci_rn="vlifp-[{0}]-[{1}]".format(node_dn, encap), module_object=node_dn, target_filter={"nodeDn": node_dn} + ), + subclass_5=dict( + aci_class="l3extRsDynPathAtt", + aci_rn="rsdynPathAtt-[{0}]".format(tDn), + module_object=tDn, + target_filter={"tDn": tDn}, + ), + subclass_6=dict( + aci_class="l3extIp", + aci_rn="addr-[{0}]".format(secondary_ip), + module_object=secondary_ip, + target_filter={"addr": secondary_ip}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="l3extIp", class_config=dict(addr=secondary_ip, ipv6Dad="disabled")) + + 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_floating_svi_secondary_ip.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi_secondary_ip.py new file mode 100644 index 000000000..ea71376dd --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_floating_svi_secondary_ip.py @@ -0,0 +1,362 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, 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_floating_svi_secondary_ip +short_description: Manages Layer 3 Outside (L3Out) Floating SVI Secondary IP addresses (l3ext:Ip) +description: +- Manages L3Out Floating SVI secondary IP addresses 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 + required: true + node_id: + description: + - Node to build the interface on for Port-channels and single ports. + type: str + required: true + encap: + description: + - Encapsulation on the interface (e.g. "vlan-500") + type: str + required: true + secondary_ip: + description: + - The secondary floating IP address. + type: str + aliases: [ secondary_floating_address ] + 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), C(logical_node_profile), C(logical_interface_profile) and C(floating_svi) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), M(cisco.aci.aci_l3out_logical_interface_profile) and \ + M(cisco.aci.aci_l3out_floating_svi) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- module: cisco.aci.aci_l3out_floating_svi +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(l3ext:Ip) + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Create a Floating SVI secondary IP + cisco.aci.aci_l3out_floating_svi_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 + encap: vlan-1 + secondary_ip: 23.45.67.90/24 + state: present + delegate_to: localhost + +- name: Remove a Floating SVI secondary IP + cisco.aci.aci_l3out_floating_svi_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 + encap: vlan-1 + secondary_ip: 23.45.67.90/24 + state: absent + delegate_to: localhost + +- name: Query a Floating SVI secondary IP + cisco.aci.aci_l3out_floating_svi_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 + encap: vlan-1 + secondary_ip: 23.45.67.90/24 + state: query + delegate_to: localhost + register: query_result + +- name: Query all the secondary IPs under a Floating SVI + cisco.aci.aci_l3out_floating_svi_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 + encap: vlan-1 + state: query + delegate_to: localhost + register: query_results +""" + +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"], 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", required=True), + node_id=dict(type="str", required=True), + encap=dict(type="str", required=True), + secondary_ip=dict(type="str", aliases=["secondary_floating_address"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["secondary_ip"]], + ["state", "present", ["secondary_ip"]], + ], + ) + + 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") + encap = module.params.get("encap") + secondary_ip = module.params.get("secondary_ip") + state = module.params.get("state") + + aci = ACIModule(module) + + node_dn = "topology/pod-{0}/node-{1}".format(pod_id, node_id) + + 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="l3extVirtualLIfP", aci_rn="vlifp-[{0}]-[{1}]".format(node_dn, encap), module_object=node_dn, target_filter={"nodeDn": node_dn} + ), + subclass_5=dict( + aci_class="l3extIp", + aci_rn="addr-[{0}]".format(secondary_ip), + module_object=secondary_ip, + target_filter={"addr": secondary_ip}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="l3extIp", class_config=dict(addr=secondary_ip, ipv6Dad="enabled")) + + 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_hsrp_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_hsrp_group.py new file mode 100644 index 000000000..c6f7316b2 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_hsrp_group.py @@ -0,0 +1,393 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Shreyas Srish (@shrsr) <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_hsrp_group +short_description: Manage HSRP group (hsrp:GroupP) of the HSRP interface profile (hsrp:IfP) +description: +- Manage HSRP group of the HSRP interface profile 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: [ interface_profile_name, logical_interface ] + hsrp_interface_group: + description: + - Name of the HSRP interface group. + type: str + aliases: [ name, hsrp_group ] + group_id: + description: + - The group id of the HSRP interface group. + type: int + ip: + description: + - The virtual IP address of the HSRP interface group. + type: str + mac: + description: + - The MAC address of the HSRP interface group. + type: str + group_name: + description: + - The group name is used to define and manage the specific HSRP interface group, facilitating high availability in the network. + type: str + description: + description: + - The description of the HSRP interface group. + type: str + aliases: [ descr ] + group_type: + description: + - The type of the HSRP interface group. + type: str + choices: [ ipv4, ipv6 ] + ip_obtain_mode: + description: + - The mode of method used to obtain the IP address. + type: str + choices: [ admin, auto, learn ] + group_policy: + description: + - The group policy of the HSRP interface 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant), C(l3out), C(logical_node_profile), C(logical_interface_profile) and C(hsrp_interface_profile) must exist before using this module in + your playbook. The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), + M(cisco.aci.aci_l3out_logical_interface_profile) and M(cisco.aci.aci_l3out_hsrp_interface_profile) can be used for this. +seealso: +- module: aci_tenant +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- module: aci_l3out_logical_interface_profile +- module: aci_l3out_hsrp_interface_profile +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(hsrp:IfP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new HSRP group + cisco.aci.aci_l3out_hsrp_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_interface_group: group1 + ip: 12.34.56.32 + group_type: ipv4 + ip_obtain_mode: admin + group_policy: default + state: present + delegate_to: localhost + +- name: Delete a HSRP group + cisco.aci.aci_l3out_hsrp_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_interface_group: group1 + ip: 12.34.56.32 + group_type: ipv4 + ip_obtain_mode: admin + group_policy: default + state: absent + delegate_to: localhost + +- name: Query a HSRP group + cisco.aci.aci_l3out_hsrp_group: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_interface_group: group1 + ip: 12.34.56.32 + group_type: ipv4 + ip_obtain_mode: admin + group_policy: default + state: query + delegate_to: localhost + register: query_result + +- name: Query all HSRP groups + cisco.aci.aci_l3out_hsrp_group: + 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 +""" + +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"]), + hsrp_interface_group=dict(type="str", aliases=["name", "hsrp_group"]), + group_id=dict(type="int"), + ip=dict(type="str"), + mac=dict(type="str"), + group_name=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + group_type=dict(type="str", choices=["ipv4", "ipv6"]), + ip_obtain_mode=dict(type="str", choices=["admin", "auto", "learn"]), + group_policy=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", "interface_profile", "hsrp_interface_group"]], + ["state", "present", ["tenant", "l3out", "node_profile", "interface_profile", "hsrp_interface_group"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + hsrp_interface_group = module.params.get("hsrp_interface_group") + group_id = module.params.get("group_id") + ip = module.params.get("ip") + mac = module.params.get("mac") + group_name = module.params.get("group_name") + description = module.params.get("description") + group_type = module.params.get("group_type") + ip_obtain_mode = module.params.get("ip_obtain_mode") + group_policy = module.params.get("group_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}, + ), + subclass_4=dict( + aci_class="hsrpIfP", + aci_rn="hsrpIfP", + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_5=dict( + aci_class="hsrpGroupP", + aci_rn="hsrpGroupP-{0}".format(hsrp_interface_group), + module_object=hsrp_interface_group, + target_filter={"name": hsrp_interface_group}, + ), + child_classes=["hsrpRsGroupPol"], + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="hsrpGroupP", + class_config=dict(groupAf=group_type, groupId=group_id, groupName=group_name, ip=ip, ipObtainMode=ip_obtain_mode, mac=mac, descr=description), + child_configs=[dict(hsrpRsGroupPol=dict(attributes=dict(tnHsrpGroupPolName=group_policy)))] if group_policy is not None else [], + ) + + aci.get_diff(aci_class="hsrpGroupP") + + 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_hsrp_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_hsrp_interface_profile.py new file mode 100644 index 000000000..634a7f2f5 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_hsrp_interface_profile.py @@ -0,0 +1,320 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Shreyas Srish (@shrsr) <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_hsrp_interface_profile +short_description: Manages Layer 3 Outside (L3Out) HSRP interface profile (hsrp:IfP) +description: +- Manages L3Out HSRP interface profile 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: [ interface_profile_name, logical_interface ] + hsrp_policy: + description: + - Name of an existing HSRP interface policy. + type: str + aliases: [ name, hsrp_policy_name ] + version: + description: + - The version of the compatibility catalog. + type: str + choices: [ v1, v2 ] + 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), C(l3out), C(logical_node_profile) and C(logical_interface_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile) and M(cisco.aci.aci_l3out_logical_interface_profile) + can be used for this. +- If C(hsrp_policy) is used, it must exist before using this module in your playbook. + The M(cisco.aci.aci_interface_policy_hsrp) can be used for this. +seealso: +- module: aci_tenant +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- module: aci_l3out_logical_interface_profile +- module: aci_interface_policy_hsrp +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(hsrp:IfP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a new HSRP interface profile + cisco.aci.aci_l3out_hsrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_policy: default + state: present + delegate_to: localhost + +- name: Delete a HSRP interface profile + cisco.aci.aci_l3out_hsrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_policy: default + state: absent + delegate_to: localhost + +- name: Query a HSRP interface profile + cisco.aci.aci_l3out_hsrp_interface_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_policy: default + 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"]), + hsrp_policy=dict(type="str", aliases=["name", "hsrp_policy_name"]), + version=dict(type="str", choices=["v1", "v2"]), + 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") + hsrp_policy = module.params.get("hsrp_policy") + version = module.params.get("version") + 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="hsrpIfP", + aci_rn="hsrpIfP", + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + child_classes=["hsrpRsIfPol"], + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if hsrp_policy is not None: + child_configs = [dict(hsrpRsIfPol=dict(attributes=dict(tnHsrpIfPolName=hsrp_policy)))] + + aci.payload(aci_class="hsrpIfP", class_config=dict(version=version), child_configs=child_configs) + + aci.get_diff(aci_class="hsrpIfP") + + 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_hsrp_secondary_vip.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_hsrp_secondary_vip.py new file mode 100644 index 000000000..c9902ba76 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_hsrp_secondary_vip.py @@ -0,0 +1,344 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Shreyas Srish (@shrsr) <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_hsrp_secondary_vip +short_description: Manage HSRP Secondary Virtual IP of a HSRP group (hsrp:SecVip) +description: +- Manage HSRP Secondary Virtual IP of a HSRP group 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 ] + hsrp_interface_group: + description: + - Name of an existing HSRP group. + type: str + aliases: [ name, hsrp_group ] + secondary_virtual_ip: + description: + - The version of the compatibility catalog. + type: str + aliases: [ vip, secondary_vip ] + 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), C(l3out), C(logical_node_profile), C(logical_interface_profile), C(hsrp_interface_profile) and C(hsrp_group) must exist before using + this module in your playbook. The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), + M(cisco.aci.aci_l3out_logical_interface_profile), M(cisco.aci.aci_l3out_hsrp_interface_profile) and M(cisco.aci.aci_l3out_hsrp_group) can be used for + this. +seealso: +- module: aci_tenant +- module: aci_l3out +- module: aci_l3out_logical_node_profile +- module: aci_l3out_logical_interface_profile +- module: aci_l3out_hsrp_interface_profile +- module: aci_l3out_hsrp_group +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(hsrp:SecVip). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Shreyas Srish (@shrsr) +""" + +EXAMPLES = r""" +- name: Add a HSRP secondary virtual ip + cisco.aci.aci_l3out_hsrp_secondary_vip: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_group: my_hsrp_group + secondary_virtual_ip: 191.1.1.1 + state: present + delegate_to: localhost + +- name: Delete a HSRP secondary virtual ip + cisco.aci.aci_l3out_hsrp_secondary_vip: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_group: my_hsrp_interface_group + secondary_virtual_ip: 191.1.1.1 + state: absent + delegate_to: localhost + +- name: Query a HSRP secondary virtual ip + cisco.aci.aci_l3out_hsrp_secondary_vip: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_group: my_hsrp_group + secondary_virtual_ip: 191.1.1.1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all HSRP secondary virtual ips + cisco.aci.aci_l3out_hsrp_secondary_vip: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + hsrp_group: my_hsrp_group + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + 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"]), + hsrp_interface_group=dict(type="str", aliases=["name", "hsrp_group"]), + secondary_virtual_ip=dict(type="str", aliases=["vip", "secondary_vip"]), + 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", "hsrp_interface_group", "secondary_virtual_ip"]], + ["state", "present", ["tenant", "l3out", "node_profile", "interface_profile", "hsrp_interface_group", "secondary_virtual_ip"]], + ], + ) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + node_profile = module.params.get("node_profile") + interface_profile = module.params.get("interface_profile") + hsrp_interface_group = module.params.get("hsrp_interface_group") + secondary_virtual_ip = module.params.get("secondary_virtual_ip") + 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="hsrpIfP", + aci_rn="hsrpIfP", + module_object=interface_profile, + target_filter={"name": interface_profile}, + ), + subclass_5=dict( + aci_class="hsrpGroupP", + aci_rn="hsrpGroupP-{0}".format(hsrp_interface_group), + module_object=hsrp_interface_group, + target_filter={"name": hsrp_interface_group}, + ), + subclass_6=dict( + aci_class="hsrpSecVip", + aci_rn="hsrpSecVip-[{0}]".format(secondary_virtual_ip), + module_object=secondary_virtual_ip, + target_filter={"ip": secondary_virtual_ip}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="hsrpSecVip", class_config=dict(ip=secondary_virtual_ip)) + + aci.get_diff(aci_class="hsrpSecVip") + + 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 index f9a43b012..1f8b86070 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_interface.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_interface.py @@ -1,6 +1,8 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2021, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -51,14 +53,19 @@ options: 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") + - 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") + - The encapsulation on the interface (e.g. "vlan-500"). type: str + encap_scope: + description: + - The scope of the encapsulation on the interface. + type: str + choices: [ vrf, local ] address: description: - IP address. @@ -80,7 +87,7 @@ options: choices: [ l3-port, sub-interface, ext-svi ] mode: description: - - Interface mode, only used if instance_type is ext-svi + - Interface mode, only used if instance_type is ext-svi. type: str choices: [ regular, native, untagged ] state: @@ -95,19 +102,51 @@ options: - SVI auto state. type: str choices: [ enabled, disabled ] + description: + description: + - The description of the interface. + type: str + aliases: [ descr] + mac: + description: + - The MAC address of the interface. + type: str + aliases: [ mac_address ] + micro_bfd: + description: + - Enable micro BFD on the interface. + - Micro BFD should only be configured on Infra SR-MPLS L3Outs Direct Port Channel Interfaces. + type: bool + micro_bfd_destination: + description: + - The micro BFD destination address of the interface. + type: str + aliases: [ micro_bfd_address, micro_bfd_destination_address ] + micro_bfd_timer: + description: + - The micro BFD start timer in seconds. + - The APIC defaults to C(0) when unset during creation. + type: int + aliases: [ micro_bfd_start_timer, micro_bfd_start ] extends_documentation_fragment: - cisco.aci.aci - cisco.aci.annotation +notes: +- The C(tenant), C(l3out), C(node_profile) and the C(interface_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile) and + M(cisco.aci.aci_l3out_logical_interface_profile) can be used for this. seealso: +- module: aci_tenant - module: aci_l3out - module: aci_l3out_logical_node_profile +- module: aci_l3out_logical_interface_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) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -146,6 +185,25 @@ EXAMPLES = r""" state: present delegate_to: localhost +- name: Add direct port channel interface in the infra SR-MPLS l3out interface profile with micro BFD enabled + aci_l3out_interface: + host: apic + username: admin + password: SomeSecretPassword + tenant: infra + l3out: ansible_infra_sr_mpls_l3out + interface_profile: ansible_infra_sr_mpls_l3out_interface_profile + pod_id: 1 + node_id: 101 + path_ep: pc_ansible_test + interface_type: l3-port + addr: 192.168.90.1/24 + micro_bfd: true + micro_bfd_destination: 192.168.90.2 + micro_bfd_timer: 75 + state: present + delegate_to: localhost + - name: Delete an interface cisco.aci.aci_l3out_interface: host: apic @@ -307,15 +365,28 @@ def main(): interface_type=dict(type="str", choices=["l3-port", "sub-interface", "ext-svi"]), mode=dict(type="str", choices=["regular", "native", "untagged"]), encap=dict(type="str"), + encap_scope=dict(type="str", choices=["vrf", "local"]), auto_state=dict(type="str", choices=["enabled", "disabled"]), + description=dict(type="str", aliases=["descr"]), + mac=dict(type="str", aliases=["mac_address"]), + micro_bfd=dict(type="bool"), + micro_bfd_destination=dict(type="str", aliases=["micro_bfd_address", "micro_bfd_destination_address"]), + micro_bfd_timer=dict(type="int", aliases=["micro_bfd_start_timer", "micro_bfd_start"]), ) 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"]]], + required_if=[ + ["state", "present", ["interface_type", "pod_id", "node_id", "path_ep"]], + ["state", "absent", ["pod_id", "node_id", "path_ep"]], + ["micro_bfd", True, ["micro_bfd_destination"]], + ], + required_by={"micro_bfd_timer": "micro_bfd", "micro_bfd_destination": "micro_bfd"}, ) + aci = ACIModule(module) + tenant = module.params.get("tenant") l3out = module.params.get("l3out") node_profile = module.params.get("node_profile") @@ -330,9 +401,14 @@ def main(): interface_type = module.params.get("interface_type") mode = module.params.get("mode") encap = module.params.get("encap") + encap_scope = module.params.get("encap_scope") auto_state = module.params.get("auto_state") + description = module.params.get("description") + mac = module.params.get("mac") + micro_bfd = aci.boolean(module.params.get("micro_bfd")) + micro_bfd_destination = module.params.get("micro_bfd_destination") + micro_bfd_timer = module.params.get("micro_bfd_timer") - aci = ACIModule(module) if node_id and "-" in node_id: path_type = "protpaths" else: @@ -342,6 +418,12 @@ def main(): 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) + child_classes = [] + child_configs = [] + if micro_bfd is not None: + child_classes.append("bfdMicroBfdP") + child_configs.append(dict(bfdMicroBfdP=dict(attributes=dict(adminState=micro_bfd, dst=micro_bfd_destination, stTm=micro_bfd_timer)))) + aci.construct_url( root_class=dict( aci_class="fvTenant", @@ -367,7 +449,13 @@ def main(): 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_4=dict( + aci_class="l3extRsPathL3OutAtt", + aci_rn="rspathL3OutAtt-[{0}]".format(path_dn), + module_object=path_dn, + target_filter={"tDn": path_dn}, + ), + child_classes=child_classes, ) aci.get_existing() @@ -375,7 +463,20 @@ def main(): 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), + class_config=dict( + tDn=path_dn, + addr=address, + ipv6Dad=ipv6_dad, + mtu=mtu, + ifInstT=interface_type, + mode=mode, + encap=encap, + encapScope="ctx" if encap_scope == "vrf" else encap_scope, + autostate=auto_state, + descr=description, + mac=mac, + ), + child_configs=child_configs, ) aci.get_diff(aci_class="l3extRsPathL3OutAtt") 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 index d8311dad5..e127e1ff6 100644 --- 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 @@ -16,9 +16,9 @@ ANSIBLE_METADATA = { DOCUMENTATION = r""" --- module: aci_l3out_interface_secondary_ip -short_description: Manage Layer 3 Outside (L3Out) interface secondary IP addresses (l3ext: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). +- Manage Layer 3 Outside (L3Out) interface secondary IP addresses. options: tenant: description: @@ -89,7 +89,7 @@ seealso: - 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) + description: More information about the internal APIC class B(l3ext:Ip) link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Marcel Zehnder (@maercu) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py index db5890205..cd32ccb5a 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -18,39 +19,96 @@ description: options: tenant: description: - - Name of an existing tenant. + - The name of an existing tenant. type: str aliases: [ tenant_name ] l3out: description: - - Name of an existing L3Out. + - The name of an existing L3Out. type: str aliases: [ l3out_name ] node_profile: description: - - Name of the node profile. + - The name of the node profile. type: str aliases: [ node_profile_name, logical_node ] interface_profile: description: - - Name of the interface profile. + - The name of the logical interface profile. type: str aliases: [ name, interface_profile_name, logical_interface ] nd_policy: description: - - Name of the neighbor discovery interface policy. + - The name of the neighbor discovery interface policy. type: str - default: "" egress_dpp_policy: description: - - Name of the egress data plane policing policy. + - The name of the egress data plane policing policy. type: str - default: "" ingress_dpp_policy: description: - - Name of the ingress data plane policing policy. + - The name of the ingress data plane policing policy. type: str - default: "" + qos_priority: + description: + - The QoS priority class ID. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, level4, level5, level6, unspecified ] + aliases: [ priority, prio ] + qos_custom_policy: + description: + - The name of the QoS custom policy. + type: str + aliases: [ qos_custom_policy_name ] + pim_v4_interface_profile: + description: + - The PIM IPv4 interface profile. + type: dict + suboptions: + tenant: + description: + - The name of the tenant to which the PIM IPv4 interface policy belongs. + type: str + aliases: [ tenant_name ] + pim: + description: + - The name of the PIM IPv4 interface policy. + type: str + aliases: [ pim_interface_policy, name ] + aliases: [ pim_v4 ] + pim_v6_interface_profile: + description: + - The PIM IPv6 interface profile. + type: dict + suboptions: + tenant: + description: + - The name of the tenant to which the PIM IPv6 interface policy belongs. + type: str + aliases: [ tenant_name ] + pim: + description: + - The name of the PIM IPv6 interface policy. + type: str + aliases: [ pim_interface_policy, name ] + aliases: [ pim_v6 ] + igmp_interface_profile: + description: + - The IGMP interface profile. + type: dict + suboptions: + tenant: + description: + - The name of the tenant to which the IGMP interface policy belongs. + type: str + aliases: [ tenant_name ] + igmp: + description: + - The name of the IGMP interface policy. + type: str + aliases: [ igmp_interface_policy, name ] + aliases: [ igmp ] description: description: - The description for the logical interface profile. @@ -68,14 +126,19 @@ extends_documentation_fragment: - cisco.aci.annotation - cisco.aci.owner +notes: +- The I(tenant), I(l3out) and I(node_profile) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out) and M(cisco.aci.aci_l3out_logical_node_profile) can be used for this. seealso: -- module: aci_l3out -- module: aci_l3out_logical_node_profile +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile - name: APIC Management Information Model reference - description: More information about the internal APIC classes + description: More information about the internal APIC class B(l3ext:LIfP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Marcel Zehnder (@maercu) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -91,7 +154,7 @@ EXAMPLES = r""" state: present delegate_to: localhost -- name: Delete an interface profile +- name: Query an interface profile cisco.aci.aci_l3out_logical_interface_profile: host: apic username: admin @@ -100,30 +163,30 @@ EXAMPLES = r""" l3out: my_l3out node_profile: my_node_profile interface_profile: my_interface_profile - state: absent + state: query delegate_to: localhost + register: query_result -- name: Query an interface profile +- name: Query all interface profiles 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 +- name: Delete an interface profile cisco.aci.aci_l3out_logical_interface_profile: host: apic username: admin password: SomeSecretPassword - state: query + tenant: my_tenant + l3out: my_l3out + node_profile: my_node_profile + interface_profile: my_interface_profile + state: absent delegate_to: localhost - register: query_result """ RETURN = r""" @@ -233,7 +296,14 @@ url: from ansible.module_utils.basic import AnsibleModule -from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec +from ansible_collections.cisco.aci.plugins.module_utils.aci import ( + ACIModule, + aci_argument_spec, + aci_annotation_spec, + aci_owner_spec, + pim_interface_profile_spec, + igmp_interface_profile_spec, +) def main(): @@ -245,9 +315,14 @@ def main(): 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=""), + nd_policy=dict(type="str"), + egress_dpp_policy=dict(type="str"), + ingress_dpp_policy=dict(type="str"), + qos_priority=dict(type="str", choices=["level1", "level2", "level3", "level4", "level5", "level6", "unspecified"], aliases=["priority", "prio"]), + qos_custom_policy=dict(type="str", aliases=["qos_custom_policy_name"]), + pim_v4_interface_profile=dict(type="dict", options=pim_interface_profile_spec(), aliases=["pim_v4"]), + pim_v6_interface_profile=dict(type="dict", options=pim_interface_profile_spec(), aliases=["pim_v6"]), + igmp_interface_profile=dict(type="dict", options=igmp_interface_profile_spec(), aliases=["igmp"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), description=dict(type="str", aliases=["descr"]), ) @@ -268,11 +343,19 @@ def main(): nd_policy = module.params.get("nd_policy") egress_dpp_policy = module.params.get("egress_dpp_policy") ingress_dpp_policy = module.params.get("ingress_dpp_policy") + qos_priority = module.params.get("qos_priority") + qos_custom_policy = module.params.get("qos_custom_policy") description = module.params.get("description") state = module.params.get("state") aci = ACIModule(module) + extra_child_classes = dict( + pimIPV6IfP=dict(rs_class="pimRsV6IfPol", attribute_input=module.params.get("pim_v6_interface_profile")), + pimIfP=dict(rs_class="pimRsIfPol", attribute_input=module.params.get("pim_v4_interface_profile")), + igmpIfP=dict(rs_class="igmpRsIfPol", attribute_input=module.params.get("igmp_interface_profile")), + ) + aci.construct_url( root_class=dict( aci_class="fvTenant", @@ -294,11 +377,11 @@ def main(): ), subclass_3=dict( aci_class="l3extLIfP", - aci_rn="lifp-[{0}]".format(interface_profile), + aci_rn="lifp-{0}".format(interface_profile), module_object=interface_profile, target_filter={"name": interface_profile}, ), - child_classes=["l3extRsNdIfPol", "l3extRsIngressQosDppPol", "l3extRsEgressQosDppPol"], + child_classes=list(extra_child_classes.keys()) + ["l3extRsEgressQosDppPol", "l3extRsIngressQosDppPol", "l3extRsLIfPCustQosPol", "l3extRsNdIfPol"], ) aci.get_existing() @@ -308,8 +391,67 @@ def main(): 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))), + dict(l3extRsLIfPCustQosPol=dict(attributes=dict(tnQosCustomPolName=qos_custom_policy))), ] - aci.payload(aci_class="l3extLIfP", class_config=dict(name=interface_profile, descr=description), child_configs=child_configs) + for class_name, attribute in extra_child_classes.items(): + attribute_input = attribute.get("attribute_input") + if attribute_input is not None: + rs_class = attribute.get("rs_class") + if all(value is None for value in attribute_input.values()) and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("l3extLIfP", {}).get("children", {}): + if child.get(class_name): + child_configs.append( + { + class_name: dict( + attributes=dict(status="deleted"), + ), + } + ) + elif all(value is not None for value in attribute_input.values()): + if rs_class in ["pimRsV6IfPol", "pimRsIfPol"]: + child_configs.append( + { + class_name: dict( + attributes={}, + children=[ + { + rs_class: dict( + attributes=dict( + tDn="uni/tn-{0}/pimifpol-{1}".format(attribute_input.get("tenant"), attribute_input.get("pim")) + ) + ) + }, + ], + ) + } + ) + elif rs_class == "igmpRsIfPol": + child_configs.append( + { + class_name: dict( + attributes={}, + children=[ + { + rs_class: dict( + attributes=dict( + tDn="uni/tn-{0}/igmpIfPol-{1}".format(attribute_input.get("tenant"), attribute_input.get("igmp")) + ) + ) + }, + ], + ) + } + ) + + aci.payload( + aci_class="l3extLIfP", + class_config=dict( + name=interface_profile, + prio=qos_priority, + descr=description, + ), + child_configs=child_configs, + ) aci.get_diff(aci_class="l3extLIfP") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py index 798a82111..2d1a407ae 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_interface_profile_ospf_policy.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2022, Jason Juenger (@jasonjuenger) <jasonjuenger@gmail.com> +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -13,9 +14,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported 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) +short_description: Manage Layer 3 Outside (L3Out) OSPF interface profile (ospf:IfP) description: -- Manage L3Out interface profile OSPF policies on Cisco ACI fabrics. +- Manage L3Out logical interface profile OSPF policies on Cisco ACI fabrics. options: tenant: description: @@ -64,14 +65,22 @@ extends_documentation_fragment: - cisco.aci.annotation - cisco.aci.owner +notes: +- The C(tenant), C(l3out), C(node_profile), C(interface_profile) and C(ospf_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_l3out), M(cisco.aci.aci_l3out_logical_node_profile), M(cisco.aci.aci_l3out_logical_interface_profile) + and (cisco.aci.aci_interface_policy_ospf) can be used for this. seealso: -- module: aci_l3out -- module: aci_l3out_logical_node_profile +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_logical_node_profile +- module: cisco.aci.aci_l3out_logical_interface_profile +- module: cisco.aci.aci_interface_policy_ospf - name: APIC Management Information Model reference - description: More information about the internal APIC classes + description: More information about the internal APIC class B(ospf:IfP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Jason Juenger (@jasonjuenger) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" 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 index 4ddd32161..822c53af7 100644 --- 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 @@ -94,7 +94,7 @@ notes: 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). + description: More information about the internal APIC class B(l3ext:Member). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Anvitha Jain (@anvitha-jain) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py index 33c8a22a5..7ab756566 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node.py @@ -1,6 +1,8 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2021, Marcel Zehnder (@maercu) +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -52,9 +54,20 @@ options: loopback_address: description: - The loopback IP address. + - The BGP-EVPN loopback IP address for Infra SR-MPLS L3Outs. - A configured loopback address can be removed by passing an empty string (see Examples). type: str aliases: [ loopback ] + mpls_transport_loopback_address: + description: + - The MPLS transport loopback IP address for Infra SR-MPLS L3Outs. + type: str + aliases: [ mpls_transport_loopback ] + sid: + description: + - The Segment ID (SID) Index for Infra SR-MPLS L3Outs. + type: str + aliases: [ segment_id ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -70,10 +83,11 @@ 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) + description: More information about the internal APIC classes B(l3ext:RsNodeL3OutAtt) link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Marcel Zehnder (@maercu) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -92,6 +106,22 @@ EXAMPLES = r""" state: present delegate_to: localhost +- name: Add a node to a infra SR-MPLS l3out node profile + cisco.aci.aci_l3out_logical_node: &aci_infra_node_profile_node + host: apic + username: admin + password: SomeSecretPassword + tenant: infra + l3out: ansible_infra_sr_mpls_l3out + node_profile: ansible_infra_sr_mpls_l3out_node_profile + pod_id: 1 + node_id: 113 + router_id_as_loopback: no + loopback_address: 50.0.0.1 + mpls_transport_loopback_address: 51.0.0.1 + sid: 500 + delegate_to: localhost + - name: Remove a loopback address from a node in node profile cisco.aci.aci_l3out_logical_node: host: apic @@ -264,6 +294,8 @@ def main(): router_id=dict(type="str"), router_id_as_loopback=dict(type="str", default="yes", choices=["yes", "no"]), loopback_address=dict(type="str", aliases=["loopback"]), + mpls_transport_loopback_address=dict(type="str", aliases=["mpls_transport_loopback"]), + sid=dict(type="str", aliases=["segment_id"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), ) @@ -274,6 +306,8 @@ def main(): ["state", "absent", ["tenant", "l3out", "node_profile", "pod_id", "node_id"]], ["state", "present", ["tenant", "l3out", "node_profile", "pod_id", "node_id"]], ], + required_by={"mpls_transport_loopback_address": "loopback_address"}, + required_together=[("mpls_transport_loopback_address", "sid")], ) tenant = module.params.get("tenant") @@ -284,6 +318,8 @@ def main(): router_id = module.params.get("router_id") router_id_as_loopback = module.params.get("router_id_as_loopback") loopback_address = module.params.get("loopback_address") + mpls_transport_loopback_address = module.params.get("mpls_transport_loopback_address") + sid = module.params.get("sid") state = module.params.get("state") tdn = None @@ -294,6 +330,9 @@ def main(): child_classes = ["l3extLoopBackIfP"] + if mpls_transport_loopback_address is not None: + child_classes.append("mplsNodeSidP") + child_configs = [] aci.construct_url( @@ -333,7 +372,12 @@ def main(): previous_loopback_address = child.get("l3extLoopBackIfP", {}).get("attributes", {}).get("addr") child_configs.append(dict(l3extLoopBackIfP=dict(attributes=dict(addr=previous_loopback_address, status="deleted")))) elif loopback_address: - child_configs.append(dict(l3extLoopBackIfP=dict(attributes=dict(addr=loopback_address)))) + loopback_address_config = dict(l3extLoopBackIfP=dict(attributes=dict(addr=loopback_address), children=[])) + if mpls_transport_loopback_address: + loopback_address_config["l3extLoopBackIfP"]["children"].append( + dict(mplsNodeSidP=dict(attributes=dict(loopbackAddr=mpls_transport_loopback_address, sidoffset=sid))) + ) + child_configs.append(loopback_address_config) aci.payload( aci_class="l3extRsNodeL3OutAtt", diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py index 10eefd018..ac6b87ebe 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_logical_node_profile.py @@ -1,6 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -43,6 +44,12 @@ options: type: str choices: [ AF11, AF12, AF13, AF21, AF22, AF23, AF31, AF32, AF33, AF41, AF42, AF43, CS0, CS1, CS2, CS3, CS4, CS5, CS6, CS7, EF, VA, unspecified ] aliases: [ target_dscp ] + mpls_custom_qos_policy: + description: + - The MPLS custom QoS policy name for the node profile. + - This argument should only be used for Infra SR-MPLS L3Outs. + aliases: [ mpls_custom_qos_policy_name ] + type: str state: description: - Use C(present) or C(absent) for adding or removing. @@ -62,10 +69,11 @@ extends_documentation_fragment: seealso: - module: aci_l3out - name: APIC Management Information Model reference - description: More information about the internal APIC classes B(vmm:DomP) + description: More information about the internal APIC classes B(l3ext:LNodeP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Jason Juenger (@jasonjuenger) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -82,6 +90,18 @@ EXAMPLES = r""" state: present delegate_to: localhost +- name: Add a new node profile with MPLS custom QOS policy to SR-MPLS infra l3out + cisco.aci.aci_l3out_logical_node_profile: + host: apic + username: admin + password: SomeSecretPassword + tenant: infra + l3out: infra_sr_mpls_l3out + node_profile: infra_sr_mpls_l3out_node_profile + mpls_custom_qos_policy: infra_mpls_custom_qos_policy + state: present + delegate_to: localhost + - name: Delete a node profile cisco.aci.aci_l3out_logical_node_profile: host: apic @@ -264,6 +284,8 @@ def main(): ], aliases=["target_dscp"], ), + # alias=dict(type="str"), not implemented because of different (api/alias/mo/uni/) api endpoint + mpls_custom_qos_policy=dict(type="str", aliases=["mpls_custom_qos_policy_name"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), ) @@ -282,11 +304,16 @@ def main(): l3out = module.params.get("l3out") description = module.params.get("description") dscp = module.params.get("dscp") + mpls_custom_qos_policy = module.params.get("mpls_custom_qos_policy") state = module.params.get("state") name_alias = module.params.get("name_alias") aci = ACIModule(module) + child_classes = [] + if mpls_custom_qos_policy is not None: + child_classes.append("l3extRsLNodePMplsCustQosPol") + aci.construct_url( root_class=dict( aci_class="fvTenant", @@ -306,11 +333,21 @@ def main(): module_object=node_profile, target_filter={"name": node_profile}, ), + child_classes=child_classes, ) aci.get_existing() if state == "present": + child_configs = [] + if mpls_custom_qos_policy is not None: + if mpls_custom_qos_policy == "": + child_configs.append(dict(l3extRsLNodePMplsCustQosPol=dict(attributes=dict(status="deleted")))) + else: + child_configs.append( + dict(l3extRsLNodePMplsCustQosPol=dict(attributes=dict(tDn="uni/tn-infra/qosmplscustom-{0}".format(mpls_custom_qos_policy)))) + ) + aci.payload( aci_class="l3extLNodeP", class_config=dict( @@ -319,6 +356,7 @@ def main(): targetDscp=dscp, nameAlias=name_alias, ), + child_configs=child_configs, ) aci.get_diff(aci_class="l3extLNodeP") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py index 039593366..a8c410bb5 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_static_routes.py @@ -15,7 +15,7 @@ DOCUMENTATION = r""" module: aci_l3out_static_routes short_description: Manage Static routes object (l3ext:ipRouteP) description: -- Manage External Subnet objects (l3ext:ipRouteP) +- Manage External Subnet objects. options: description: description: @@ -86,7 +86,7 @@ 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). + description: More information about the internal APIC class B(l3ext:ipRouteP). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Anvitha Jain(@anvitha-jain) 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 index 36c3afa36..b475bba5c 100644 --- 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 @@ -65,7 +65,7 @@ seealso: - 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) + description: More information about the internal APIC classes B(ip:NexthopP) link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Marcel Zehnder (@maercu) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_l3out_to_sr_mpls_infra_l3out.py b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_to_sr_mpls_infra_l3out.py new file mode 100644 index 000000000..db621d5c8 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_l3out_to_sr_mpls_infra_l3out.py @@ -0,0 +1,355 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "certified", +} + +DOCUMENTATION = r""" +--- +module: aci_l3out_to_sr_mpls_infra_l3out +short_description: Manage Layer 3 Outside (L3Out) to SR-MPLS Infra L3Outs objects (l3ext:ConsLbl) +description: +- Manage Layer 3 Outside (L3Out) to SR-MPLS Infra L3Outs objects on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of an existing SR MPLS VRF L3Out. + type: str + aliases: [ l3out_name, name ] + infra_l3out: + description: + - The name of an existing SR-MPLS Infra L3Out. + type: str + aliases: [ infra_l3out_name ] + external_epg: + description: + - The distinguished name (DN) of the external EPG. + type: str + aliases: [ external_epg_dn ] + outbound_route_map: + description: + - The distinguished name (DN) of the outbound route map. + type: str + aliases: [ outbound_route_map_dn, outbound ] + inbound_route_map: + description: + - The distinguished name (DN) of the inbound route map. + - Use an empty string to remove the inbound route map. + type: str + aliases: [ inbound_route_map_dn, inbound ] + 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(l3out) 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:ConsLbl). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a new l3out to sr-mpls infra l3out + cisco.aci.aci_l3out_to_sr_mpls_infra_l3out: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + description: L3Out for Production tenant + infra_l3out: infra_l3out_name + external_epg: uni/tn-production/out-l3out_name/instP-external_epg_name + outbound_route_map: uni/tn-production/prof-outbound_route_map_name + inbound_route_map: uni/tn-production/prof-inbound_route_map_name + state: present + delegate_to: localhost + +- name: Delete a l3out to sr-mpls infra l3out + cisco.aci.aci_l3out_to_sr_mpls_infra_l3out: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + infra_l3out: infra_l3out_name + state: absent + delegate_to: localhost + +- name: Query a l3out to sr-mpls infra l3out + cisco.aci.aci_l3out_to_sr_mpls_infra_l3out: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: prod_l3out + state: query + delegate_to: localhost + register: query_result + +- name: Query all l3out to sr-mpls infra l3outs + cisco.aci.aci_l3out_to_sr_mpls_infra_l3out: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_all_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 + description=dict(type="str", aliases=["descr"]), + infra_l3out=dict(type="str", aliases=["infra_l3out_name"]), + external_epg=dict(type="str", aliases=["external_epg_dn"]), + outbound_route_map=dict(type="str", aliases=["outbound_route_map_dn", "outbound"]), + inbound_route_map=dict(type="str", aliases=["inbound_route_map_dn", "inbound"]), + 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", "infra_l3out"]], + ["state", "present", ["l3out", "tenant", "infra_l3out", "external_epg", "outbound_route_map"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + description = module.params.get("description") + infra_l3out = module.params.get("infra_l3out") + external_epg = module.params.get("external_epg") + outbound_route_map = module.params.get("outbound_route_map") + inbound_route_map = module.params.get("inbound_route_map") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + # l3extRsProvLblDef, bgpDomainIdAllocator are auto-generated classes, added for query output + child_classes = ["l3extRsLblToInstP", "l3extRsLblToProfile", "l3extRsProvLblDef", "bgpDomainIdAllocator"] + + 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="l3extConsLbl", + aci_rn="conslbl-{0}".format(infra_l3out), + module_object=infra_l3out, + target_filter={"name": infra_l3out}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + + if aci.existing: + children = aci.existing[0].get("l3extConsLbl", {}).get("children", []) + for child in children: + if child.get("l3extRsLblToProfile"): + tdn = child.get("l3extRsLblToProfile").get("attributes").get("tDn") + direction = child.get("l3extRsLblToProfile").get("attributes").get("direction") + route_map = outbound_route_map if direction == "export" else inbound_route_map + # Inbound route-map is removed when input is different or an empty string, otherwise ignored. + if route_map is not None and tdn != route_map: + child_configs.append(dict(l3extRsLblToProfile=dict(attributes=dict(tDn=tdn, direction=direction, status="deleted")))) + elif child.get("l3extRsLblToInstP"): + tdn = child.get("l3extRsLblToInstP").get("attributes").get("tDn") + if tdn != external_epg: + child_configs.append(dict(l3extRsLblToInstP=dict(attributes=dict(tDn=tdn, status="deleted")))) + + child_configs.append(dict(l3extRsLblToProfile=dict(attributes=dict(tDn=outbound_route_map, direction="export")))) + child_configs.append(dict(l3extRsLblToInstP=dict(attributes=dict(tDn=external_epg)))) + + if inbound_route_map: + child_configs.append(dict(l3extRsLblToProfile=dict(attributes=dict(tDn=inbound_route_map, direction="import")))) + + aci.payload( + aci_class="l3extConsLbl", + class_config=dict( + name=infra_l3out, + descr=description, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="l3extConsLbl") + + 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 index be97ede40..30fb14403 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_maintenance_group_node.py @@ -15,7 +15,7 @@ DOCUMENTATION = r""" module: aci_maintenance_group_node short_description: Manage maintenance group nodes (fabric:NodeBlk) description: -- Manage maintenance group nodes +- Manage maintenance group nodes. options: group: description: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py b/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py index 973d70561..1ba471c9d 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_match_route_destination.py @@ -14,7 +14,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_match_route_destination -short_description: Manage Match action rule term based on the Route Destination. (rtctrl:MatchRtDest) +short_description: Manage Match action rule term based on the Route Destination (rtctrl:MatchRtDest) description: - Match action rule terms based on the Route Destination for Subject Profiles on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_netflow_exporter_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_netflow_exporter_policy.py new file mode 100644 index 000000000..e45eb6217 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_netflow_exporter_policy.py @@ -0,0 +1,490 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_netflow_exporter_policy +short_description: Manage Netflow Exporter Policy (netflow:ExporterPol) +description: +- Manage Netflow Exporter Policies for tenants on Cisco ACI fabrics. +- Exporter information for bootstrapping the netflow Collection agent. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + netflow_exporter_policy: + description: + - The name of the Netflow Exporter Policy. + type: str + aliases: [ netflow_exporter, netflow_exporter_name, name ] + dscp: + description: + - The IP DSCP value. + - The APIC defaults to C(CS2) when unset during creation. + It defaults to C(VA) for APIC versions 4.2 or prior. + 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 ] + destination_address: + description: + - The remote node destination IP address. + type: str + destination_port: + description: + - The remote node destination port. + - Accepted values are any valid TCP/UDP port range. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + source_ip_type: + description: + - The type of Exporter source IP Address. + - It can be one of the available management IP Address for a given leaf or a custom IP Address. + type: str + choices: [ custom_source_ip, inband_management_ip, out_of_band_management_ip, ptep ] + custom_source_address: + description: + - The custom source IP address. + - It can only be used if O(source_ip_type=custom_source_ip). + type: str + associated_epg: + description: + - The associated EPG. + - To remove the current associated EPG, pass an empty dictionary. + type: dict + aliases: [ epg ] + suboptions: + tenant: + description: + - The name of the tenant to which the associated AP/EPG belong. + type: str + ap: + description: + - The name of the associated Application Profile to which the associated EPG belongs. + type: str + epg: + description: + - The name of the associated EPG. + type: str + associated_extepg: + description: + - The associated external EPG. + - To remove the current associated external EPG, pass an empty dictionary. + type: dict + aliases: [ external_epg, associated_external_epg ] + suboptions: + tenant: + description: + - The name of the tenant to which the associated L3Out/external EPG belong. + type: str + l3out: + description: + - The name of the L3Out to which the associated external EPG belongs. + type: str + extepg: + description: + - The name of the associated EPG. + type: str + associated_vrf: + description: + - The associated VRF. + - To remove the current associated VRF, pass an empty dictionary. + type: dict + aliases: [ vrf, context, associated_context ] + suboptions: + tenant: + description: + - The name of the tenant to which the associated VRF belongs. + type: str + vrf: + description: + - The name of the associated VRF. + type: str + description: + description: + - The description for the Netflow Exporter 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) can be used for this. +- The I(associated_epg) and I(associated_extepg) are mutually exclusive. +- If the I(associated_epg) is used, the I(epg), the(tenant) and + the I(ap) must exist before using this module in your play book. + The M(cisco.aci.aci_epg) and the M(cisco.aci.aci_ap) can be used for this. +- If the I(associated_extepg) is used, the I(extepg), the(tenant) and + the I(l3out) must exist before using this module in your play book. + The M(cisco.aci.aci_l3out_extepg) and the M(cisco.aci.aci_l3out) can be used for this. +- If the I(associated_vrf) is used, the I(vrf) and the I(tenant) must exist + before using this module in your play book. + The M(cisco.aci.aci_vrf) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_vrf +- module: cisco.aci.aci_ap +- module: cisco.aci.aci_epg +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_l3out_extepg +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(netflow:ExporterPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new Netflow Exporter Policy + cisco.aci.aci_netflow_exporter_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_exporter_policy: my_netflow_exporter_policy + dscp: CS2 + destination_address: 11.11.11.1 + destination_port: 25 + source_ip_type: custom_source_ip + custom_source_address: 11.11.11.2 + associated_epg: + tenant: my_tenant + ap: my_ap + epg: my_epg + associated_vrf: + tenant: my_tenant + vrf: my_vrf + state: present + delegate_to: localhost + +- name: Remove associated EPG and VRF from the new Netflow Exporter Policy + cisco.aci.aci_netflow_exporter_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_exporter_policy: my_netflow_exporter_policy + associated_epg: {} + associated_vrf: {} + delegate_to: localhost + +- name: Query a Netflow Exporter Policy + cisco.aci.aci_netflow_exporter_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_exporter_policy: my_netflow_exporter_policy + state: query + delegate_to: localhost + +- name: Query all Netflow Exporter Policies in my_tenant + cisco.aci.aci_netflow_exporter_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + state: query + delegate_to: localhost + +- name: Query all Netflow Exporter Policies + cisco.aci.aci_netflow_exporter_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete a Netflow Exporter Policy + cisco.aci.aci_netflow_exporter_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_exporter_policy: my_netflow_exporter_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, + aci_contract_dscp_spec, + associated_netflow_exporter_epg_spec, + associated_netflow_exporter_extepg_spec, + associated_netflow_exporter_vrf_spec, +) +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_SOURCE_IP_TYPE_NETFLOW_EXPORTER_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + netflow_exporter_policy=dict(type="str", aliases=["netflow_exporter", "netflow_exporter_name", "name"]), + dscp=dict((k, aci_contract_dscp_spec()[k]) for k in aci_contract_dscp_spec() if k != "aliases"), + destination_address=dict(type="str"), + destination_port=dict(type="str"), + source_ip_type=dict(type="str", choices=list(MATCH_SOURCE_IP_TYPE_NETFLOW_EXPORTER_MAPPING.keys())), + custom_source_address=dict(type="str"), + associated_epg=dict(type="dict", aliases=["epg"], options=associated_netflow_exporter_epg_spec()), + associated_extepg=dict(type="dict", aliases=["external_epg", "associated_external_epg"], options=associated_netflow_exporter_extepg_spec()), + associated_vrf=dict(type="dict", aliases=["vrf", "associated_context", "context"], options=associated_netflow_exporter_vrf_spec()), + 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", ["tenant", "netflow_exporter_policy"]], + ["state", "present", ["tenant", "netflow_exporter_policy", "destination_address", "destination_port"]], + ], + mutually_exclusive=[["associated_epg", "associated_extepg"]], + ) + + tenant = module.params.get("tenant") + description = module.params.get("description") + netflow_exporter_policy = module.params.get("netflow_exporter_policy") + dscp = module.params.get("dscp") + destination_address = module.params.get("destination_address") + destination_port = module.params.get("destination_port") + source_ip_type = MATCH_SOURCE_IP_TYPE_NETFLOW_EXPORTER_MAPPING.get(module.params.get("source_ip_type")) + custom_source_address = module.params.get("custom_source_address") + associated_epg = module.params.get("associated_epg") + associated_extepg = module.params.get("associated_extepg") + associated_vrf = module.params.get("associated_vrf") + state = module.params.get("state") + + aci = ACIModule(module) + + child_classes = ["netflowRsExporterToCtx", "netflowRsExporterToEPg"] + + 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="netflowExporterPol", + aci_rn="exporterpol-{0}".format(netflow_exporter_policy), + module_object=netflow_exporter_policy, + target_filter={"name": netflow_exporter_policy}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if associated_vrf is not None: + if all(value is None for value in associated_vrf.values()) and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("netflowExporterPol", {}).get("children", {}): + if child.get("netflowRsExporterToCtx"): + child_configs.extend([dict(netflowRsExporterToCtx=dict(attributes=dict(status="deleted")))]) + elif all(value is not None for value in associated_vrf.values()): + child_configs.extend( + [ + dict( + netflowRsExporterToCtx=dict( + attributes=dict(tDn="uni/tn-{0}/ctx-{1}".format(associated_vrf.get("tenant"), associated_vrf.get("vrf"))) + ) + ), + ] + ) + if associated_epg is not None: + if all(value is None for value in associated_epg.values()) and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("netflowExporterPol", {}).get("children", {}): + if child.get("netflowRsExporterToEPg"): + child_configs.extend([dict(netflowRsExporterToEPg=dict(attributes=dict(status="deleted")))]) + elif all(value is not None for value in associated_epg.values()): + child_configs.extend( + [ + dict( + netflowRsExporterToEPg=dict( + attributes=dict( + tDn="uni/tn-{0}/ap-{1}/epg-{2}".format(associated_epg.get("tenant"), associated_epg.get("ap"), associated_epg.get("epg")) + ) + ) + ), + ] + ) + elif associated_extepg is not None: + if all(value is None for value in associated_extepg.values()) and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("netflowExporterPol", {}).get("children", {}): + if child.get("netflowRsExporterToEPg"): + child_configs.extend([dict(netflowRsExporterToEPg=dict(attributes=dict(status="deleted")))]) + elif all(value is not None for value in associated_extepg.values()): + child_configs.extend( + [ + dict( + netflowRsExporterToEPg=dict( + attributes=dict( + tDn="uni/tn-{0}/out-{1}/instP-{2}".format( + associated_extepg.get("tenant"), associated_extepg.get("l3out"), associated_extepg.get("extepg") + ) + ) + ) + ), + ] + ) + aci.payload( + aci_class="netflowExporterPol", + class_config=dict( + name=netflow_exporter_policy, + descr=description, + dscp=dscp, + dstAddr=destination_address, + dstPort=destination_port, + sourceIpType=source_ip_type, + srcAddr=custom_source_address, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="netflowExporterPol") + + 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_netflow_monitor_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_netflow_monitor_policy.py new file mode 100644 index 000000000..462187609 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_netflow_monitor_policy.py @@ -0,0 +1,300 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_netflow_monitor_policy +short_description: Manage Netflow Monitor Policy (netflow:MonitorPol) +description: +- Manage Netflow Monitor Policies for tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + netflow_monitor_policy: + description: + - The name of the Netflow Monitor Policy. + type: str + aliases: [ netflow_monitor, netflow_monitor_name, name ] + netflow_record_policy: + description: + - The name of the Netflow Record Policy. + - To remove the current Netflow Record Policy, pass an empty string. + type: str + aliases: [ netflow_record, netflow_record_name ] + description: + description: + - The description for the Netflow Monitor 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) can be used for this. +- If the I(netflow_record_policy) is used, it must exist before using this module in your playbook. + The M(cisco.aci.aci_netflow_record_policy) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_netflow_record_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(netflow:MonitorPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new Netflow Monitor Policy + cisco.aci.aci_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_monitor_policy: my_netflow_monitor_policy + netflow_record_policy: my_netflow_record_policy + state: present + delegate_to: localhost + +- name: Query a Netflow Monitor Policy + cisco.aci.aci_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_monitor_policy: my_netflow_monitor_policy + state: query + delegate_to: localhost + +- name: Query all Netflow Monitor Policies in my_tenant + cisco.aci.aci_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + state: query + delegate_to: localhost + +- name: Query all Netflow Monitor Policies + cisco.aci.aci_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete a Netflow Monitor Policy + cisco.aci.aci_netflow_monitor_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_monitor_policy: my_netflow_monitor_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( + tenant=dict(type="str", aliases=["tenant_name"]), + netflow_monitor_policy=dict(type="str", aliases=["netflow_monitor", "netflow_monitor_name", "name"]), + netflow_record_policy=dict(type="str", aliases=["netflow_record", "netflow_record_name"]), + 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", ["tenant", "netflow_monitor_policy"]], + ["state", "present", ["tenant", "netflow_monitor_policy"]], + ], + ) + + tenant = module.params.get("tenant") + description = module.params.get("description") + netflow_monitor_policy = module.params.get("netflow_monitor_policy") + netflow_record_policy = module.params.get("netflow_record_policy") + state = module.params.get("state") + + aci = ACIModule(module) + + child_classes = ["netflowRsMonitorToRecord"] + + 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="netflowMonitorPol", + aci_rn="monitorpol-{0}".format(netflow_monitor_policy), + module_object=netflow_monitor_policy, + target_filter={"name": netflow_monitor_policy}, + ), + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + child_configs.append(dict(netflowRsMonitorToRecord=dict(attributes=dict(tnNetflowRecordPolName=netflow_record_policy)))) + aci.payload( + aci_class="netflowMonitorPol", + class_config=dict( + name=netflow_monitor_policy, + descr=description, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="netflowMonitorPol") + + 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_netflow_monitor_to_exporter.py b/ansible_collections/cisco/aci/plugins/modules/aci_netflow_monitor_to_exporter.py new file mode 100644 index 000000000..50e2f2524 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_netflow_monitor_to_exporter.py @@ -0,0 +1,290 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_netflow_monitor_to_exporter +short_description: Manage Netflow Monitor to Exporter (netflow:RsMonitorToExporter) +description: +- Link Netflow Exporter policies to Netflow Monitor policies for tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + netflow_monitor_policy: + description: + - The name of the Netflow Monitor Policy. + type: str + aliases: [ netflow_monitor, netflow_monitor_name, name ] + netflow_exporter_policy: + description: + - The name of the Netflow Exporter Policy. + type: str + aliases: [ netflow_exporter, netflow_exporter_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: +- The I(tenant), I(netflow_monitor_policy) and I(netflow_exporter_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), M(cisco.aci.aci_netflow_monitor_policy), M(cisco.aci.aci_netflow_exporter_policy) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_netflow_monitor_policy +- module: cisco.aci.aci_netflow_exporter_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(netflow:RsMonitorToExporter). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new Netflow Monitor Policy + cisco.aci.aci_netflow_monitor_to_exporter: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_monitor_policy: my_netflow_monitor_policy + netflow_exporter_policy: my_netflow_exporter_policy + state: present + delegate_to: localhost + +- name: Query a Netflow Monitor Policy + cisco.aci.aci_netflow_monitor_to_exporter: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_monitor_policy: my_netflow_monitor_policy + netflow_exporter_policy: my_netflow_exporter_policy + state: query + delegate_to: localhost + +- name: Query all Netflow Monitor Policies in my_tenant + cisco.aci.aci_netflow_monitor_to_exporter: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + state: query + delegate_to: localhost + +- name: Query all Netflow Monitor Policies + cisco.aci.aci_netflow_monitor_to_exporter: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete a Netflow Monitor Policy + cisco.aci.aci_netflow_monitor_to_exporter: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_monitor_policy: my_netflow_monitor_policy + netflow_exporter_policy: my_netflow_exporter_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( + tenant=dict(type="str", aliases=["tenant_name"]), + netflow_monitor_policy=dict(type="str", aliases=["netflow_monitor", "netflow_monitor_name", "name"]), + netflow_exporter_policy=dict(type="str", aliases=["netflow_exporter", "netflow_exporter_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", ["tenant", "netflow_monitor_policy", "netflow_exporter_policy"]], + ["state", "present", ["tenant", "netflow_monitor_policy", "netflow_exporter_policy"]], + ], + ) + + tenant = module.params.get("tenant") + netflow_monitor_policy = module.params.get("netflow_monitor_policy") + netflow_exporter_policy = module.params.get("netflow_exporter_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="netflowMonitorPol", + aci_rn="monitorpol-{0}".format(netflow_monitor_policy), + module_object=netflow_monitor_policy, + target_filter={"name": netflow_monitor_policy}, + ), + subclass_2=dict( + aci_class="netflowRsMonitorToExporter", + aci_rn="rsmonitorToExporter-{0}".format(netflow_exporter_policy), + module_object=netflow_exporter_policy, + target_filter={"tnNetflowExporterPolName": netflow_exporter_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="netflowRsMonitorToExporter", + class_config=dict(tnNetflowExporterPolName=netflow_exporter_policy), + ) + + aci.get_diff(aci_class="netflowRsMonitorToExporter") + + 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_netflow_record_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_netflow_record_policy.py new file mode 100644 index 000000000..e2f04af0c --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_netflow_record_policy.py @@ -0,0 +1,313 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_netflow_record_policy +short_description: Manage Netflow Record Policy (netflow:RecordPol) +description: +- Manage Netflow Record Policies for tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + netflow_record_policy: + description: + - The name of the Netflow Record Policy. + type: str + aliases: [ netflow_record, netflow_record_name, name ] + collect: + description: + - The collect parameters for the flow record. + - The APIC defaults to C(source_interface) when unset during creation. + type: list + elements: str + choices: [ bytes_counter, pkts_counter, pkt_disposition, sampler_id, source_interface, tcp_flags, first_pkt_timestamp, recent_pkt_timestamp ] + match: + description: + - The match parameters for the flow record. + type: list + elements: str + choices: [ destination_ipv4_v6, destination_ipv4, destination_ipv6, destination_mac, destination_port, ethertype, ip_protocol, source_ipv4_v6, + source_ipv4, source_ipv6, source_mac, source_port, ip_tos, unspecified, vlan ] + description: + description: + - The description for the Netflow Record 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) 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(netflow:RecordPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new Netflow Record Policy + cisco.aci.aci_netflow_record_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_record_policy: my_netflow_record_policy + collect: [pkts_counter, pkt_disposition] + match: [destination_ipv4, source_ipv4] + state: present + delegate_to: localhost + +- name: Query a Netflow Record Policy + cisco.aci.aci_netflow_record_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_record_policy: my_netflow_record_policy + state: query + delegate_to: localhost + +- name: Query all Netflow Record Policies in my_tenant + cisco.aci.aci_netflow_record_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + state: query + delegate_to: localhost + +- name: Query all Netflow Record Policies + cisco.aci.aci_netflow_record_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete a Netflow Record Policy + cisco.aci.aci_netflow_record_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + netflow_record_policy: my_netflow_record_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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_COLLECT_NETFLOW_RECORD_MAPPING, MATCH_MATCH_NETFLOW_RECORD_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + netflow_record_policy=dict(type="str", aliases=["netflow_record", "netflow_record_name", "name"]), + collect=dict(type="list", elements="str", choices=list(MATCH_COLLECT_NETFLOW_RECORD_MAPPING.keys())), + match=dict(type="list", elements="str", choices=list(MATCH_MATCH_NETFLOW_RECORD_MAPPING.keys())), + 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", ["tenant", "netflow_record_policy"]], + ["state", "present", ["tenant", "netflow_record_policy"]], + ], + ) + + tenant = module.params.get("tenant") + description = module.params.get("description") + netflow_record_policy = module.params.get("netflow_record_policy") + state = module.params.get("state") + + if module.params.get("collect") is not None: + collect = ",".join(sorted(MATCH_COLLECT_NETFLOW_RECORD_MAPPING.get(v) for v in module.params.get("collect"))) + else: + collect = None + + if module.params.get("match") is not None: + match = ",".join(sorted(MATCH_MATCH_NETFLOW_RECORD_MAPPING.get(v) for v in module.params.get("match"))) + else: + match = None + + 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="netflowRecordPol", + aci_rn="recordpol-{0}".format(netflow_record_policy), + module_object=netflow_record_policy, + target_filter={"name": netflow_record_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="netflowRecordPol", + class_config=dict( + name=netflow_record_policy, + collect=collect, + match=match, + descr=description, + ), + ) + + aci.get_diff(aci_class="netflowRecordPol") + + 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_block.py b/ansible_collections/cisco/aci/plugins/modules/aci_node_block.py new file mode 100644 index 000000000..d2f86a275 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_node_block.py @@ -0,0 +1,390 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_node_block +short_description: Manage Node Block (infra:NodeBlk) +description: +- Manage Node Blocks on Cisco ACI fabrics. +- A node block is a range of nodes. Each node block begins with the first port and ends with the last port. +options: + switch_profile: + description: + - The name of the Fabric access policy leaf/spine switch profile. + type: str + aliases: + - leaf_profile_name + - leaf_profile + - switch_profile_name + - spine_switch_profile + - spine_switch_profile_name + access_port_selector: + description: + - The name of the Fabric access policy leaf/spine switch port selector. + type: str + aliases: [ access_port_selector_name, port_selector, port_selector_name ] + node_block: + description: + - The name of the Node Block. + type: str + aliases: [ node_block_name, name ] + description: + description: + - The description for the Node Block. + type: str + aliases: [ node_block_description ] + from_port: + description: + - The beginning of the port range block for the Node Block. + type: str + aliases: [ from, from_port_range ] + to_port: + description: + - The end of the port range block for the Node Block. + type: str + aliases: [ to, to_port_range ] + type_node: + description: + - The type of Node Block to be created under respective access port. + type: str + choices: [ leaf, spine ] + 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- If Adding a port block on an access leaf switch port selector of I(type) C(leaf), + The I(switch_profile) and I(access_port_selector) must exist before using this module in your playbook. + The M(cisco.aci.aci_switch_policy_leaf_profile) and M(cisco.aci.aci_switch_leaf_selector) modules can be used for this. +- If Adding a port block on an access switch port selector of C(type) C(spine), + The I(switch_profile) and I(access_port_selector) must exist before using this module in your playbook. + The M(cisco.aci.aci_access_spine_switch_profile) and M(cisco.aci.aci_access_spine_switch_selector) modules can be used for this. +seealso: +- module: cisco.aci.aci_switch_policy_leaf_profile +- module: cisco.aci.aci_switch_leaf_selector +- module: cisco.aci.aci_access_spine_switch_profile +- module: cisco.aci.aci_access_spine_switch_selector +- name: APIC Management Information Model reference + description: More information about the internal APIC classes B(infra:NodeBlk). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new Node Block associated to a switch policy leaf profile selector + cisco.aci.aci_node_block: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: my_leaf_switch_profile + access_port_selector: my_leaf_switch_selector + node_block: my_node_block + from_port: 1011 + to_port: 1011 + type_node: leaf + state: present + delegate_to: localhost + +- name: Add a new Node Block associated to a switch policy spine profile selector + cisco.aci.aci_node_block: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: my_spine_switch_profile + access_port_selector: my_spine_switch_selector + node_block: my_node_block + from_port: 1012 + to_port: 1012 + type_node: spine + state: present + delegate_to: localhost + +- name: Query a Node Block associated to a switch policy leaf profile selector + cisco.aci.aci_node_block: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: my_leaf_switch_profile + access_port_selector: my_leaf_switch_selector + node_block: my_node_block + state: query + delegate_to: localhost + register: query_result + +- name: Query all Node Blocks under the switch policy leaf profile selector + cisco.aci.aci_node_block: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: my_leaf_switch_profile + access_port_selector: my_leaf_switch_selector + state: query + delegate_to: localhost + register: query_result + +- name: Query all Node Blocks + cisco.aci.aci_node_block: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove a Node Block associated to a switch policy leaf profile selector + cisco.aci.aci_node_block: + host: apic + username: admin + password: SomeSecretPassword + switch_profile: my_leaf_switch_profile + access_port_selector: my_leaf_switch_selector + node_block: my_node_block + type_node: leaf + state: absent + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '<?xml version="1.0" encoding="UTF-8"?><imdata totalCount="1"><error code="122" text="unknown managed object class foo"/></imdata>' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + switch_profile=dict( + type="str", + aliases=[ + "leaf_profile_name", + "leaf_profile", + "switch_profile_name", + "spine_switch_profile", + "spine_switch_profile_name", + ], + ), # Not required for querying all objects + access_port_selector=dict( + type="str", + aliases=[ + "access_port_selector_name", + "port_selector", + "port_selector_name", + ], + ), # Not required for querying all objects + node_block=dict(type="str", aliases=["node_block_name", "name"]), # Not required for querying all objects + description=dict(type="str", aliases=["node_block_description"]), + from_port=dict(type="str", aliases=["from", "from_port_range"]), + to_port=dict(type="str", aliases=["to", "to_port_range"]), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + type_node=dict(type="str", choices=["leaf", "spine"], aliases=["type"]), # Not required for querying all objects + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["switch_profile", "access_port_selector", "node_block", "type_node"]], + ["state", "present", ["switch_profile", "access_port_selector", "node_block", "type_node"]], + ], + ) + + switch_profile = module.params.get("switch_profile") + access_port_selector = module.params.get("access_port_selector") + node_block = module.params.get("node_block") + description = module.params.get("description") + from_port = module.params.get("from_port") + to_port = module.params.get("to_port") + state = module.params.get("state") + type_node = module.params.get("type_node") + + aci = ACIModule(module) + + if type_node == "spine": + subclass_1 = dict( + aci_class="infraSpineP", + aci_rn="spprof-{0}".format(switch_profile), + module_object=switch_profile, + target_filter={"name": switch_profile}, + ) + subclass_2 = dict( + aci_class="infraSpineS", + aci_rn="spines-{0}-typ-range".format(access_port_selector), + module_object=access_port_selector, + target_filter={"name": access_port_selector}, + ) + else: + subclass_1 = dict( + aci_class="infraNodeP", + aci_rn="nprof-{0}".format(switch_profile), + module_object=switch_profile, + target_filter={"name": switch_profile}, + ) + subclass_2 = dict( + aci_class="infraLeafS", + aci_rn="leaves-{0}-typ-range".format(access_port_selector), + module_object=access_port_selector, + target_filter={"name": access_port_selector}, + ) + aci.construct_url( + root_class=dict( + aci_class="infraInfra", + aci_rn="infra", + ), + subclass_1=subclass_1, + subclass_2=subclass_2, + subclass_3=dict( + aci_class="infraNodeBlk", + aci_rn="nodeblk-{0}".format(node_block), + module_object=node_block, + target_filter={"name": node_block}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="infraNodeBlk", + class_config=dict( + descr=description, + name=node_block, + from_=from_port, + to_=to_port, + ), + ) + + aci.get_diff(aci_class="infraNodeBlk") + + 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 index be8fe1838..161ce36f3 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_node_mgmt_epg.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_node_mgmt_epg.py @@ -11,7 +11,7 @@ __metaclass__ = type DOCUMENTATION = r""" --- module: aci_node_mgmt_epg -short_description: In band or Out of band management EPGs +short_description: In band or Out of band management EPGs (mgmt:OoB and mgmt:InB) description: - Cisco ACI Fabric Node EPGs options: @@ -45,6 +45,10 @@ 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(mgmt:OoB) and B(mgmt:InB). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Shreyas Srish (@shrsr) """ diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_ntp_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_ntp_policy.py index 7fc8abde3..1adb35cc3 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_ntp_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_ntp_policy.py @@ -17,9 +17,9 @@ ANSIBLE_METADATA = { DOCUMENTATION = r""" --- module: aci_ntp_policy -short_description: Manage NTP policies. +short_description: Manage NTP policies (datetime:Pol) description: -- Manage NTP policy (datetimePol) configuration on Cisco ACI fabrics. +- Manage NTP policy configuration on Cisco ACI fabrics. options: name: description: @@ -64,7 +64,7 @@ extends_documentation_fragment: seealso: - name: APIC Management Information Model reference - description: More information about the internal APIC class B(datetimePol). + description: More information about the internal APIC class B(datetime:Pol). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_ntp_server.py b/ansible_collections/cisco/aci/plugins/modules/aci_ntp_server.py index 3e40b652a..e8a94a03b 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_ntp_server.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_ntp_server.py @@ -17,7 +17,7 @@ ANSIBLE_METADATA = { DOCUMENTATION = r""" --- module: aci_ntp_server -short_description: Manage NTP servers. +short_description: Manage NTP servers (datetime:NtpProv) description: - Manage NTP server (datetimeNtpProv) configuration on Cisco ACI fabrics. options: @@ -74,7 +74,7 @@ notes: 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). + description: More information about the internal APIC class B(datetime:NtpProv). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_pim_route_map_entry.py b/ansible_collections/cisco/aci/plugins/modules/aci_pim_route_map_entry.py new file mode 100644 index 000000000..79ad849a7 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_pim_route_map_entry.py @@ -0,0 +1,329 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_pim_route_map_entry +short_description: Manage Protocol-Independent Multicast (PIM) Route Map Entry (pim:RouteMapEntry) +description: +- Manage PIM Route Map Entries for the PIM route Map Policies on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + pim_route_map_policy: + description: + - The name of the PIM Route Map policy. + type: str + aliases: [ route_map_policy_name ] + order: + description: + - The PIM Route Map Entry order. + type: int + source_ip: + description: + - The Multicast Source IP. + type: str + group_ip: + description: + - The Multicast Group IP. + type: str + rp_ip: + description: + - The Multicast Rendezvous Point (RP) IP. + type: str + aliases: [ rendezvous_point_ip ] + action: + description: + - The route action. + - The APIC defaults to C(permit) when unset during creation. + type: str + choices: [ permit, deny ] + description: + description: + - The description for the PIM Route Map entry. + 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(tenant) and the C(pim_route_map_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_pim_route_map_policy) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_pim_route_map_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(pim:RouteMapEntry). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new PIM Route Map Entry + cisco.aci.aci_pim_route_map_entry: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + pim_route_map_policy: my_pim_route_map_policy + order: 1 + source_ip: 1.1.1.1/24 + group_ip: 224.0.0.1/24 + rp_ip: 1.1.1.2 + action: permit + state: present + delegate_to: localhost + +- name: Query a PIM Route Map Entry + cisco.aci.aci_pim_route_map_entry: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + pim_route_map_policy: my_pim_route_map_policy + order: 1 + state: query + delegate_to: localhost + register: query_result + +- name: Query all PIM Route Map Entries in my_pim_route_map_policy + cisco.aci.aci_pim_route_map_entry: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + pim_route_map_policy: my_pim_route_map_policy + state: query + delegate_to: localhost + register: query_result + +- name: Delete a PIM Route Map Entry + cisco.aci.aci_pim_route_map_entry: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + pim_route_map_policy: my_pim_route_map_policy + order: 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 + + +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"]), + pim_route_map_policy=dict(type="str", aliases=["route_map_policy_name"]), + description=dict(type="str", aliases=["descr"]), + order=dict(type="int"), + source_ip=dict(type="str"), + group_ip=dict(type="str"), + rp_ip=dict(type="str", aliases=["rendezvous_point_ip"]), + action=dict(type="str", choices=["permit", "deny"]), + 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", "pim_route_map_policy", "order"]], + ["state", "present", ["tenant", "pim_route_map_policy", "order"]], + ], + ) + + tenant = module.params.get("tenant") + description = module.params.get("description") + pim_route_map_policy = module.params.get("pim_route_map_policy") + order = module.params.get("order") + source_ip = module.params.get("source_ip") + group_ip = module.params.get("group_ip") + rp_ip = module.params.get("rp_ip") + action = module.params.get("action") + 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="pimRouteMapPol", + aci_rn="rtmap-{0}".format(pim_route_map_policy), + module_object=pim_route_map_policy, + target_filter={"name": pim_route_map_policy}, + ), + subclass_2=dict( + aci_class="pimRouteMapEntry", + aci_rn="rtmapentry-{0}".format(order), + module_object=order, + target_filter={"order": order}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="pimRouteMapEntry", + class_config=dict( + name=pim_route_map_policy, + descr=description, + action=action, + grp=group_ip, + order=order, + rp=rp_ip, + src=source_ip, + ), + ) + + aci.get_diff(aci_class="pimRouteMapEntry") + + 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_pim_route_map_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_pim_route_map_policy.py new file mode 100644 index 000000000..06c7c7b29 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_pim_route_map_policy.py @@ -0,0 +1,276 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_pim_route_map_policy +short_description: Manage Protocol-Independent Multicast (PIM) Route Map Policy (pim:RouteMapPol) +description: +- Manage PIM Route Map Policies for tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + pim_route_map_policy: + description: + - The name of the PIM Route Map policy. + type: str + aliases: [ route_map_policy_name, name ] + description: + description: + - The description for the PIM Route Map 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 +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) 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(pim:RouteMapPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new PIM Route Map policy + cisco.aci.aci_pim_route_map_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + pim_route_map_policy: my_pim_route_map_policy + state: present + delegate_to: localhost + +- name: Query a PIM Route Map policy + cisco.aci.aci_pim_route_map_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + pim_route_map_policy: my_pim_route_map_policy + state: query + delegate_to: localhost + register: query_result + +- name: Query all PIM Route Map policies in my_tenant + cisco.aci.aci_pim_route_map_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + state: query + delegate_to: localhost + register: query_result + +- name: Delete a PIM Route Map policy + cisco.aci.aci_pim_route_map_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + pim_route_map_policy: my_pim_route_map_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( + tenant=dict(type="str", aliases=["tenant_name"]), + pim_route_map_policy=dict(type="str", aliases=["route_map_policy_name", "name"]), + 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", ["tenant", "pim_route_map_policy"]], + ["state", "present", ["tenant", "pim_route_map_policy"]], + ], + ) + + tenant = module.params.get("tenant") + description = module.params.get("description") + pim_route_map_policy = module.params.get("pim_route_map_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="pimRouteMapPol", + aci_rn="rtmap-{0}".format(pim_route_map_policy), + module_object=pim_route_map_policy, + target_filter={"name": pim_route_map_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="pimRouteMapPol", + class_config=dict( + name=pim_route_map_policy, + descr=description, + ), + ) + + aci.get_diff(aci_class="pimRouteMapPol") + + 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_qos_custom_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_qos_custom_policy.py new file mode 100644 index 000000000..c5200b0f7 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_qos_custom_policy.py @@ -0,0 +1,284 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_qos_custom_policy +short_description: Manage QoS Custom Policy (qos:CustomPol) +description: +- Manage QoS Custom Policies for tenants on Cisco ACI fabrics. +- The custom QoS policy enables different levels of service to be assigned to network traffic, + including specifications for the Differentiated Services Code Point (DSCP) value(s), and the 802.1p Dot1p priority. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + qos_custom_policy: + description: + - The name of the QoS Custom Policy. + type: str + aliases: [ qos_custom_policy_name, name ] + description: + description: + - The description for the QoS Custom 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 +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The I(tenant) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) 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(qos:CustomPol). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new QoS Custom Policy + cisco.aci.aci_qos_custom_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + state: present + delegate_to: localhost + +- name: Query a QoS Custom Policy + cisco.aci.aci_qos_custom_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + state: query + delegate_to: localhost + +- name: Query all QoS Custom Policies in my_tenant + cisco.aci.aci_qos_custom_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + state: query + delegate_to: localhost + +- name: Query all QoS Custom Policies + cisco.aci.aci_qos_custom_policy: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete a QoS Custom Policy + cisco.aci.aci_qos_custom_policy: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_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( + tenant=dict(type="str", aliases=["tenant_name"]), + qos_custom_policy=dict(type="str", aliases=["qos_custom_policy_name", "name"]), + 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", ["tenant", "qos_custom_policy"]], + ["state", "present", ["tenant", "qos_custom_policy"]], + ], + ) + + tenant = module.params.get("tenant") + description = module.params.get("description") + qos_custom_policy = module.params.get("qos_custom_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="qosCustomPol", + aci_rn="qoscustom-{0}".format(qos_custom_policy), + module_object=qos_custom_policy, + target_filter={"name": qos_custom_policy}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="qosCustomPol", + class_config=dict( + name=qos_custom_policy, + descr=description, + ), + ) + + aci.get_diff(aci_class="qosCustomPol") + + 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_qos_dot1p_class.py b/ansible_collections/cisco/aci/plugins/modules/aci_qos_dot1p_class.py new file mode 100644 index 000000000..f6939af20 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_qos_dot1p_class.py @@ -0,0 +1,355 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_qos_dot1p_class +short_description: Manage QoS Dot1P Class (qos:Dot1PClass) +description: +- Manage Dot1P Class levels for QoS Custom Policies on Cisco ACI fabrics. +- The class level for Dot1P to prioritize the map. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + qos_custom_policy: + description: + - The name of the QoS Custom Policy. + type: str + aliases: [ qos_custom_policy_name ] + priority: + description: + - The desired QoS class level to be used. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ level1, level2, level3, level4, level5, level6, unspecified ] + aliases: [ prio ] + dot1p_from: + description: + - The Dot1P range starting value. + type: str + choices: [ background, best_effort, excellent_effort, critical_applications, video, voice, internetwork_control, network_control, unspecified ] + dot1p_to: + description: + - The Dot1P range ending value. + type: str + choices: [ background, best_effort, excellent_effort, critical_applications, video, voice, internetwork_control, network_control, unspecified ] + dot1p_target: + description: + - The Dot1P target 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 ] + target_cos: + description: + - The target COS to be driven based on the range of input values of Dot1P coming into the fabric. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ background, best_effort, excellent_effort, critical_applications, video, voice, internetwork_control, network_control, 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 +- cisco.aci.owner + +notes: +- The I(tenant) and I(qos_custom_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_qos_custom_policy) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_qos_custom_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(qos:Dot1PClass). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new QoS dot1P Class + cisco.aci.aci_qos_dot1p_class: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + priority: level3 + dot1p_from: best_effort + dot1p_to: excellent_effort + dot1p_target: unspecified + target_cos: unspecified + state: present + delegate_to: localhost + +- name: Query a QoS dot1P Class + cisco.aci.aci_qos_dot1p_class: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + dot1p_from: best_effort + dot1p_to: excellent_effort + state: query + delegate_to: localhost + +- name: Query all QoS dot1P Classes in my_qos_custom_policy + cisco.aci.aci_qos_dot1p_class: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + state: query + delegate_to: localhost + +- name: Query all QoS dot1P Classes + cisco.aci.aci_qos_dot1p_class: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete a QoS dot1P Class + cisco.aci.aci_qos_dot1p_class: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + dot1p_from: best_effort + dot1p_to: excellent_effort + 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_contract_dscp_spec, +) +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_TARGET_COS_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + qos_custom_policy=dict(type="str", aliases=["qos_custom_policy_name"]), + priority=dict( + type="str", + choices=[ + "level1", + "level2", + "level3", + "level4", + "level5", + "level6", + "unspecified", + ], + aliases=["prio"], + ), + dot1p_from=dict(type="str", choices=list(MATCH_TARGET_COS_MAPPING.keys())), + dot1p_to=dict(type="str", choices=list(MATCH_TARGET_COS_MAPPING.keys())), + dot1p_target=aci_contract_dscp_spec(), + target_cos=dict(type="str", choices=list(MATCH_TARGET_COS_MAPPING.keys())), + 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", "qos_custom_policy", "dot1p_from", "dot1p_to"]], + ["state", "present", ["tenant", "qos_custom_policy", "dot1p_from", "dot1p_to"]], + ], + ) + + tenant = module.params.get("tenant") + qos_custom_policy = module.params.get("qos_custom_policy") + priority = module.params.get("priority") + dot1p_from = MATCH_TARGET_COS_MAPPING.get(module.params.get("dot1p_from")) + dot1p_to = MATCH_TARGET_COS_MAPPING.get(module.params.get("dot1p_to")) + dot1p_target = module.params.get("dot1p_target") + target_cos = MATCH_TARGET_COS_MAPPING.get(module.params.get("target_cos")) + 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="qosCustomPol", + aci_rn="qoscustom-{0}".format(qos_custom_policy), + module_object=qos_custom_policy, + target_filter={"name": qos_custom_policy}, + ), + subclass_2=dict( + aci_class="qosDot1PClass", + aci_rn="dot1P-{0}-{1}".format(dot1p_from, dot1p_to), + module_object=qos_custom_policy, + target_filter={"from": dot1p_from, "to": dot1p_to}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="qosDot1PClass", + class_config={ + "prio": priority, + "from": dot1p_from, + "to": dot1p_to, + "target": dot1p_target, + "targetCos": target_cos, + }, + ) + + aci.get_diff(aci_class="qosDot1PClass") + + 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_qos_dscp_class.py b/ansible_collections/cisco/aci/plugins/modules/aci_qos_dscp_class.py new file mode 100644 index 000000000..18219d0c0 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_qos_dscp_class.py @@ -0,0 +1,356 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_qos_dscp_class +short_description: Manage QoS DSCP Class (qos:DscpClass) +description: +- Manage QoS Custom Differentiated Services Code Point (DSCP) Class levels for QoS Custom Policies on Cisco ACI fabrics. +- The class level for DSCP to prioritize the map. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + qos_custom_policy: + description: + - The name of the QoS Custom Policy. + type: str + aliases: [ qos_custom_policy_name ] + priority: + description: + - The desired QoS class level to be used. + - The APIC defaults to C(level3) when unset during creation. + type: str + choices: [ level1, level2, level3, level4, level5, level6, unspecified ] + aliases: [ prio ] + dscp_from: + description: + - The DSCP range starting value. + 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 ] + dscp_to: + description: + - The DSCP range ending value. + 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 ] + dscp_target: + description: + - The DSCP target 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 ] + target_cos: + description: + - The target COS to be driven based on the range of input values of DSCP coming into the fabric. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ background, best_effort, excellent_effort, critical_applications, video, voice, internetwork_control, network_control, 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 +- cisco.aci.owner + +notes: +- The I(tenant) and I(qos_custom_policy) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_qos_custom_policy) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_qos_custom_policy +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(qos:DscpClass). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Add a new QoS DSCP Class + cisco.aci.aci_qos_dscp_class: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + priority: level3 + dscp_from: AF11 + dscp_to: AF21 + dscp_target: unspecified + target_cos: best_effort + state: present + delegate_to: localhost + +- name: Query a QoS DSCP Class + cisco.aci.aci_qos_dscp_class: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + dscp_from: AF11 + dscp_to: AF21 + state: query + delegate_to: localhost + +- name: Query all QoS DSCP Classes in my_qos_custom_policy + cisco.aci.aci_qos_dscp_class: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + state: query + delegate_to: localhost + +- name: Query all QoS DSCP Classes + cisco.aci.aci_qos_dscp_class: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + +- name: Delete a QoS DSCP Class + cisco.aci.aci_qos_dscp_class: + host: apic + username: admin + password: SomeSecretPassword + tenant: my_tenant + qos_custom_policy: my_qos_custom_policy + dscp_from: AF11 + dscp_to: AF21 + 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_contract_dscp_spec, +) +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_TARGET_COS_MAPPING + + +def main(): + new_dscp_spec = dict((k, aci_contract_dscp_spec()[k]) for k in aci_contract_dscp_spec() if k != "aliases") + 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"]), + qos_custom_policy=dict(type="str", aliases=["qos_custom_policy_name"]), + priority=dict( + type="str", + choices=[ + "level1", + "level2", + "level3", + "level4", + "level5", + "level6", + "unspecified", + ], + aliases=["prio"], + ), + dscp_from=new_dscp_spec, + dscp_to=new_dscp_spec, + dscp_target=aci_contract_dscp_spec(), + target_cos=dict(type="str", choices=list(MATCH_TARGET_COS_MAPPING.keys())), + 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", "qos_custom_policy", "dscp_from", "dscp_to"]], + ["state", "present", ["tenant", "qos_custom_policy", "dscp_from", "dscp_to"]], + ], + ) + + tenant = module.params.get("tenant") + qos_custom_policy = module.params.get("qos_custom_policy") + priority = module.params.get("priority") + dscp_from = module.params.get("dscp_from") + dscp_to = module.params.get("dscp_to") + dscp_target = module.params.get("dscp_target") + target_cos = MATCH_TARGET_COS_MAPPING.get(module.params.get("target_cos")) + 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="qosCustomPol", + aci_rn="qoscustom-{0}".format(qos_custom_policy), + module_object=qos_custom_policy, + target_filter={"name": qos_custom_policy}, + ), + subclass_2=dict( + aci_class="qosDscpClass", + aci_rn="dcsp-{0}-{1}".format(dscp_from, dscp_to), + module_object=qos_custom_policy, + target_filter={"from": dscp_from, "to": dscp_to}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="qosDscpClass", + class_config={ + "prio": priority, + "from": dscp_from, + "to": dscp_to, + "target": dscp_target, + "targetCos": target_cos, + }, + ) + + aci.get_diff(aci_class="qosDscpClass") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_rest.py b/ansible_collections/cisco/aci/plugins/modules/aci_rest.py index ad73cb5bd..43986592a 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_rest.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_rest.py @@ -3,6 +3,7 @@ # Copyright: (c) 2017, Dag Wieers (@dagwieers) <dag@wieers.com> # Copyright: (c) 2020, Cindy Zhao (@cizhao) <cizhao@cisco.com> +# Copyright: (c) 2023, Samita Bhattacharjee (@samitab) <samitab@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 @@ -62,6 +63,7 @@ options: default: false extends_documentation_fragment: - cisco.aci.aci +- cisco.aci.annotation notes: - Certain payloads are known not to be idempotent, so be careful when constructing payloads, @@ -73,6 +75,7 @@ notes: - 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. +- Annotation set directly in c(src) or C(content) will take precedent over the C(annotation) parameter. seealso: - module: cisco.aci.aci_tenant - name: Cisco APIC REST API Configuration Guide @@ -81,6 +84,7 @@ seealso: author: - Dag Wieers (@dagwieers) - Cindy Zhao (@cizhao) +- Samita Bhattacharjee (@samitab) """ EXAMPLES = r""" @@ -284,8 +288,11 @@ 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_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec from ansible.module_utils._text import to_text +from ansible_collections.cisco.aci.plugins.module_utils.annotation_unsupported import ( + ANNOTATION_UNSUPPORTED, +) def update_qsl(url, params): @@ -303,6 +310,33 @@ def update_qsl(url, params): return url + "?" + "&".join(["%s=%s" % (k, v) for k, v in params.items()]) +def add_annotation(annotation, payload): + """Add annotation to payload only if it has not already been added""" + if annotation and isinstance(payload, dict): + for key, val in payload.items(): + if key in ANNOTATION_UNSUPPORTED: + continue + att = val.get("attributes", {}) + if "annotation" not in att.keys(): + att["annotation"] = annotation + # Recursively add annotation to children + children = val.get("children", None) + if children: + for child in children: + add_annotation(annotation, child) + + +def add_annotation_xml(annotation, tree): + """Add annotation to payload xml only if it has not already been added""" + if annotation: + for element in tree.iter(): + if element.tag in ANNOTATION_UNSUPPORTED: + continue + ann = element.get("annotation") + if ann is None: + element.set("annotation", annotation) + + class ACIRESTModule(ACIModule): def changed(self, d): """Check ACI response for changes""" @@ -335,6 +369,7 @@ class ACIRESTModule(ACIModule): def main(): argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) argument_spec.update( path=dict(type="str", required=True, aliases=["uri"]), method=dict(type="str", default="get", choices=["delete", "get", "post"], aliases=["action"]), @@ -353,6 +388,7 @@ def main(): path = module.params.get("path") src = module.params.get("src") rsp_subtree_preserve = module.params.get("rsp_subtree_preserve") + annotation = module.params.get("annotation") # Report missing file file_exists = False @@ -388,21 +424,27 @@ def main(): if rest_type == "json": if content and isinstance(content, dict): # Validate inline YAML/JSON + add_annotation(annotation, payload) 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)) + payload = yaml.safe_load(payload) + add_annotation(annotation, payload) + payload = json.dumps(payload) except Exception as e: module.fail_json(msg="Failed to parse provided JSON/YAML payload: {0}".format(to_text(e)), exception=to_text(e), payload=payload) elif rest_type == "xml" and HAS_LXML_ETREE: if content and isinstance(content, dict) and HAS_XMLJSON_COBRA: # Validate inline YAML/JSON + add_annotation(annotation, payload) 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") + payload = etree.fromstring(payload) + add_annotation_xml(annotation, payload) + payload = etree.tostring(payload, encoding="unicode") except Exception as e: module.fail_json(msg="Failed to parse provided XML payload: {0}".format(to_text(e)), payload=payload) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client.py index a7c01dc8e..545167377 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_snmp_client -short_description: Manage SNMP clients (snmp:ClientP). +short_description: Manage SNMP clients (snmp:ClientP) description: - Manage SNMP clients options: 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 index 7f7b12504..b18d4aecc 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_client_group.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_snmp_client_group -short_description: Manage SNMP client groups (snmp:ClientGrpP). +short_description: Manage SNMP client groups (snmp:ClientGrpP) description: - Manage SNMP client groups options: 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 index 446962165..1494a6bbe 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_community_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_community_policy.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_snmp_community_policy -short_description: Manage SNMP community policies (snmp:CommunityP). +short_description: Manage SNMP community policies (snmp:CommunityP) description: - Manage SNMP community policies options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_policy.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_policy.py index c3956db2f..3295453a7 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_policy.py @@ -12,9 +12,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_snmp_policy -short_description: Manage Syslog groups (snmp:Pol). +short_description: Manage Syslog groups (snmp:Pol) description: -- Manage syslog policies +- Manage syslog policies. options: admin_state: description: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py index 9e9b6d852..23e7c6f3e 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_snmp_user.py @@ -14,9 +14,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_snmp_user -short_description: Manage SNMP v3 Users (snmp:UserP). +short_description: Manage SNMP v3 Users (snmp:UserP) description: -- Manage SNMP v3 Users +- 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: 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 index 62602487e..bc99969be 100644 --- 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 @@ -13,7 +13,7 @@ __metaclass__ = type DOCUMENTATION = r""" --- module: aci_static_node_mgmt_address -short_description: In band or Out of band management IP address +short_description: In band or Out of band management IP address (mgmt:RsOoBStNode and mgmt:RsInBStNode) description: - Cisco ACI Fabric Node IP address options: @@ -65,6 +65,10 @@ 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(mgmt:RsOoBStNode) and B(mgmt:RsInBStNode). + link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Sudhakar Shet Kudtarkar (@kudtarkar1) - Lionel Hercot (@lhercot) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_subject_label.py b/ansible_collections/cisco/aci/plugins/modules/aci_subject_label.py new file mode 100644 index 000000000..a68c39d8f --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_subject_label.py @@ -0,0 +1,746 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Mark Ciecior (@markciecior) +# Copyright: (c) 2024, 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_subject_label +short_description: Manage Subject Labels (vz:ConsSubjLbl and vz:ProvSubjLbl) +description: +- Manage Subject Labels on Cisco ACI fabrics. +options: + tenant: + description: + - The name of the Tenant. + type: str + aliases: [ tenant_name ] + l2out: + description: + - The name of the L2Out. + type: str + aliases: [ l2out_name ] + l3out: + description: + - The name of the L3Out. + type: str + aliases: [ l3out_name ] + external_epg: + description: + - The name of the External End Point Group. + type: str + aliases: [ extepg, extepg_name, external_epg_name ] + contract: + description: + - The name of the Contract. + type: str + aliases: [ contract_name ] + subject: + description: + - The name of the Subject. + type: str + aliases: [ subject_name ] + ap: + description: + - The name of the Application Profile. + type: str + aliases: [ app_profile, app_profile_name, application_profile, application_profile_name] + epg: + description: + - The name of the End Point Group. + type: str + aliases: [ epg_name ] + esg: + description: + - The name of the Endpoint Security Group. + type: str + aliases: [ esg_name ] + subject_label: + description: + - The name of the Subject Label. + type: str + aliases: [ subject_label_name, name, label ] + subject_label_type: + description: + - Determines the type of the Subject Label. + type: str + required: true + choices: [ consumer, provider ] + aliases: [ type ] + complement: + description: + - Whether complement is enabled on the Subject Label. + - The APIC defaults to C(false) when unset during creation. + type: bool + tag: + description: + - The color of a policy label of the Subject Label. + - The APIC defaults to C(yellow-green) when unset during creation. + type: str + choices: + - alice_blue + - antique_white + - aqua + - aquamarine + - azure + - beige + - bisque + - black + - blanched_almond + - blue + - blue_violet + - brown + - burlywood + - cadet_blue + - chartreuse + - chocolate + - coral + - cornflower_blue + - cornsilk + - crimson + - cyan + - dark_blue + - dark_cyan + - dark_goldenrod + - dark_gray + - dark_green + - dark_khaki + - dark_magenta + - dark_olive_green + - dark_orange + - dark_orchid + - dark_red + - dark_salmon + - dark_sea_green + - dark_slate_blue + - dark_slate_gray + - dark_turquoise + - dark_violet + - deep_pink + - deep_sky_blue + - dim_gray + - dodger_blue + - fire_brick + - floral_white + - forest_green + - fuchsia + - gainsboro + - ghost_white + - gold + - goldenrod + - gray + - green + - green_yellow + - honeydew + - hot_pink + - indian_red + - indigo + - ivory + - khaki + - lavender + - lavender_blush + - lawn_green + - lemon_chiffon + - light_blue + - light_coral + - light_cyan + - light_goldenrod_yellow + - light_gray + - light_green + - light_pink + - light_salmon + - light_sea_green + - light_sky_blue + - light_slate_gray + - light_steel_blue + - light_yellow + - lime + - lime_green + - linen + - magenta + - maroon + - medium_aquamarine + - medium_blue + - medium_orchid + - medium_purple + - medium_sea_green + - medium_slate_blue + - medium_spring_green + - medium_turquoise + - medium_violet_red + - midnight_blue + - mint_cream + - misty_rose + - moccasin + - navajo_white + - navy + - old_lace + - olive + - olive_drab + - orange + - orange_red + - orchid + - pale_goldenrod + - pale_green + - pale_turquoise + - pale_violet_red + - papaya_whip + - peachpuff + - peru + - pink + - plum + - powder_blue + - purple + - red + - rosy_brown + - royal_blue + - saddle_brown + - salmon + - sandy_brown + - sea_green + - seashell + - sienna + - silver + - sky_blue + - slate_blue + - slate_gray + - snow + - spring_green + - steel_blue + - tan + - teal + - thistle + - tomato + - turquoise + - violet + - wheat + - white + - white_smoke + - yellow + - yellow_green + description: + description: + - The description for the Subject Label. + 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: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(vz:ConsSubjLbl) and (vz:ProvSubjLbl). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Mark Ciecior (@markciecior) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a Subject Label on a Contract Subject + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web + subject: web_subject + subject_label: web_subject_label + subject_type: consumer + state: present + delegate_to: localhost + +- name: Add a Subject Label on a L2Out External EPG + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l2out: l2out_name + external_epg: external_epg_name + subject_label: l2out_subject_label + subject_type: consumer + state: present + delegate_to: localhost + +- name: Add a Subject Label on a L3Out External EPG + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: l3out_name + external_epg: external_epg_name + subject_label: l3out_subject_label + subject_type: consumer + state: present + delegate_to: localhost + +- name: Add a Subject Label on a L3Out External EPG Contract + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + l3out: l3out_name + external_epg: external_epg_name + contract: web + subject_label: l3out_subject_label + subject_type: consumer + state: present + delegate_to: localhost + +- name: Add a Subject Label on a ESG + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: app_profile_name + esg: esg_name + subject_label: esg_subject_label + subject_type: consumer + state: present + delegate_to: localhost + +- name: Add a Subject Label on a EPG + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: app_profile_name + epg: epg_name + subject_label: epg_subject_label + subject_type: consumer + state: present + delegate_to: localhost + +- name: Add a Subject Label on a EPG Contract + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + ap: app_profile_name + epg: epg_name + contract: web + subject_label: epg_subject_label + subject_type: consumer + state: present + delegate_to: localhost + +- name: Query a Subject Label on a Contract Subject + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web + subject: web_subject + subject_label: web_subject_label + subject_type: consumer + state: query + delegate_to: localhost + register: query_result + +- name: Query all Subject Labels + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Remove a Subject Label on a Contract Subject + cisco.aci.aci_subject_label: + host: apic + username: admin + password: SomeSecretPassword + tenant: production + contract: web + subject: web_subject + subject_label: web_subject_label + subject_type: consumer + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import ACI_CLASS_MAPPING, SUBJ_LABEL_MAPPING, SUBJ_LABEL_RN, POLICY_LABEL_COLORS + + +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"]), + l2out=dict(type="str", aliases=["l2out_name"]), + l3out=dict(type="str", aliases=["l3out_name"]), + external_epg=dict(type="str", aliases=["extepg", "extepg_name", "external_epg_name"]), + contract=dict(type="str", aliases=["contract_name"]), + subject=dict(type="str", aliases=["subject_name"]), + ap=dict(type="str", aliases=["app_profile", "app_profile_name", "application_profile", "application_profile_name"]), + epg=dict(type="str", aliases=["epg_name"]), + esg=dict(type="str", aliases=["esg_name"]), + complement=dict(type="bool"), + description=dict(type="str", aliases=["descr"]), + subject_label=dict(type="str", aliases=["subject_label_name", "name", "label"]), + subject_label_type=dict(type="str", choices=["consumer", "provider"], aliases=["type"], required=True), + tag=dict(type="str", choices=POLICY_LABEL_COLORS), + 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", ["tenant", "subject_label"]], + ["state", "present", ["l2out", "l3out", "epg", "esg", "subject"], True], + ["state", "absent", ["tenant", "subject_label"]], + ["state", "absent", ["l2out", "l3out", "epg", "esg", "subject"], True], + ], + mutually_exclusive=[ + ["l2out", "l3out", "epg", "esg", "subject"], + ["esg", "contract"], + ["l2out", "contract"], + ], + required_by={ + "subject": ["contract"], + "l2out": ["external_epg"], + "l3out": ["external_epg"], + "epg": ["ap"], + "esg": ["ap"], + }, + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + l2out = module.params.get("l2out") + l3out = module.params.get("l3out") + external_epg = module.params.get("external_epg") + contract = module.params.get("contract") + subject_label_type = module.params.get("subject_label_type") + subject = module.params.get("subject") + ap = module.params.get("ap") + epg = module.params.get("epg") + esg = module.params.get("esg") + complement = aci.boolean(module.params.get("complement")) + description = module.params.get("description") + subject_label = module.params.get("subject_label") + tag = module.params.get("tag") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci_class = SUBJ_LABEL_MAPPING.get(subject_label_type) + aci_rn = SUBJ_LABEL_RN.get(subject_label_type) + subject_label if subject_label else None + + if contract: + contract_rn = ACI_CLASS_MAPPING.get(subject_label_type).get("rn") + contract + contract_class = ACI_CLASS_MAPPING.get(subject_label_type).get("class") + + root_class = dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ) + subclass_1 = None + subclass_2 = None + subclass_3 = None + subclass_4 = None + if esg: + 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=aci_class, + aci_rn=aci_rn, + module_object=subject_label, + target_filter={"name": subject_label}, + ) + elif l2out: + 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(external_epg), + module_object=external_epg, + target_filter={"name": external_epg}, + ) + subclass_3 = dict( + aci_class=aci_class, + aci_rn=aci_rn, + module_object=subject_label, + target_filter={"name": subject_label}, + ) + elif epg: + 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}, + ) + if contract: + subclass_3 = dict( + aci_class=contract_class, + aci_rn=contract_rn, + module_object=contract, + target_filter={"name": contract}, + ) + subclass_4 = dict( + aci_class=aci_class, + aci_rn=aci_rn, + module_object=subject_label, + target_filter={"name": subject_label}, + ) + else: + subclass_3 = dict( + aci_class=aci_class, + aci_rn=aci_rn, + module_object=subject_label, + target_filter={"name": subject_label}, + ) + elif l3out: + subclass_1 = 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(external_epg), + module_object=external_epg, + target_filter={"name": external_epg}, + ) + if contract: + subclass_3 = dict( + aci_class=contract_class, + aci_rn=contract_rn, + module_object=contract, + target_filter={"name": contract}, + ) + subclass_4 = dict( + aci_class=aci_class, + aci_rn=aci_rn, + module_object=subject_label, + target_filter={"name": subject_label}, + ) + else: + subclass_3 = dict( + aci_class=aci_class, + aci_rn=aci_rn, + module_object=subject_label, + target_filter={"name": subject_label}, + ) + elif subject: + 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=aci_class, + aci_rn=aci_rn, + module_object=subject_label, + target_filter={"name": subject_label}, + ) + else: # Query scenario without any filters forcing class query on the subject_label_class + root_class = dict( + aci_class=aci_class, + aci_rn=aci_rn, + module_object=subject_label, + target_filter={"name": subject_label}, + ) + + aci.construct_url( + root_class=root_class, + subclass_1=subclass_1, + subclass_2=subclass_2, + subclass_3=subclass_3, + subclass_4=subclass_4, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class=aci_class, + class_config=dict( + name=subject_label, + descr=description, + nameAlias=name_alias, + isComplement=complement, + tag=tag.replace("_", "-") if tag else None, + ), + ) + + 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_switch_leaf_selector.py b/ansible_collections/cisco/aci/plugins/modules/aci_switch_leaf_selector.py index 2b850f1cd..bf081bd57 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_switch_leaf_selector.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_switch_leaf_selector.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2017, Bruno Calogero <brunocalogero@hotmail.com> +# Copyright: (c) 2024, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -13,7 +14,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_switch_leaf_selector -short_description: Bind leaf selectors to switch policy leaf profiles (infra:LeafS, infra:NodeBlk, infra:RsAccNodePGrep) +short_description: Bind leaf selectors to switch policy leaf profiles (infra:LeafS, infra:NodeBlk, and infra:RsAccNodePGrep) description: - Bind leaf selectors (with node block range and policy group) to switch policy leaf profiles on Cisco ACI fabrics. options: @@ -82,6 +83,7 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Bruno Calogero (@brunocalogero) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -266,7 +268,7 @@ def main(): 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"]]], + required_if=[["state", "absent", ["leaf_profile", "leaf"]], ["state", "present", ["leaf_profile", "leaf"]]], ) description = module.params.get("description") @@ -280,19 +282,21 @@ def main(): 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_, + child_configs = [] + # Add infraNodeBlk only when leaf_node_blk was defined + if leaf_node_blk is not None: + child_configs.append( + 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: 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 index a7994db73..507c84a17 100644 --- 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 @@ -13,7 +13,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_switch_policy_vpc_protection_group -short_description: Manage switch policy explicit vPC protection groups (fabric:ExplicitGEp, fabric:NodePEp). +short_description: Manage switch policy explicit vPC protection groups (fabric:ExplicitGEp and fabric:NodePEp). description: - Manage switch policy explicit vPC protection groups on Cisco ACI fabrics. options: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_group.py b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_group.py index bab3379a1..205c3593b 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_group.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_group.py @@ -12,9 +12,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_syslog_group -short_description: Manage Syslog groups (syslog:Group, syslog:Console, syslog:File and syslog:Prof). +short_description: Manage Syslog groups (syslog:Group, syslog:Console, syslog:File and syslog:Prof) description: -- Manage syslog groups +- Manage syslog groups. options: admin_state: description: @@ -72,7 +72,7 @@ extends_documentation_fragment: seealso: - name: APIC Management Information Model reference - description: More information about the internal APIC classes B(syslog:Group). + description: More information about the internal APIC classes B(syslog:Group), B(syslog:Console), B(syslog:File) and B(syslog:Prof). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Tim Cragg (@timcragg) 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 index b3c1a02b4..43d2e85eb 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_remote_dest.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_remote_dest.py @@ -12,10 +12,9 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_syslog_remote_dest -short_description: Manage Syslog Remote Destinations (syslog:RemoteDest). +short_description: Manage Syslog Remote Destinations (syslog:RemoteDest) description: -- Manage remote destinations for syslog messages within - an existing syslog group object +- Manage remote destinations for syslog messages within an existing syslog group object. options: admin_state: description: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py index b0bb61bb5..0e3bc6b78 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_syslog_source.py @@ -15,7 +15,7 @@ DOCUMENTATION = r""" module: aci_syslog_source short_description: Manage Syslog Source objects (syslog:Src) description: -- Manage Syslog Source objects +- Manage Syslog Source objects. options: name: description: diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_system.py b/ansible_collections/cisco/aci/plugins/modules/aci_system.py index 6a3349ec0..53e2b90e8 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_system.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_system.py @@ -13,7 +13,7 @@ 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. +- Query the ACI system information on Cisco ACI. author: - Lionel Hercot (@lhercot) options: @@ -30,10 +30,11 @@ options: 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. - +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(top:System). + link: https://developer.cisco.com/docs/apic-mim-ref/ extends_documentation_fragment: - cisco.aci.aci """ diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_tag.py b/ansible_collections/cisco/aci/plugins/modules/aci_tag.py index 9e56ae068..d005b462b 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_tag.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_tag.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_tag -short_description: Tagging of ACI objects +short_description: Tagging of ACI objects (tag:Annotation, tag:Inst, and tag:Tag) description: - Tagging a object on Cisco ACI fabric. options: @@ -50,7 +50,11 @@ notes: - 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: APIC Management Information Model reference + description: More information about the internal APIC classes B(tag:Annotation), B(tag:Inst), and B(tag:Tag). + link: https://developer.cisco.com/docs/apic-mim-ref/ - 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 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 index 55b76f3d6..f51d28fcd 100644 --- 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 @@ -1,6 +1,9 @@ #!/usr/bin/python # -*- coding: utf-8 -*- +# Copyright: (c) 2023, Dag Wieers (@dagwieers) +# Copyright: (c) 2023, Tim Cragg (@timcragg) <tcragg@cisco.com> +# Copyright: (c) 2023, Gaspard Micol (@gmicol) <gmicol@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -16,21 +19,105 @@ short_description: Manage action rule profiles (rtctrl:AttrP) description: - Manage action rule profiles on Cisco ACI fabrics. options: + tenant: + description: + - The name of the tenant. + type: str + aliases: [ tenant_name ] action_rule: description: - The name of the action rule profile. type: str - aliases: [ action_rule_name, name ] + aliases: [action_rule_name, name ] + set_community: + description: + - The set action rule based on communities. + - To delete this attribute, pass an empty dictionary. + type: dict + suboptions: + community: + description: + - The community value. + type: str + criteria: + description: + - The community criteria. + - The option to append or replace the community value. + type: str + choices: [ append, replace, none ] + set_dampening: + description: + - The set action rule based on dampening. + - To delete this attribute, pass an empty dictionary. + type: dict + suboptions: + half_life: + description: + - The half life value (minutes). + type: int + max_suppress_time: + description: + - The maximum suppress time value (minutes). + type: int + reuse: + description: + - The reuse limit value. + type: int + suppress: + description: + - The suppress limit value. + type: int + set_next_hop: + description: + - The set action rule based on the next hop address. + - To delete this attribute, pass an empty string. + type: str + next_hop_propagation: + description: + - The set action rule based on nexthop unchanged configuration. + - Can not be configured along with C(set_route_tag). + - Can not be configured for APIC version 4.2 and prior. + - The APIC defaults to C(false) when unset. + type: bool + multipath: + description: + - Set action rule based on set redistribute multipath configuration. + - Can not be configured along with C(set_route_tag). + - Can not be configured for APIC version 4.2 and prior. + - The APIC defaults to C(false) when unset. + type: bool + set_preference: + description: + - The set action rule based on preference. + - To delete this attribute, pass an empty string. + type: str + set_metric: + description: + - The set action rule based on metric. + - To delete this attribute, pass an empty string. + type: str + set_metric_type: + description: + - The set action rule based on a metric type. + - To delete this attribute, pass an empty string. + type: str + choices: [ ospf_type_1, ospf_type_2, "" ] + set_route_tag: + description: + - The set action rule based on route tag. + - Can not be configured along with C(next_hop_propagation) and C(multipath). + - To delete this attribute, pass an empty string. + type: str + set_weight: + description: + - The set action rule based on weight. + - To delete this attribute, pass an empty string. + type: str 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. @@ -56,6 +143,8 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Dag Wieers (@dagwieers) +- Tim Cragg (@timcragg) +- Gaspard Micol (@gmicol) """ EXAMPLES = r""" @@ -66,6 +155,40 @@ EXAMPLES = r""" password: SomeSecretPassword action_rule: my_action_rule tenant: prod + set_preference: 100 + set_weight: 100 + set_metric: 100 + set_metric_type: ospf_type_1 + set_next_hop: 1.1.1.1 + next_hop_propagation: true + multipath: true + set_community: + community: no-advertise + criteria: replace + set_dampening: + half_life: 10 + reuse: 1 + suppress: 10 + max_suppress_time: 100 + state: present + delegate_to: localhost + +- name: Delete action rule profile's children + cisco.aci.aci_tenant_action_rule_profile: + host: apic + username: admin + password: SomeSecretPassword + action_rule: my_action_rule + tenant: prod + set_preference: "" + set_weight: "" + set_metric: "" + set_metric_type: "" + set_next_hop: "" + next_hop_propagation: false + multipath: false + set_community: {} + set_dampening: {} state: present delegate_to: localhost @@ -206,7 +329,14 @@ url: """ from ansible.module_utils.basic import AnsibleModule -from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible_collections.cisco.aci.plugins.module_utils.aci import ( + ACIModule, + aci_argument_spec, + aci_annotation_spec, + action_rule_set_comm_spec, + action_rule_set_dampening_spec, +) +from ansible_collections.cisco.aci.plugins.module_utils.constants import MATCH_ACTION_RULE_SET_METRIC_TYPE_MAPPING def main(): @@ -215,6 +345,16 @@ def main(): 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 + set_community=dict(type="dict", options=action_rule_set_comm_spec()), + set_dampening=dict(type="dict", options=action_rule_set_dampening_spec()), + set_next_hop=dict(type="str"), + next_hop_propagation=dict(type="bool"), + multipath=dict(type="bool"), + set_preference=dict(type="str"), + set_metric=dict(type="str"), + set_metric_type=dict(type="str", choices=["ospf_type_1", "ospf_type_2", ""]), + set_route_tag=dict(type="str"), + set_weight=dict(type="str"), description=dict(type="str", aliases=["descr"]), state=dict(type="str", default="present", choices=["absent", "present", "query"]), name_alias=dict(type="str"), @@ -236,6 +376,31 @@ def main(): name_alias = module.params.get("name_alias") aci = ACIModule(module) + + # This dict contains the name of the child classes as well as the corresping attribute input (and attribute name if the input is a string) + # this dict is deviating from normal child classes list structure in order to determine which child classes should be created, modified, deleted or ignored. + child_classes = dict( + rtctrlSetComm=dict(attribute_input=module.params.get("set_community")), + rtctrlSetDamp=dict(attribute_input=module.params.get("set_dampening")), + rtctrlSetNh=dict(attribute_input=module.params.get("set_next_hop"), attribute_name="addr"), + rtctrlSetPref=dict(attribute_input=module.params.get("set_preference"), attribute_name="localPref"), + rtctrlSetRtMetric=dict(attribute_input=module.params.get("set_metric"), attribute_name="metric"), + rtctrlSetRtMetricType=dict( + attribute_input=MATCH_ACTION_RULE_SET_METRIC_TYPE_MAPPING.get(module.params.get("set_metric_type")), attribute_name="metricType" + ), + rtctrlSetTag=dict(attribute_input=module.params.get("set_route_tag"), attribute_name="tag"), + rtctrlSetWeight=dict(attribute_input=module.params.get("set_weight"), attribute_name="weight"), + ) + + # This condition deal with child classes which do not exist in APIC version 4.2 and prior. + additional_child_classes = dict( + rtctrlSetNhUnchanged=dict(attribute_input=module.params.get("next_hop_propagation")), + rtctrlSetRedistMultipath=dict(attribute_input=module.params.get("multipath")), + ) + for class_name, attribute in additional_child_classes.items(): + if attribute.get("attribute_input") is not None: + child_classes[class_name] = attribute + aci.construct_url( root_class=dict( aci_class="fvTenant", @@ -249,11 +414,63 @@ def main(): module_object=action_rule, target_filter={"name": action_rule}, ), + child_classes=list(child_classes.keys()), ) aci.get_existing() if state == "present": + child_configs = [] + for class_name, attribute in child_classes.items(): + attribute_input = attribute.get("attribute_input") + # This condition enables to user to keep its previous configurations if they are not passing anything in the payload. + if attribute_input is not None: + # This condition checks if the attribute input is a dict and checks if all of its values are None (stored as a boolean in only_none). + only_none = False + if isinstance(attribute_input, dict): + only_none = all(value is None for value in attribute_input.values()) + # This condition checks if the child object needs to be deleted depending on the type of the corresponding attribute input (bool, str, dict). + if (attribute_input == "" or attribute_input is False or only_none) and isinstance(aci.existing, list) and len(aci.existing) > 0: + for child in aci.existing[0].get("rtctrlAttrP", {}).get("children", {}): + if child.get(class_name): + child_configs.append( + { + class_name: dict( + attributes=dict(status="deleted"), + ), + } + ) + # This condition checks if the child object needs to be modified or created depending on the type of the corresponding attribute input. + elif attribute_input != "" or attribute_input is True or attribute_input != {}: + if class_name == "rtctrlSetComm" and isinstance(attribute_input, dict): + child_configs.append( + { + class_name: dict( + attributes=dict( + community=attribute_input.get("community"), + setCriteria=attribute_input.get("criteria"), + ), + ) + } + ) + elif class_name == "rtctrlSetDamp" and isinstance(attribute_input, dict): + child_configs.append( + { + class_name: dict( + attributes=dict( + halfLife=attribute_input.get("half_life"), + maxSuppressTime=attribute_input.get("max_suppress_time"), + reuse=attribute_input.get("reuse"), + suppress=attribute_input.get("suppress"), + ), + ) + } + ) + elif class_name in ["rtctrlSetNhUnchanged", "rtctrlSetRedistMultipath"]: + child_configs.append({class_name: dict(attributes=dict(descr=""))}) + else: + child_configs.append({class_name: dict(attributes={attribute.get("attribute_name"): attribute_input})}) + aci.payload( aci_class="rtctrlAttrP", class_config=dict( @@ -261,6 +478,7 @@ def main(): descr=description, nameAlias=name_alias, ), + child_configs=child_configs, ) aci.get_diff(aci_class="rtctrlAttrP") diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py index f8a51529c..eac547dc1 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_controller.py @@ -3,6 +3,7 @@ # Copyright: (c) 2021, Manuel Widmer <mawidmer@cisco.com> # Copyright: (c) 2021, Anvitha Jain (@anvitha-jain) <anvjain@cisco.com> +# Copyright: (c) 2023, Akini Ross (@akinross) <akinross@cisco.com> # GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function @@ -88,6 +89,8 @@ seealso: link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Manuel Widmer (@lumean) +- Anvitha Jain (@anvitha-jain) +- Akini Ross (@akinross) """ EXAMPLES = r""" @@ -132,7 +135,6 @@ EXAMPLES = r""" host: apic username: admin password: SomeSecretPassword - vm_provider: vmware state: query delegate_to: localhost register: query_result @@ -310,6 +312,8 @@ def main(): aci = ACIModule(module) + child_classes = ["vmmRsMgmtEPg", "vmmRsAcc"] + aci.construct_url( root_class=dict( aci_class="vmmProvP", @@ -327,11 +331,23 @@ def main(): aci_class="vmmCtrlrP", aci_rn="ctrlr-{0}".format(name), module_object=name, - target_filter={"name": "name"}, + target_filter={"name": name}, ), - child_classes=["vmmRsMgmtEPg", "vmmRsAcc"], + child_classes=child_classes, ) + # vmmProvP is not allowed to execute a query with rsp-subtree set in the filter_string + # due to complicated url construction logic which should be refactored creating a temporary fix inside module + # TODO refactor url construction logic if more occurences of rsp-subtree not supported problem appear + # check if the url is pointing towards the vmmProvP class and rsp-subtree is set in the filter_string + if aci.url.split("/")[-1].startswith("vmmp-") and "rsp-subtree" in aci.filter_string: + if name: + aci.url = "{0}/api/class/vmmCtrlrP.json".format(aci.base_url) + aci.filter_string = '?query-target-filter=eq(vmmCtrlrP.name,"{0}")&rsp-subtree=full&rsp-subtree-class={1}'.format(name, ",".join(child_classes)) + else: + aci.url = "{0}/api/mo/uni/vmmp-{1}.json".format(aci.base_url, VM_PROVIDER_MAPPING.get(vm_provider)) + aci.filter_string = "" + aci.get_existing() if state == "present": 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 index 79d8182ff..90525e11c 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_vmm_vswitch_policy.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vmm_vswitch_policy.py @@ -14,7 +14,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_vmm_vswitch_policy -short_description: Manage vSwitch policy for VMware virtual domains profiles (vmm:DomP) +short_description: Manage vSwitch policy for VMware virtual domains profiles (vmm:VSwitchPolicyCont) description: - Manage vSwitch policy for VMware VMM domains on Cisco ACI fabrics. options: @@ -139,7 +139,7 @@ extends_documentation_fragment: seealso: - module: cisco.aci.aci_domain - name: APIC Management Information Model reference - description: More information about the internal APIC classes B(vmm:DomP) + description: More information about the internal APIC classes B(vmm:VSwitchPolicyCont). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Manuel Widmer (@lumean) diff --git a/ansible_collections/cisco/aci/plugins/modules/aci_vrf_multicast.py b/ansible_collections/cisco/aci/plugins/modules/aci_vrf_multicast.py new file mode 100644 index 000000000..4d3809902 --- /dev/null +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vrf_multicast.py @@ -0,0 +1,702 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# Copyright: (c) 2023, Akini Ross (@akinross) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"} + +DOCUMENTATION = r""" +--- +module: aci_vrf_multicast +short_description: Manage VRF Multicast objects (pim:CtxP) +description: +- Manage VRF Multicast objects on Cisco ACI fabrics. +- Creating I(state=present) enables Protocol Independent Multicast (PIM) on a VRF +- Deleting I(state=absent) disables Protocol Independent Multicast (PIM) on a VRF. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + vrf: + description: + - The name of an existing VRF. + type: str + aliases: [ vrf_name ] + pim_setting: + description: Configuration container for Protocol Independent Multicast (PIM) settings. + type: dict + suboptions: + mtu: + description: + - The MTU size supported for multicast. + - The APIC defaults to C(1500) when unset during creation. + type: int + control_state: + description: + - The action(s) to take when a loop is detected. + - Specify C([]) to remove the control state configuration. + type: list + elements: str + aliases: [ control, ctrl ] + choices: [ fast, strict ] + resource_policy: + description: Configuration container for Protocol Independent Multicast (PIM) resource policy. + type: dict + suboptions: + maximum_limit: + description: + - The Max Multicast Entries. + - The APIC defaults to C(unlimited) when unset during creation. + - Specify C(0) to reset to C(unlimited). + type: int + aliases: [ max ] + reserved_multicast_entries: + description: + - The Reserved Multicast Entries. + - The APIC defaults to C(undefined) when unset during creation. + - Required when C(reserved_route_map) is provided. + type: int + aliases: [ rsvd ] + reserved_route_map: + description: + - The DN of the Route Map. + - Specify C("") to remove the Route Map configuration. + - Required when C(reserved_multicast_entries) is provided. + type: str + aliases: [ route_map, route_map_dn ] + any_source_multicast: + description: Configuration container for Protocol Independent Multicast (PIM) Any Source Multicast (ASM) settings. + type: dict + aliases: [ asm, any_source ] + suboptions: + shared_range_route_map: + description: + - The DN of the Route Map. + - Specify C("") to remove the Route Map configuration. + type: str + aliases: [ shared_range_policy ] + source_group_expiry_route_map: + description: + - The DN of the Route Map. + - Specify C("") to remove the Route Map configuration. + type: str + aliases: [ sg_expiry_route_map ] + expiry: + description: + - The expiry time in seconds. + - The APIC defaults to C(default-timeout) when unset during creation. + - Specify C(0) to reset to C(default-timeout). + type: int + aliases: [ expiry_seconds ] + max_rate: + description: + - The maximum rate per second. + - The APIC defaults to C(65535) when unset during creation. + type: int + aliases: [ max_rate_per_second ] + source_ip: + description: + - The source IP address. + type: str + aliases: [ source, source_ip_address ] + source_specific_multicast: + description: Configuration container for Protocol Independent Multicast (PIM) Source Specific Multicast (SSM) settings. + type: dict + aliases: [ ssm, specific_source ] + suboptions: + group_range_route_map: + description: + - The DN of the Route Map. + - Specify C("") to remove the Route Map configuration. + type: str + aliases: [ group_range_policy ] + bootstrap_router: + description: Configuration container for Protocol Independent Multicast (PIM) Bootstrap Router (BSR) settings. + type: dict + aliases: [ bsr, bootstrap ] + suboptions: + bsr_filter: + description: + - The DN of the Route Map. + - Specify C("") to remove the Route Map configuration. + type: str + aliases: [ filter, route_map, route_map_dn ] + rp_updates: + description: + - The control state of the Bootstrap Router (BSR) policy. + - Specify C([]) to remove the control state configuration. + type: list + elements: str + aliases: [ control, ctrl ] + choices: [ forward, listen ] + auto_rp: + description: Configuration container for Protocol Independent Multicast (PIM) Auto-Rendezvous Point (Auto-RP) settings. + type: dict + aliases: [ auto ] + suboptions: + ma_filter: + description: + - The DN of the Route Map. + - Specify C("") to remove the Route Map configuration. + type: str + aliases: [ filter, route_map, route_map_dn ] + rp_updates: + description: + - The control state of the Auto-Rendezvous Point (Auto-RP) policy. + - Specify C([]) to remove the control state configuration. + type: list + elements: str + aliases: [ control, ctrl ] + choices: [ forward, listen ] + 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 I(tenant) and I(vrf) must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and M(cisco.aci.aci_vrf) modules can be used for this. +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(pim:CtxP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Tim Cragg (@timcragg) +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Enable Multicast on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + state: present + delegate_to: localhost + +- name: Change Multicast PIM Settings on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + pim_setting: + mtu: 2000 + control_state: [ fast, strict ] + state: present + delegate_to: localhost + +- name: Change Multicast Resource Policy on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + resource_policy: + maximum_limit: 100 + reserved_multicast_entries: 20 + reserved_route_map: uni/tn-ansible_test/rtmap-ansible_test + state: present + delegate_to: localhost + +- name: Remove Route-Map from Multicast Resource Policy on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + resource_policy: + reserved_route_map: "" + state: present + delegate_to: localhost + +- name: Change Multicast Any Source Multicast (ASM) Settings on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + any_source_multicast: + shared_range_route_map: uni/tn-ansible_test/rtmap-ansible_test + source_group_expiry_route_map: uni/tn-ansible_test/rtmap-ansible_test + expiry: 500 + max_rate: 64000 + source_ip: 1.1.1.1 + state: present + delegate_to: localhost + +- name: Change Multicast Source Specific Multicast (SSM) Settings on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + source_specific_multicast: + group_range_route_map: uni/tn-ansible_test/rtmap-ansible_test + state: present + delegate_to: localhost + +- name: Change Multicast Bootstrap Router (BSR) Settings on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + bootstrap_router: + bsr_filter: uni/tn-ansible_test/rtmap-ansible_test + rp_updates: [ forward, listen ] + state: present + delegate_to: localhost + +- name: Change Multicast Auto-Rendezvous Point (Auto-RP) Settings on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + auto_rp: + ma_filter: uni/tn-ansible_test/rtmap-ansible_test + rp_updates: [ forward, listen ] + state: present + delegate_to: localhost + +- name: Disable Multicast on a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + state: absent + delegate_to: localhost + +- name: Query Multicast Settings for a VRF + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + tenant: ansible_tenant + vrf: ansible_vrf + state: query + delegate_to: localhost + register: query_result + +- name: Query Multicast Settings for all VRFs + cisco.aci.aci_vrf_multicast: + host: apic + username: admin + password: SomeSecretePassword + 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 +from ansible_collections.cisco.aci.plugins.module_utils.constants import PIM_SETTING_CONTROL_STATE_MAPPING + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), + vrf=dict(type="str", aliases=["vrf_name"]), + pim_setting=dict( + type="dict", + options=dict( + mtu=dict(type="int"), + control_state=dict(type="list", elements="str", choices=["fast", "strict"], aliases=["control", "ctrl"]), + ), + ), + resource_policy=dict( + type="dict", + options=dict( + maximum_limit=dict(type="int", aliases=["max"]), + reserved_multicast_entries=dict(type="int", aliases=["rsvd"]), + reserved_route_map=dict(type="str", aliases=["route_map", "route_map_dn"]), + ), + ), + any_source_multicast=dict( + type="dict", + options=dict( + shared_range_route_map=dict(type="str", aliases=["shared_range_policy"]), + source_group_expiry_route_map=dict(type="str", aliases=["sg_expiry_route_map"]), + expiry=(dict(type="int", aliases=["expiry_seconds"])), + max_rate=(dict(type="int", aliases=["max_rate_per_second"])), + source_ip=(dict(type="str", aliases=["source", "source_ip_address"])), + ), + aliases=["asm", "any_source"], + ), + source_specific_multicast=dict( + type="dict", + options=dict( + group_range_route_map=dict(type="str", aliases=["group_range_policy"]), + ), + aliases=["ssm", "specific_source"], + ), + bootstrap_router=dict( + type="dict", + options=dict( + bsr_filter=dict(type="str", aliases=["filter", "route_map", "route_map_dn"]), + rp_updates=dict(type="list", elements="str", choices=["forward", "listen"], aliases=["control", "ctrl"]), + ), + aliases=["bsr", "bootstrap"], + ), + auto_rp=dict( + type="dict", + options=dict( + ma_filter=dict(type="str", aliases=["filter", "route_map", "route_map_dn"]), + rp_updates=dict(type="list", elements="str", choices=["forward", "listen"], aliases=["control", "ctrl"]), + ), + aliases=["auto"], + ), + 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", "vrf"]], + ["state", "present", ["tenant", "vrf"]], + ], + ) + + aci = ACIModule(module) + + tenant = module.params.get("tenant") + vrf = module.params.get("vrf") + pim_setting = module.params.get("pim_setting") + resource_policy = module.params.get("resource_policy") + any_source_multicast = module.params.get("any_source_multicast") + source_specific_multicast = module.params.get("source_specific_multicast") + bootstrap_router = module.params.get("bootstrap_router") + auto_rp = module.params.get("auto_rp") + state = module.params.get("state") + + if resource_policy and resource_policy.get("reserved_multicast_entries") and resource_policy.get("reserved_route_map") is None: + aci.fail_json(msg="parameters are mutually exclusive: reserved_route_map|reserved_multicast_entries") + elif resource_policy and resource_policy.get("reserved_route_map") and not resource_policy.get("reserved_multicast_entries"): + aci.fail_json(msg="C(reserved_multicast_entries) must be provided and greater than 0 when C(reserved_route_map) is provided") + + 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="pimCtxP", + aci_rn="pimctxp", + target_filter={"name": ""}, + ), + child_classes=["pimResPol", "pimASMPatPol", "pimSSMPatPol", "pimAutoRPPol", "pimBSRPPol"], + ) + + aci.get_existing() + + if state == "present": + existing_config = aci.existing[0] if aci.existing else {} + child_configs = [] + + resource_policy_config = dict(pimResPol=dict(attributes=dict(name=""), children=[])) + if resource_policy: + max = "unlimited" if resource_policy.get("maximum_limit") == 0 else resource_policy.get("maximum_limit") + if max is not None: + resource_policy_config["pimResPol"]["attributes"]["max"] = str(max) + + reserved_route_map = resource_policy.get("reserved_route_map") + if reserved_route_map is not None: + existing_rrm = get_child_from_existing_config(existing_config, ["pimCtxP", "pimResPol", "rtdmcRsFilterToRtMapPol"]) + rsvd = resource_policy.get("reserved_multicast_entries") + + if (existing_rrm or reserved_route_map != "") and rsvd: + resource_policy_config["pimResPol"]["attributes"]["rsvd"] = str(rsvd) + elif existing_rrm and reserved_route_map == "": + resource_policy_config["pimResPol"]["attributes"]["rsvd"] = "undefined" + + set_route_map_config( + existing_config, + resource_policy_config["pimResPol"]["children"], + ["pimCtxP", "pimResPol", "rtdmcRsFilterToRtMapPol"], + reserved_route_map, + ) + + child_configs.append(resource_policy_config) + + any_source_multicast_config = dict( + pimASMPatPol=dict( + attributes=dict(name=""), + children=[ + dict(pimSharedRangePol=dict(attributes=dict(name=""), children=[])), + dict(pimSGRangeExpPol=dict(attributes=dict(name=""), children=[])), + dict(pimRegTrPol=dict(attributes=dict(name=""), children=[])), + ], + ) + ) + if any_source_multicast: + if any_source_multicast.get("shared_range_route_map") is not None: + set_route_map_config( + existing_config, + any_source_multicast_config["pimASMPatPol"]["children"][0]["pimSharedRangePol"]["children"], + ["pimCtxP", "pimASMPatPol", "pimSharedRangePol", "rtdmcRsFilterToRtMapPol"], + any_source_multicast.get("shared_range_route_map"), + ) + + if any_source_multicast.get("source_group_expiry_route_map") is not None: + set_route_map_config( + existing_config, + any_source_multicast_config["pimASMPatPol"]["children"][1]["pimSGRangeExpPol"]["children"], + ["pimCtxP", "pimASMPatPol", "pimSGRangeExpPol", "rtdmcRsFilterToRtMapPol"], + any_source_multicast.get("source_group_expiry_route_map"), + ) + + expiry = any_source_multicast.get("expiry") + if expiry is not None: + sg_expiry_config = "default-timeout" if any_source_multicast.get("expiry") == 0 else any_source_multicast.get("expiry") + any_source_multicast_config["pimASMPatPol"]["children"][1]["pimSGRangeExpPol"]["attributes"]["sgExpItvl"] = str(sg_expiry_config) + + if any_source_multicast.get("max_rate") is not None: + any_source_multicast_config["pimASMPatPol"]["children"][2]["pimRegTrPol"]["attributes"]["maxRate"] = str(any_source_multicast.get("max_rate")) + + if any_source_multicast.get("source_ip") is not None: + any_source_multicast_config["pimASMPatPol"]["children"][2]["pimRegTrPol"]["attributes"]["srcIp"] = any_source_multicast.get("source_ip") + + child_configs.append(any_source_multicast_config) + + source_specific_multicast_config = dict( + pimSSMPatPol=dict( + attributes=dict(name=""), + children=[ + dict(pimSSMRangePol=dict(attributes=dict(name=""), children=[])), + ], + ) + ) + if source_specific_multicast and source_specific_multicast.get("group_range_route_map") is not None: + set_route_map_config( + existing_config, + source_specific_multicast_config["pimSSMPatPol"]["children"][0]["pimSSMRangePol"]["children"], + ["pimCtxP", "pimSSMPatPol", "pimSSMRangePol", "rtdmcRsFilterToRtMapPol"], + source_specific_multicast.get("group_range_route_map"), + ) + + child_configs.append(source_specific_multicast_config) + + if bootstrap_router: + bsr_config = dict(pimBSRPPol=dict(attributes=dict(name=""), children=[dict(pimBSRFilterPol=dict(attributes=dict(name=""), children=[]))])) + if bootstrap_router.get("bsr_filter") is not None: + set_route_map_config( + existing_config, + bsr_config["pimBSRPPol"]["children"][0]["pimBSRFilterPol"]["children"], + ["pimCtxP", "pimBSRPPol", "pimBSRFilterPol", "rtdmcRsFilterToRtMapPol"], + bootstrap_router.get("bsr_filter"), + ) + + rp_updates = bootstrap_router.get("rp_updates") + if rp_updates is not None: + bsr_config["pimBSRPPol"]["attributes"]["ctrl"] = ",".join(sorted(rp_updates)) + + child_configs.append(bsr_config) + + if auto_rp: + auto_rp_config = dict(pimAutoRPPol=dict(attributes=dict(name=""), children=[dict(pimMAFilterPol=dict(attributes=dict(name=""), children=[]))])) + + if auto_rp.get("ma_filter") is not None: + set_route_map_config( + existing_config, + auto_rp_config["pimAutoRPPol"]["children"][0]["pimMAFilterPol"]["children"], + ["pimCtxP", "pimAutoRPPol", "pimMAFilterPol", "rtdmcRsFilterToRtMapPol"], + auto_rp.get("ma_filter"), + ) + + rp_updates = auto_rp.get("rp_updates") + if rp_updates is not None: + auto_rp_config["pimAutoRPPol"]["attributes"]["ctrl"] = ",".join(sorted(rp_updates)) + + child_configs.append(auto_rp_config) + + mtu = None + control_state = None + if pim_setting: + mtu = pim_setting.get("mtu") + control_state = ( + ",".join(sorted([PIM_SETTING_CONTROL_STATE_MAPPING.get(v) for v in pim_setting.get("control_state")])) + if pim_setting.get("control_state") is not None + else None + ) + + aci.payload( + aci_class="pimCtxP", + class_config=dict(mtu=mtu, ctrl=control_state), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="pimCtxP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +def get_child_from_existing_config(config, class_names): + parent = class_names[0] + class_names.remove(parent) + + for child in config.get(parent, {}).get("children", []): + if len(class_names) == 1 and class_names[0] in child.keys(): + return child + elif child.get(class_names[0], {}).get("children"): + return get_child_from_existing_config(child, class_names) + + +def set_route_map_config(existing_config, new_config, class_names, route_map): + existing_route_map = get_child_from_existing_config(existing_config, class_names) + if route_map == "" and existing_route_map: + new_config.append(dict(rtdmcRsFilterToRtMapPol=dict(attributes=dict(tDn=route_map, status="deleted")))) + elif route_map: + new_config.append(dict(rtdmcRsFilterToRtMapPol=dict(attributes=dict(tDn=route_map)))) + + +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 index f2632307c..411f63936 100644 --- a/ansible_collections/cisco/aci/plugins/modules/aci_vzany_to_contract.py +++ b/ansible_collections/cisco/aci/plugins/modules/aci_vzany_to_contract.py @@ -12,7 +12,7 @@ ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported DOCUMENTATION = r""" --- module: aci_vzany_to_contract -short_description: Attach contracts to vzAny (vz:RsAnyToProv, vz:RsAnyToCons, vz:RsAnyToConsIf) +short_description: Attach contracts to vzAny (vz:RsAnyToProv, vz:RsAnyToCons, and vz:RsAnyToConsIf) description: - Bind contracts to vzAny on Cisco ACI fabrics. options: @@ -54,7 +54,7 @@ seealso: - 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). + description: More information about the internal APIC classes B(vz:RsAnyToProv), B(vz:RsAnyToCons), and B(vz:RsAnyToConsIf). link: https://developer.cisco.com/docs/apic-mim-ref/ author: - Marcel Zehnder (@maercu) |