diff --git a/plugins/modules/aci_match_as_path_regex_term.py b/plugins/modules/aci_match_as_path_regex_term.py new file mode 100644 index 000000000..bb87acaa5 --- /dev/null +++ b/plugins/modules/aci_match_as_path_regex_term.py @@ -0,0 +1,306 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_as_path_regex_term +short_description: Manage Match Regular Expression AS-Path Term (rtctrl:MatchAsPathRegexTerm) +description: +- Manage Match Term Based on Route Regular Expression AS-Path for Match Rule Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of an exising match rule profile. + type: str + aliases: [ match_rule_name ] + match_as_path_regex_term: + description: + - The name of the match regex AS-Path term. + type: str + aliases: [ name, match_as_path_regex_term_name ] + regex: + description: + - The Regular Expression. + type: str + description: + description: + - The description for the match regex AS-Path term. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchAsPathRegexTerm). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a match with AS-path regex term + cisco.aci.aci_match_as_path_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + match_as_path_regex_term: prod_match_as_path_regex_term + regex: .* + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match with AS-path regex term + cisco.aci.aci_match_as_path_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_as_path_regex_term: prod_match_as_path_regex_term + state: absent + delegate_to: localhost + +- name: Query all match with AS-path regex terms + cisco.aci.aci_match_as_path_regex_term: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match with AS-path regex term + cisco.aci.aci_match_as_path_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_as_path_regex_term: prod_match_as_path_regex_term + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + match_as_path_regex_term=dict(type="str", aliases=["name", "match_as_path_regex_term_name"]), + regex=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["match_as_path_regex_term", "tenant"]], + ["state", "present", ["match_as_path_regex_term", "tenant"]], + ], + ) + + match_as_path_regex_term = module.params.get("match_as_path_regex_term") + description = module.params.get("description") + regex = module.params.get("regex") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchAsPathRegexTerm", + aci_rn="aspathrxtrm-{0}".format(match_as_path_regex_term), + module_object=match_as_path_regex_term, + target_filter={"name": match_as_path_regex_term}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchAsPathRegexTerm", + class_config=dict( + name=match_as_path_regex_term, + regex=regex, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchAsPathRegexTerm") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_match_community_factor.py b/plugins/modules/aci_match_community_factor.py new file mode 100644 index 000000000..8db4e6e71 --- /dev/null +++ b/plugins/modules/aci_match_community_factor.py @@ -0,0 +1,325 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_community_factor +short_description: Manage Match Community Factor (rtctrl:MatchCommFactor) +description: +- Manage Match Community Factors for Match Terms Based on Community on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of an exising match rule profile. + type: str + aliases: [ match_rule_name ] + match_community_term: + description: + - The name of an existing match community term. + type: str + aliases: [ match_community_term_name ] + community: + description: + - The match community value. + type: str + scope: + description: + - The item scope. + - If the scope is transitive, this community may be passed between ASs. + - If the scope is non-transitive, this community should be carried only within the local AS. + type: str + choices: [ transitive, non-transitive ] + description: + description: + - The description for the match community factor. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant), the C(match_rule) and the C(match_community_term) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant), the M(cisco.aci.aci_match_rule) and M(cisco.aci.aci_match_community_term) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- module: cisco.aci.aci_match_community_term +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchCommFactor). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a match match AS-path regex term + cisco.aci.aci_match_community_factor: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + match_community_term: prod_match_community_term + community: regular:as2-nn2:4:15 + scope: transitive + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match match AS-path regex term + cisco.aci.aci_match_community_factor: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_community_term: prod_match_community_term + community: regular:as2-nn2:4:15 + state: absent + delegate_to: localhost + +- name: Query all match AS-path regex terms + cisco.aci.aci_match_community_factor: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match match AS-path regex term + cisco.aci.aci_match_community_factor: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_community_term: prod_match_community_term + community: regular:as2-nn2:4:15 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + match_community_term=dict(type="str", aliases=["match_community_term_name"]), # Not required for querying all objects + community=dict(type="str"), + scope=dict(type="str", choices=["transitive", "non-transitive"]), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["community", "tenant", "match_rule", "match_community_term"]], + ["state", "present", ["community", "tenant", "match_rule", "match_community_term"]], + ], + ) + + community = module.params.get("community") + scope = module.params.get("scope") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + match_community_term = module.params.get("match_community_term") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchCommTerm", + aci_rn="commtrm-{0}".format(match_community_term), + module_object=match_community_term, + target_filter={"name": match_community_term}, + ), + subclass_3=dict( + aci_class="rtctrlMatchCommFactor", + aci_rn="commfct-{0}".format(community), + module_object=community, + target_filter={"community": community}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchCommFactor", + class_config=dict( + community=community, + scope=scope, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchCommFactor") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_match_community_regex_term.py b/plugins/modules/aci_match_community_regex_term.py new file mode 100644 index 000000000..1b2d0007a --- /dev/null +++ b/plugins/modules/aci_match_community_regex_term.py @@ -0,0 +1,317 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_community_regex_term +short_description: Manage Match Regular Expression Community Term (rtctrl:MatchCommRegexTerm) +description: +- Manage Match Terms Based on Route Regular Expression Community for Match Rule Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of an exising math rule profile. + type: str + aliases: [ match_rule_name ] + match_community_regex_term: + description: + - The name of the match regex community term. + type: str + aliases: [ name, match_community_regex_term_name ] + community_type: + description: + - The community type. + type: str + choices: [ extended, regular ] + default: regular + regex: + description: + - The regular expression. + type: str + description: + description: + - The description for the match regex community term. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this. +- Only two match community regex terms can exist at the same time, one of each C(community_type). +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchCommRegexTerm). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a match with comunity regex term + cisco.aci.aci_match_community_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + match_community_regex_term: prod_match_community_regex_term + community_type: regular + regex: .* + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match with comunity regex term + cisco.aci.aci_match_community_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + community_type: regular + state: absent + delegate_to: localhost + +- name: Query all match with commmuntiy regex terms + cisco.aci.aci_match_community_regex_term: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match with comunity regex term + cisco.aci.aci_match_community_regex_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + community_type: regular + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + match_community_regex_term=dict(type="str", aliases=["name", "match_community_regex_term_name"]), + community_type=dict(type="str", default="regular", choices=["extended", "regular"]), + regex=dict(type="str"), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["community_type", "tenant", "match_rule"]], + ["state", "present", ["community_type", "tenant", "match_rule"]], + ], + ) + + match_community_regex_term = module.params.get("match_community_regex_term") + description = module.params.get("description") + community_type = module.params.get("community_type") + regex = module.params.get("regex") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchCommRegexTerm", + aci_rn="commrxtrm-{0}".format(community_type), + module_object=community_type, + target_filter={"commType": community_type}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchCommRegexTerm", + class_config=dict( + name=match_community_regex_term, + commType=community_type, + regex=regex, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchCommRegexTerm") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_match_community_term.py b/plugins/modules/aci_match_community_term.py new file mode 100644 index 000000000..21a3f35db --- /dev/null +++ b/plugins/modules/aci_match_community_term.py @@ -0,0 +1,298 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_community_term +short_description: Manage Match Community Term (rtctrl:MatchCommTerm) +description: +- Manage Match Term Based on Community for Match Rule Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - the name of an exising match rule profile. + type: str + aliases: [ match_rule_name ] + match_community_term: + description: + - the name of the Match Community Term. + type: str + aliases: [ name, match_community_term_name ] + description: + description: + - The description for the Match Community Term. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchCommTerm). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +""" + +EXAMPLES = r""" +- name: Create a match with community term + cisco.aci.aci_match_community_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + match_community_term: prod_match_community_term + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match with community term + cisco.aci.aci_match_community_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_community_term: prod_match_community_term + state: absent + delegate_to: localhost + +- name: Query all with community terms + cisco.aci.aci_match_community_term: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match with community term + cisco.aci.aci_match_community_term: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + match_community_term: prod_match_community_term + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + match_community_term=dict(type="str", aliases=["name", "match_community_term_name"]), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["match_community_term", "tenant", "match_rule"]], + ["state", "present", ["match_community_term", "tenant", "match_rule"]], + ], + ) + + match_community_term = module.params.get("match_community_term") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchCommTerm", + aci_rn="commtrm-{0}".format(match_community_term), + module_object=match_community_term, + target_filter={"name": match_community_term}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchCommTerm", + class_config=dict( + name=match_community_term, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchCommTerm") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_match_route_destination.py b/plugins/modules/aci_match_route_destination.py new file mode 100644 index 000000000..973d70561 --- /dev/null +++ b/plugins/modules/aci_match_route_destination.py @@ -0,0 +1,325 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_route_destination +short_description: Manage Match action rule term based on the Route Destination. (rtctrl:MatchRtDest) +description: +- Match action rule terms based on the Route Destination for Subject Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of an exising match rule profile. + type: str + aliases: [ match_rule_name ] + ip: + description: + - The IP address of the route destination. + type: str + aggregate: + description: + - Option to enable/disable aggregated route. + type: str + choices: [ "no", "yes" ] + from_prefix_length: + description: + - The start of the prefix length. + - It corresponds to the lesser Mask if the route is aggregated. + type: int + to_prefix_length: + description: + - The end of the prefix length. + - It corresponds to greater Mask if the route is aggregated. + type: int + description: + description: + - The description for the match action rule term. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(match_rule) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_match_rule) modules can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:MatchRtDest). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a match rule destination + cisco.aci.aci_match_rule_destination: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + ip: 11.11.11.11/24 + aggregate: "yes" + from_prefix_length: 0 + to_prefix_length: 32 + state: present + delegate_to: localhost + +- name: Delete a match rule destination + cisco.aci.aci_match_rule_destination: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + state: absent + delegate_to: localhost + +- name: Query all match rules destination + cisco.aci.aci_match_rule_destination: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match rule destination + cisco.aci.aci_match_rule_destination: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + ip: 11.11.11.11/24 + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), # Not required for querying all objects + ip=dict(type="str"), + aggregate=dict(type="str", choices=["no", "yes"]), + from_prefix_length=dict(type="int"), + to_prefix_length=dict(type="int"), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["ip", "tenant", "match_rule"]], + ["state", "present", ["ip", "tenant", "match_rule"]], + ], + ) + + ip = module.params.get("ip") + description = module.params.get("description") + aggregate = module.params.get("aggregate") + from_prefix_length = module.params.get("from_prefix_length") + to_prefix_length = module.params.get("to_prefix_length") + state = module.params.get("state") + tenant = module.params.get("tenant") + match_rule = module.params.get("match_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + subclass_2=dict( + aci_class="rtctrlMatchRtDest", + aci_rn="dest-[{0}]".format(ip), + module_object=ip, + target_filter={"ip": ip}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlMatchRtDest", + class_config=dict( + ip=ip, + aggregate=aggregate, + fromPfxLen=from_prefix_length, + toPfxLen=to_prefix_length, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlMatchRtDest") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_match_rule.py b/plugins/modules/aci_match_rule.py new file mode 100644 index 000000000..c412d2458 --- /dev/null +++ b/plugins/modules/aci_match_rule.py @@ -0,0 +1,283 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_match_rule +short_description: Manage Match Rule (rtcrtl:SubjP) +description: +- Manage Match Rule Profiles for the Tenants on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + match_rule: + description: + - The name of the match rule profile being created. + type: str + aliases: [ name, match_rule_name ] + description: + description: + - The description for the match rule profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:SubjP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a match rule + cisco.aci.aci_match_rule: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + state: present + delegate_to: localhost + +- name: Delete a match rule + cisco.aci.aci_match_rule: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + state: absent + delegate_to: localhost + +- name: Query all match rules + cisco.aci.aci_match_rule: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific match rule + cisco.aci.aci_match_rule: + host: apic + username: admin + password: SomeSecretPassword + match_rule: prod_match_rule + tenant: production + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["name", "match_rule_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["match_rule", "tenant"]], + ["state", "present", ["match_rule", "tenant"]], + ], + ) + + match_rule = module.params.get("match_rule") + description = module.params.get("description") + state = module.params.get("state") + tenant = module.params.get("tenant") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + aci.construct_url( + root_class=dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ), + subclass_1=dict( + aci_class="rtctrlSubjP", + aci_rn="subj-{0}".format(match_rule), + module_object=match_rule, + target_filter={"name": match_rule}, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlSubjP", + class_config=dict( + name=match_rule, + descr=description, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlSubjP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_route_control_context.py b/plugins/modules/aci_route_control_context.py new file mode 100644 index 000000000..19993767a --- /dev/null +++ b/plugins/modules/aci_route_control_context.py @@ -0,0 +1,393 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_route_control_context +short_description: Manage Route Control Context (rtcrtl:CtxP) +description: +- Manage Route Control Context Policies for the Route Control Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of an existing L3Out. + - To use only if the route control profile is linked to an existing L3Out. + type: str + aliases: [ l3out_name ] + route_control_profile: + description: + - The name of an existing route control profile. + type: str + aliases: [ route_control_profile_name ] + route_control_context: + description: + - The name of the route control context being created. + type: str + aliases: [ name, route_control_context_name, context ] + action: + description: + - The action required when the condition is met. + type: str + choices: [ deny, permit ] + action_rule: + description: + - The name of the action rule profile to be associated with this route control context. + - Set the rules for a Route Map. + type: str + aliases: [ action_rule_name ] + match_rule: + description: + - The name of the match rule profile to be associated with this route control context. + - Set the associated Matched rules. + type: str + aliases: [ match_rule_name ] + order: + description: + - The order of the route control context. + - The value range from 0 to 9. + type: int + description: + description: + - The description for the route control context. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) and the C(route_control_profile) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) and the M(cisco.aci.aci_route_control_profile) modules can be used for this. +- If C(l3out) is used, the C(l3out) used must exist before using this module in your playbook. + The M(cisco.aci.aci_l3out) module can be used for this. +- if C(action_rule) is used, the C(action_rule) used must exist before using this module in your plabook. + The module M(cisco.aci.aci_tenant_action_rule_profile) can be used for this. +- if C(match_rule) is used, the C(match_rule) used must exist before using this module in your plabook. + The module M(cisco.aci.aci_match_rule) can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_route_control_profile +- module: cisco.aci.aci_l3out +- module: cisco.aci.aci_tenant_action_rule_profile +- module: cisco.aci.aci_match_rule +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:CtxP). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a route context policy + cisco.aci.aci_route_control_context: + host: apic + username: admin + password: SomeSecretPassword + route_control_context: prod_route_control_context + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + action: permit + order: 0 + action_rule: prod_action_rule_profile + match_rule: prod_match_rule + state: present + delegate_to: localhost + +- name: Delete a route context policy + cisco.aci.aci_route_control_context: + host: apic + username: admin + password: SomeSecretPassword + route_control_context: prod_route_control_context + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + state: absent + delegate_to: localhost + +- name: Query all route context policy + cisco.aci.aci_route_control_context: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific route context policy + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + route_control_context: prod_route_control_context + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects + route_control_profile=dict(type="str", aliases=["route_control_profile_name"]), # Not required for querying all objects + route_control_context=dict(type="str", aliases=["name", "route_control_context_name", "context"]), # Not required for querying all objects + match_rule=dict(type="str", aliases=["match_rule_name"]), + action_rule=dict(type="str", aliases=["action_rule_name"]), + action=dict(type="str", choices=["deny", "permit"]), + order=dict(type="int"), + description=dict(type="str", aliases=["descr"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["route_control_context", "tenant"]], + ["state", "present", ["route_control_context", "tenant"]], + ], + ) + + route_control_context = module.params.get("route_control_context") + description = module.params.get("description") + action = module.params.get("action") + order = module.params.get("order") + state = module.params.get("state") + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + route_control_profile = module.params.get("route_control_profile") + match_rule = module.params.get("match_rule") + action_rule = module.params.get("action_rule") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + child_classes = ["rtctrlRsCtxPToSubjP", "rtctrlScope"] + + tenant_url_config = dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ) + + route_control_profile_url_config = dict( + aci_class="rtctrlProfile", + aci_rn="prof-{0}".format(route_control_profile), + module_object=route_control_profile, + target_filter={"name": route_control_profile}, + ) + + route_control_context_url_config = dict( + aci_class="rtctrlCtxP", + aci_rn="ctx-{0}".format(route_control_context), + module_object=route_control_context, + target_filter={"name": route_control_context}, + ) + + if l3out is not None: + aci.construct_url( + root_class=tenant_url_config, + subclass_1=dict( + aci_class="l3extOut", + aci_rn="out-{0}".format(l3out), + module_object=l3out, + target_filter={"name": l3out}, + ), + subclass_2=route_control_profile_url_config, + subclass_3=route_control_context_url_config, + child_classes=child_classes, + ) + else: + aci.construct_url( + root_class=tenant_url_config, + subclass_1=route_control_profile_url_config, + subclass_2=route_control_context_url_config, + child_classes=child_classes, + ) + + aci.get_existing() + + if state == "present": + child_configs = [] + if match_rule is not None: + child_configs.append({"rtctrlRsCtxPToSubjP": {"attributes": {"tnRtctrlSubjPName": match_rule}}}) + if action_rule is not None: + child_configs.append( + { + "rtctrlScope": { + "attributes": {"descr": ""}, + "children": [{"rtctrlRsScopeToAttrP": {"attributes": {"tnRtctrlAttrPName": action_rule}}}], + } + } + ) + + aci.payload( + aci_class="rtctrlCtxP", + class_config=dict( + name=route_control_context, + descr=description, + action=action, + order=order, + nameAlias=name_alias, + ), + child_configs=child_configs, + ) + + aci.get_diff(aci_class="rtctrlCtxP") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/aci_route_control_profile.py b/plugins/modules/aci_route_control_profile.py new file mode 100644 index 000000000..496cce9b3 --- /dev/null +++ b/plugins/modules/aci_route_control_profile.py @@ -0,0 +1,333 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# Copyright: (c) 2023, Tim Cragg (@timcragg) +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_route_control_profile +short_description: Manage Route Control Profile (rtcrtl:Profile) +description: +- Manage Route Control Profiles on Cisco ACI fabrics. +options: + tenant: + description: + - The name of an existing tenant. + type: str + aliases: [ tenant_name ] + l3out: + description: + - The name of an existing L3Out. + - This will link the created route control profile to the existing L3Out. + type: str + aliases: [ l3out_name ] + route_control_profile: + description: + - The name of the route control profile being created. + type: str + aliases: [ name, route_control_profile_name ] + auto_continue: + description: + - The option to enable/disable auto-continue. + type: str + choices: [ "no", "yes" ] + default: "no" + policy_type: + description: + - Set the policy type to combinable or global. + type: str + choices: [ combinable, global ] + default: combinable + description: + description: + - The description for the route control profile. + type: str + aliases: [ descr ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation +- cisco.aci.owner + +notes: +- The C(tenant) used must exist before using this module in your playbook. + The M(cisco.aci.aci_tenant) module can be used for this. +- If C(l3out) is used, the C(l3out) used must exist before using this module in your playbook. + The M(cisco.aci.aci_l3out) module can be used for this. +seealso: +- module: cisco.aci.aci_tenant +- module: cisco.aci.aci_l3out +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(rtctrl:Profile). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Gaspard Micol (@gmicol) +- Tim Cragg (@timcragg) +""" + +EXAMPLES = r""" +- name: Create a route control profile + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + auto_continue: "no" + policy_type: combinable + state: present + delegate_to: localhost + +- name: Delete a route control profile + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + state: absent + delegate_to: localhost + +- name: Query all route control profiles + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost + register: query_result + +- name: Query a specific route control profile + cisco.aci.aci_route_control_profile: + host: apic + username: admin + password: SomeSecretPassword + route_control_profile: prod_route_control_profile + tenant: production + l3out: prod_l3out + state: query + delegate_to: localhost + register: query_result +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerauto_continue": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec, aci_owner_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update(aci_owner_spec()) + argument_spec.update( + tenant=dict(type="str", aliases=["tenant_name"]), # Not required for querying all objects + l3out=dict(type="str", aliases=["l3out_name"]), # Not required for querying all objects + route_control_profile=dict(type="str", aliases=["name", "route_control_profile_name"]), # Not required for querying all objects + description=dict(type="str", aliases=["descr"]), + auto_continue=dict(type="str", default="no", choices=["no", "yes"]), + policy_type=dict(type="str", default="combinable", choices=["combinable", "global"]), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["present", "absent", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["route_control_profile", "tenant"]], + ["state", "present", ["route_control_profile", "tenant"]], + ], + ) + + route_control_profile = module.params.get("route_control_profile") + description = module.params.get("description") + auto_continue = module.params.get("auto_continue") + policy_type = module.params.get("policy_type") + state = module.params.get("state") + tenant = module.params.get("tenant") + l3out = module.params.get("l3out") + name_alias = module.params.get("name_alias") + + aci = ACIModule(module) + + tenant_url_config = dict( + aci_class="fvTenant", + aci_rn="tn-{0}".format(tenant), + module_object=tenant, + target_filter={"name": tenant}, + ) + + route_control_profile_url_config = dict( + aci_class="rtctrlProfile", + aci_rn="prof-{0}".format(route_control_profile), + module_object=route_control_profile, + target_filter={"name": route_control_profile}, + ) + + if l3out is not None: + aci.construct_url( + root_class=tenant_url_config, + subclass_1=dict( + aci_class="l3extOut", + aci_rn="out-{0}".format(l3out), + module_object=l3out, + target_filter={"name": l3out}, + ), + subclass_2=route_control_profile_url_config, + ) + else: + aci.construct_url( + root_class=tenant_url_config, + subclass_1=route_control_profile_url_config, + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="rtctrlProfile", + class_config=dict( + name=route_control_profile, + descr=description, + autoContinue=auto_continue, + type=policy_type, + nameAlias=name_alias, + ), + ) + + aci.get_diff(aci_class="rtctrlProfile") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_match_as_path_regex_term/aliases b/tests/integration/targets/aci_match_as_path_regex_term/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_match_as_path_regex_term/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_match_as_path_regex_term/tasks/main.yml b/tests/integration/targets/aci_match_as_path_regex_term/tasks/main.yml new file mode 100644 index 000000000..cdabd8b4e --- /dev/null +++ b/tests/integration/targets/aci_match_as_path_regex_term/tasks/main.yml @@ -0,0 +1,150 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match rule profile for ansible_tenant tenant + state: present + register: cm_add_match_rule + + - name: Add a match regex AS-Path term (check_mode) + aci_match_as_path_regex_term: &aci_match_as_path_regex_term_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_as_path_regex_term: ansible_match_as_path_regex_term_1 + description: match regex AS-Path term 1 for ansible_match_rule match rule profile + regex: .* + state: present + check_mode: true + register: cm_add_match_as_path_regex_term + + - name: Add a match regex AS-Path term (normal_mode) + aci_match_as_path_regex_term: + <<: *aci_match_as_path_regex_term_present + register: nm_add_match_as_path_regex_term + + - name: Add the first match regex AS-Path term again - testing idempotency + aci_match_as_path_regex_term: + <<: *aci_match_as_path_regex_term_present + register: nm_add_match_as_path_regex_term_idempotency + + - name: Add a second match regex AS-Path term (normal_mode) + aci_match_as_path_regex_term: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_as_path_regex_term: ansible_match_as_path_regex_term_2 + description: match regex AS-Path term 2 for ansible_match_rule match rule profile + state: present + register: nm_add_match_as_path_regex_term_2 + + - name: Asserts for match regex AS-Path terms creation tasks + assert: + that: + - cm_add_match_as_path_regex_term is changed + - cm_add_match_as_path_regex_term.previous == [] + - cm_add_match_as_path_regex_term.current == [] + - nm_add_match_as_path_regex_term is changed + - nm_add_match_as_path_regex_term.current.0.rtctrlMatchAsPathRegexTerm.attributes.name == "ansible_match_as_path_regex_term_1" + - nm_add_match_as_path_regex_term.current.0.rtctrlMatchAsPathRegexTerm.attributes.regex == ".*" + - nm_add_match_as_path_regex_term_idempotency is not changed + - nm_add_match_as_path_regex_term_2 is changed + - nm_add_match_as_path_regex_term_2.previous == [] + - nm_add_match_as_path_regex_term_2.current.0.rtctrlMatchAsPathRegexTerm.attributes.name == "ansible_match_as_path_regex_term_2" + - nm_add_match_as_path_regex_term_2.current.0.rtctrlMatchAsPathRegexTerm.attributes.regex == "" + + - name: Query all match regex AS-Path terms + aci_match_as_path_regex_term: + <<: *aci_info + state: query + register: query_all_match_as_path_regex_term + + - name: Query ansible_match_as_path_regex_term_1 + aci_match_as_path_regex_term: + <<: *aci_match_as_path_regex_term_present + state: query + register: query_ansible_match_as_path_regex_term_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_as_path_regex_term is not changed + - query_all_match_as_path_regex_term.current|length >= 2 + - query_ansible_match_as_path_regex_term_1 is not changed + - query_ansible_match_as_path_regex_term_1.current.0.rtctrlMatchAsPathRegexTerm.attributes.name == "ansible_match_as_path_regex_term_1" + - query_ansible_match_as_path_regex_term_1.current.0.rtctrlMatchAsPathRegexTerm.attributes.regex == ".*" + + - name: Remove match regex AS-Path term for l3out (check_mode) + aci_match_as_path_regex_term: &match_as_path_regex_term_absent + <<: *aci_match_as_path_regex_term_present + state: absent + check_mode: true + register: cm_remove_match_as_path_regex_term + + - name: Remove match regex AS-Path term for l3out (normal_mode) + aci_match_as_path_regex_term: + <<: *match_as_path_regex_term_absent + register: nm_remove_match_as_path_regex_term + + - name: Remove match regex AS-Path term for l3out again - testing previous Removal + aci_match_as_path_regex_term: + <<: *match_as_path_regex_term_absent + register: nm_remove_match_as_path_regex_term_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_as_path_regex_term is changed + - cm_remove_match_as_path_regex_term.proposed == {} + - nm_remove_match_as_path_regex_term is changed + - nm_remove_match_as_path_regex_term.previous != [] + - nm_remove_match_as_path_regex_term.method == "DELETE" + - nm_remove_match_as_path_regex_term_idempotency is not changed + - nm_remove_match_as_path_regex_term_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/tests/integration/targets/aci_match_community_factor/aliases b/tests/integration/targets/aci_match_community_factor/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_match_community_factor/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_match_community_factor/tasks/main.yml b/tests/integration/targets/aci_match_community_factor/tasks/main.yml new file mode 100644 index 000000000..2cc1f3ac1 --- /dev/null +++ b/tests/integration/targets/aci_match_community_factor/tasks/main.yml @@ -0,0 +1,157 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match rule profile for ansible_tenant tenant + state: present + + - name: Add a match community term + aci_match_community_term: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term + description: match community term for ansible_match_rule match rule profile + state: present + + - name: Add a match community factor (check_mode) + aci_match_community_factor: &aci_match_community_factor_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term + community: regular:as2-nn2:4:15 + scope: non-transitive + description: match community factor 1 for ansible_match_community_term + state: present + check_mode: true + register: cm_add_match_community_factor + + - name: Add a match community factor (normal_mode) + aci_match_community_factor: + <<: *aci_match_community_factor_present + register: nm_add_match_community_factor + + - name: Add the first match community factor again - testing idempotency + aci_match_community_factor: + <<: *aci_match_community_factor_present + register: nm_add_match_community_factor_idempotency + + - name: Add a second match community factor (normal_mode) + aci_match_community_factor: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term + community: regular:as2-nn2:4:16 + description: match community factor 2 for ansible_match_community_term + state: present + register: nm_add_match_community_factor_2 + + - name: Asserts for match community factors creation tasks + assert: + that: + - cm_add_match_community_factor is changed + - cm_add_match_community_factor.previous == [] + - cm_add_match_community_factor.current == [] + - nm_add_match_community_factor is changed + - nm_add_match_community_factor.current.0.rtctrlMatchCommFactor.attributes.community == "regular:as2-nn2:4:15" + - nm_add_match_community_factor_idempotency is not changed + - nm_add_match_community_factor_2 is changed + - nm_add_match_community_factor_2.previous == [] + - nm_add_match_community_factor_2.current.0.rtctrlMatchCommFactor.attributes.community == "regular:as2-nn2:4:16" + + - name: Query all match community factors + aci_match_community_factor: + <<: *aci_info + state: query + register: query_all_match_community_factor + + - name: Query a specific match community factor + aci_match_community_factor: + <<: *aci_match_community_factor_present + state: query + register: query_ansible_match_community_factor_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_community_factor is not changed + - query_all_match_community_factor.current|length >= 2 + - query_ansible_match_community_factor_1 is not changed + - query_ansible_match_community_factor_1.current.0.rtctrlMatchCommFactor.attributes.community == "regular:as2-nn2:4:15" + + - name: Remove match community factor (check_mode) + aci_match_community_factor: &match_community_term_absent + <<: *aci_match_community_factor_present + state: absent + check_mode: true + register: cm_remove_match_community_factor + + - name: Remove match community factor (normal_mode) + aci_match_community_factor: + <<: *match_community_term_absent + register: nm_remove_match_community_factor + + - name: Remove match community factor again - testing previous Removal + aci_match_community_factor: + <<: *match_community_term_absent + register: nm_remove_match_community_factor_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_community_factor is changed + - cm_remove_match_community_factor.proposed == {} + - nm_remove_match_community_factor is changed + - nm_remove_match_community_factor.previous != [] + - nm_remove_match_community_factor.method == "DELETE" + - nm_remove_match_community_factor_idempotency is not changed + - nm_remove_match_community_factor_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/tests/integration/targets/aci_match_community_regex_term/aliases b/tests/integration/targets/aci_match_community_regex_term/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_match_community_regex_term/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_match_community_regex_term/tasks/main.yml b/tests/integration/targets/aci_match_community_regex_term/tasks/main.yml new file mode 100644 index 000000000..c0b12c78c --- /dev/null +++ b/tests/integration/targets/aci_match_community_regex_term/tasks/main.yml @@ -0,0 +1,152 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match rule profile for ansible_tenant tenant + state: present + register: cm_add_match_rule + + - name: Add a match community regex term (check_mode) + aci_match_community_regex_term: &aci_match_community_regex_term_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + name: ansible_community_regex_extended + description: match extended community regex term for ansible_match_rule match rule profile + community_type: extended + regex: .* + state: present + check_mode: true + register: cm_add_match_community_regex_term + + - name: Add a match community regex term (normal_mode) + aci_match_community_regex_term: + <<: *aci_match_community_regex_term_present + register: nm_add_match_community_regex_term + + - name: Add the first match community regex term again - testing idempotency + aci_match_community_regex_term: + <<: *aci_match_community_regex_term_present + register: nm_add_match_community_regex_term_idempotency + + - name: Add a second match community regex term (normal_mode) + aci_match_community_regex_term: &aci_match_community_regex_term_2_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match regular community regex term for ansible_match_rule match rule profile + state: present + register: nm_add_match_community_regex_term_2 + + - name: Asserts for match community regex terms creation tasks + assert: + that: + - cm_add_match_community_regex_term is changed + - cm_add_match_community_regex_term.previous == [] + - cm_add_match_community_regex_term.current == [] + - nm_add_match_community_regex_term is changed + - nm_add_match_community_regex_term.current.0.rtctrlMatchCommRegexTerm.attributes.regex == ".*" + - nm_add_match_community_regex_term.current.0.rtctrlMatchCommRegexTerm.attributes.name == "ansible_community_regex_extended" + - nm_add_match_community_regex_term.current.0.rtctrlMatchCommRegexTerm.attributes.commType == "extended" + - nm_add_match_community_regex_term_idempotency is not changed + - nm_add_match_community_regex_term_2 is changed + - nm_add_match_community_regex_term_2.previous == [] + - nm_add_match_community_regex_term_2.current.0.rtctrlMatchCommRegexTerm.attributes.regex == "" + - nm_add_match_community_regex_term_2.current.0.rtctrlMatchCommRegexTerm.attributes.commType == "regular" + + - name: Query all match community regex terms + aci_match_community_regex_term: + <<: *aci_info + state: query + community_type: extended + register: query_all_match_community_regex_term + + - name: Query ansible_match_community_regex_term_regular + aci_match_community_regex_term: + <<: *aci_match_community_regex_term_2_present + state: query + register: query_ansible_match_community_regex_term_regular + + - name: Asserts query tasks + assert: + that: + - query_all_match_community_regex_term is not changed + - query_all_match_community_regex_term.current.0.rtctrlMatchCommRegexTerm.attributes.commType == "extended" + - query_ansible_match_community_regex_term_regular is not changed + - query_ansible_match_community_regex_term_regular.current.0.rtctrlMatchCommRegexTerm.attributes.regex == "" + - query_ansible_match_community_regex_term_regular.current.0.rtctrlMatchCommRegexTerm.attributes.commType == "regular" + + - name: Remove match community regex term (check_mode) + aci_match_community_regex_term: &match_community_regex_term_absent + <<: *aci_match_community_regex_term_present + state: absent + check_mode: true + register: cm_remove_match_community_regex_term + + - name: Remove match community regex term (normal_mode) + aci_match_community_regex_term: + <<: *match_community_regex_term_absent + register: nm_remove_match_community_regex_term + + - name: Remove match community regex term again - testing previous Removal + aci_match_community_regex_term: + <<: *match_community_regex_term_absent + register: nm_remove_match_community_regex_term_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_community_regex_term is changed + - cm_remove_match_community_regex_term.proposed == {} + - nm_remove_match_community_regex_term is changed + - nm_remove_match_community_regex_term.previous != [] + - nm_remove_match_community_regex_term.method == "DELETE" + - nm_remove_match_community_regex_term_idempotency is not changed + - nm_remove_match_community_regex_term_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent \ No newline at end of file diff --git a/tests/integration/targets/aci_match_community_term/aliases b/tests/integration/targets/aci_match_community_term/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_match_community_term/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_match_community_term/tasks/main.yml b/tests/integration/targets/aci_match_community_term/tasks/main.yml new file mode 100644 index 000000000..aecf89096 --- /dev/null +++ b/tests/integration/targets/aci_match_community_term/tasks/main.yml @@ -0,0 +1,146 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match rule profile for ansible_tenant tenant + state: present + register: cm_add_match_rule + + - name: Add a match community term (check_mode) + aci_match_community_term: &aci_match_community_term_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term_1 + description: match community term 1 for ansible_match_rule match rule profile + state: present + check_mode: true + register: cm_add_match_community_term + + - name: Add a match community term (normal_mode) + aci_match_community_term: + <<: *aci_match_community_term_present + register: nm_add_match_community_term + + - name: Add the first match community term again - testing idempotency + aci_match_community_term: + <<: *aci_match_community_term_present + register: nm_add_match_community_term_idempotency + + - name: Add a second match community term (normal_mode) + aci_match_community_term: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + match_community_term: ansible_match_community_term_2 + description: match community term 2 for ansible_match_rule match rule profile + state: present + register: nm_add_match_community_term_2 + + - name: Asserts for match community terms creation tasks + assert: + that: + - cm_add_match_community_term is changed + - cm_add_match_community_term.previous == [] + - cm_add_match_community_term.current == [] + - nm_add_match_community_term is changed + - nm_add_match_community_term.current.0.rtctrlMatchCommTerm.attributes.name == "ansible_match_community_term_1" + - nm_add_match_community_term_idempotency is not changed + - nm_add_match_community_term_2 is changed + - nm_add_match_community_term_2.previous == [] + - nm_add_match_community_term_2.current.0.rtctrlMatchCommTerm.attributes.name == "ansible_match_community_term_2" + + - name: Query all match community terms + aci_match_community_term: + <<: *aci_info + state: query + register: query_all_match_community_term + + - name: Query ansible_match_community_term_1 + aci_match_community_term: + <<: *aci_match_community_term_present + state: query + register: query_ansible_match_community_term_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_community_term is not changed + - query_all_match_community_term.current|length >= 2 + - query_ansible_match_community_term_1 is not changed + - query_ansible_match_community_term_1.current.0.rtctrlMatchCommTerm.attributes.name == "ansible_match_community_term_1" + + - name: Remove match community term (check_mode) + aci_match_community_term: &match_community_term_absent + <<: *aci_match_community_term_present + state: absent + check_mode: true + register: cm_remove_match_community_term + + - name: Remove match community term (normal_mode) + aci_match_community_term: + <<: *match_community_term_absent + register: nm_remove_match_community_term + + - name: Remove match community term again - testing previous Removal + aci_match_community_term: + <<: *match_community_term_absent + register: nm_remove_match_community_term_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_community_term is changed + - cm_remove_match_community_term.proposed == {} + - nm_remove_match_community_term is changed + - nm_remove_match_community_term.previous != [] + - nm_remove_match_community_term.method == "DELETE" + - nm_remove_match_community_term_idempotency is not changed + - nm_remove_match_community_term_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/tests/integration/targets/aci_match_route_destination/aliases b/tests/integration/targets/aci_match_route_destination/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_match_route_destination/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_match_route_destination/tasks/main.yml b/tests/integration/targets/aci_match_route_destination/tasks/main.yml new file mode 100644 index 000000000..b4d49c2ff --- /dev/null +++ b/tests/integration/targets/aci_match_route_destination/tasks/main.yml @@ -0,0 +1,159 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# Copyright: (c) 2023, Tim Cragg (@timcragg) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: subject profile for ansible_tenant tenant + state: present + register: cm_add_match_rule + + - name: Add a match route destination rule (check_mode) + aci_match_route_destination: &aci_match_route_destination_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match route destination rule 1 for ansible_match_rule match rule profile + ip: 11.11.11.11/24 + aggregate: yes + from_prefix_length: 0 + to_prefix_length: 32 + state: present + check_mode: true + register: cm_add_match_route_destination + + - name: Add a match route destination rule (normal_mode) + aci_match_route_destination: + <<: *aci_match_route_destination_present + register: nm_add_match_route_destination + + - name: Add the first match route destination rule again - testing idempotency + aci_match_route_destination: + <<: *aci_match_route_destination_present + register: nm_add_match_route_destination_idempotency + + - name: Add a second match route destination rule (normal_mode) + aci_match_route_destination: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: match route destination rule 2 for ansible_match_rule match rule profile + ip: 11.11.11.12/24 + state: present + register: nm_add_match_route_destination_2 + + - name: Asserts for match route destination rule creation tasks + assert: + that: + - cm_add_match_route_destination is changed + - cm_add_match_route_destination.previous == [] + - cm_add_match_route_destination.current == [] + - nm_add_match_route_destination is changed + - nm_add_match_route_destination.current.0.rtctrlMatchRtDest.attributes.ip == "11.11.11.11/24" + - nm_add_match_route_destination.current.0.rtctrlMatchRtDest.attributes.aggregate == "yes" + - nm_add_match_route_destination.current.0.rtctrlMatchRtDest.attributes.fromPfxLen == "0" + - nm_add_match_route_destination.current.0.rtctrlMatchRtDest.attributes.toPfxLen == "32" + - nm_add_match_route_destination_idempotency is not changed + - nm_add_match_route_destination_2 is changed + - nm_add_match_route_destination_2.previous == [] + - nm_add_match_route_destination_2.current.0.rtctrlMatchRtDest.attributes.ip == "11.11.11.12/24" + - nm_add_match_route_destination_2.current.0.rtctrlMatchRtDest.attributes.aggregate == "no" + - nm_add_match_route_destination_2.current.0.rtctrlMatchRtDest.attributes.fromPfxLen == "0" + - nm_add_match_route_destination_2.current.0.rtctrlMatchRtDest.attributes.toPfxLen == "0" + + - name: Query all match regex AS-Path terms + aci_match_route_destination: + <<: *aci_info + state: query + register: query_all_match_route_destination + + - name: Query ansible_match_route_destination_1 + aci_match_route_destination: + <<: *aci_match_route_destination_present + state: query + register: query_ansible_match_route_destination_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_route_destination is not changed + - query_all_match_route_destination.current|length >= 2 + - query_ansible_match_route_destination_1 is not changed + - query_ansible_match_route_destination_1.current.0.rtctrlMatchRtDest.attributes.ip == "11.11.11.11/24" + - query_ansible_match_route_destination_1.current.0.rtctrlMatchRtDest.attributes.aggregate == "yes" + - query_ansible_match_route_destination_1.current.0.rtctrlMatchRtDest.attributes.fromPfxLen == "0" + - query_ansible_match_route_destination_1.current.0.rtctrlMatchRtDest.attributes.toPfxLen == "32" + + - name: Remove match route destination rule (check_mode) + aci_match_route_destination: &match_route_destination_absent + <<: *aci_match_route_destination_present + state: absent + check_mode: true + register: cm_remove_match_route_destination + + - name: Remove match route destination rule (normal_mode) + aci_match_route_destination: + <<: *match_route_destination_absent + register: nm_remove_match_route_destination + + - name: Remove match route destination rule again - testing previous Removal + aci_match_route_destination: + <<: *match_route_destination_absent + register: nm_remove_match_route_destination_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_route_destination is changed + - cm_remove_match_route_destination.proposed == {} + - nm_remove_match_route_destination is changed + - nm_remove_match_route_destination.previous != [] + - nm_remove_match_route_destination.method == "DELETE" + - nm_remove_match_route_destination_idempotency is not changed + - nm_remove_match_route_destination_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent \ No newline at end of file diff --git a/tests/integration/targets/aci_match_rule/aliases b/tests/integration/targets/aci_match_rule/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_match_rule/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_match_rule/tasks/main.yml b/tests/integration/targets/aci_match_rule/tasks/main.yml new file mode 100644 index 000000000..23509494c --- /dev/null +++ b/tests/integration/targets/aci_match_rule/tasks/main.yml @@ -0,0 +1,136 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# Copyright: (c) 2023, Tim Cragg (@timcragg) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a match rule profile (check_mode) + aci_match_rule: &aci_match_rule_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule_1 + description: match rule profile 1 for ansible_tenant tenant + state: present + check_mode: true + register: cm_add_match_rule + + - name: Add a match rule profile (normal_mode) + aci_match_rule: + <<: *aci_match_rule_present + register: nm_add_match_rule + + - name: Add the first match rule profile again - testing idempotency + aci_match_rule: + <<: *aci_match_rule_present + register: nm_add_match_rule_idempotency + + - name: Add a second match rule profile (normal_mode) + aci_match_rule: + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule_2 + description: match rule profile 2 for ansible_tenant tenant + state: present + register: nm_add_match_rule_2 + + - name: Asserts for match rule profiles creation tasks + assert: + that: + - cm_add_match_rule is changed + - cm_add_match_rule.previous == [] + - cm_add_match_rule.current == [] + - nm_add_match_rule is changed + - nm_add_match_rule.current.0.rtctrlSubjP.attributes.name == "ansible_match_rule_1" + - nm_add_match_rule_idempotency is not changed + - nm_add_match_rule_2 is changed + - nm_add_match_rule_2.previous == [] + - nm_add_match_rule_2.current.0.rtctrlSubjP.attributes.name == "ansible_match_rule_2" + + - name: Query all match rule profiles + aci_match_rule: + <<: *aci_info + state: query + register: query_all_match_rule + + - name: Query ansible_match_rule_1 + aci_match_rule: + <<: *aci_match_rule_present + state: query + register: query_ansible_match_rule_1 + + - name: Asserts query tasks + assert: + that: + - query_all_match_rule is not changed + - query_all_match_rule.current|length >= 2 + - query_ansible_match_rule_1 is not changed + - query_ansible_match_rule_1.current.0.rtctrlSubjP.attributes.name == "ansible_match_rule_1" + + - name: Remove match rule profile for l3out (check_mode) + aci_match_rule: &match_rule_absent + <<: *aci_match_rule_present + state: absent + check_mode: true + register: cm_remove_match_rule + + - name: Remove match rule profile for l3out (normal_mode) + aci_match_rule: + <<: *match_rule_absent + register: nm_remove_match_rule + + - name: Remove match rule profile for l3out again - testing previous Removal + aci_match_rule: + <<: *match_rule_absent + register: nm_remove_match_rule_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_match_rule is changed + - cm_remove_match_rule.proposed == {} + - nm_remove_match_rule is changed + - nm_remove_match_rule.previous != [] + - nm_remove_match_rule.method == "DELETE" + - nm_remove_match_rule_idempotency is not changed + - nm_remove_match_rule_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/tests/integration/targets/aci_route_control_context/aliases b/tests/integration/targets/aci_route_control_context/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_route_control_context/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_route_control_context/tasks/main.yml b/tests/integration/targets/aci_route_control_context/tasks/main.yml new file mode 100644 index 000000000..83f46e4b8 --- /dev/null +++ b/tests/integration/targets/aci_route_control_context/tasks/main.yml @@ -0,0 +1,197 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# Copyright: (c) 2023, Tim Cragg (@timcragg) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a new action rule profile + aci_tenant_action_rule_profile: &aci_action_rule_present + <<: *aci_info + tenant: ansible_tenant + action_rule: ansible_action_rule + description: Ansible action rule profile for ansible_tenant tenant + state: present + + - name: Add a new L3Out + aci_l3out: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + description: Ansible l3Out for ansible_tenant tenant + domain: ansible_dom + vrf: ansible_vrf + state: present + + - name: Add a route control profile for l3out + aci_route_control_profile: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + route_control_profile: ansible_rtctrl_profile_l3out + description: Ansible Route Control Profile for ansible_l3out l3Out + state: present + + - name: Add a subject profile + aci_match_rule: &aci_match_rule_present + <<: *aci_info + tenant: ansible_tenant + match_rule: ansible_match_rule + description: Ansible Subject Profile for ansible_tenant tenant + state: present + + - name: Add a route control context policy for l3out (check_mode) + aci_route_control_context: &aci_route_control_context_present + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + route_control_profile: ansible_rtctrl_profile_l3out + route_control_context: ansible_route_control_context_l3out + description: Ansible route control Context Policy for ansible_rtctrl_profile_l3out route control profile + match_rule: ansible_match_rule + action_rule: ansible_action_rule + action: deny + order: 5 + state: present + check_mode: true + register: cm_add_route_control_context_l3out + + - name: Add a route control context policy again (normal_mode) + aci_route_control_context: + <<: *aci_route_control_context_present + register: nm_add_route_control_context_l3out + + - name: Add a route control context policy again - testing idempotency + aci_route_control_context: + <<: *aci_route_control_context_present + register: nm_add_route_control_context_l3out_idempotency + + - name: Add a route control profile for tenant + aci_route_control_profile: + <<: *aci_info + tenant: ansible_tenant + route_control_profile: ansible_rtctrl_profile_tenant + description: Route Control Profile for ansible_tenant tenant + state: present + + - name: Add a route control context policy for tenant + aci_route_control_context: + <<: *aci_info + tenant: ansible_tenant + route_control_profile: ansible_rtctrl_profile_tenant + route_control_context: ansible_route_control_context_tenant + description: Ansible route control Context Policy for ansible_rtctrl_profile_tenant route control profile + state: present + register: nm_add_route_control_context_tenant + + - name: Asserts for route control profiles creation tasks + assert: + that: + - cm_add_route_control_context_l3out is changed + - cm_add_route_control_context_l3out.previous == [] + - cm_add_route_control_context_l3out.current == [] + - nm_add_route_control_context_l3out is changed + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.attributes.name == "ansible_route_control_context_l3out" + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.attributes.action == "deny" + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.attributes.order == "5" + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.children.0.rtctrlScope.children.0.rtctrlRsScopeToAttrP.attributes.tnRtctrlAttrPName == "ansible_action_rule" + - nm_add_route_control_context_l3out.current.0.rtctrlCtxP.children.1.rtctrlRsCtxPToSubjP.attributes.tnRtctrlSubjPName == "ansible_match_rule" + - nm_add_route_control_context_l3out_idempotency is not changed + - nm_add_route_control_context_tenant is changed + - nm_add_route_control_context_tenant.previous == [] + - nm_add_route_control_context_tenant.current.0.rtctrlCtxP.attributes.name == "ansible_route_control_context_tenant" + - nm_add_route_control_context_tenant.current.0.rtctrlCtxP.attributes.action == "permit" + - nm_add_route_control_context_tenant.current.0.rtctrlCtxP.attributes.order == "0" + + - name: Query all route control context policies + aci_route_control_context: + <<: *aci_info + state: query + register: query_all_route_control_context + + - name: Query ansible_route_control_context_l3out route control context policy + aci_route_control_context: + <<: *aci_info + route_control_context: ansible_route_control_context_l3out + state: query + register: query_route_control_context_l3out + + - name: Asserts query tasks + assert: + that: + - query_all_route_control_context is not changed + - query_all_route_control_context.current|length >= 2 + - query_route_control_context_l3out is not changed + - query_route_control_context_l3out.current.0.rtctrlCtxP.attributes.name == "ansible_route_control_context_l3out" + - query_route_control_context_l3out.current.0.rtctrlCtxP.attributes.action == "deny" + - query_route_control_context_l3out.current.0.rtctrlCtxP.attributes.order == "5" + - query_route_control_context_l3out.current.0.rtctrlCtxP.children.0.rtctrlScope.children.0.rtctrlRsScopeToAttrP.attributes.tDn == "uni/tn-ansible_tenant/attr-ansible_action_rule" + - query_route_control_context_l3out.current.0.rtctrlCtxP.children.1.rtctrlRsCtxPToSubjP.attributes.tDn == "uni/tn-ansible_tenant/subj-ansible_match_rule" + + - name: Remove route control context policy for l3out (check_mode) + aci_route_control_context: &aci_route_control_context_absent + <<: *aci_route_control_context_present + state: absent + check_mode: true + register: cm_remove_route_control_context + + - name: Remove route control context policy for l3out (normal_mode) + aci_route_control_context: + <<: *aci_route_control_context_absent + register: nm_remove_remove_route_control_context + + - name: Remove route control profile for l3out again - testing previous Removal + aci_route_control_context: + <<: *aci_route_control_context_absent + register: nm_remove_route_control_context_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_route_control_context is changed + - cm_remove_route_control_context.proposed == {} + - nm_remove_remove_route_control_context is changed + - nm_remove_remove_route_control_context.previous != [] + - nm_remove_remove_route_control_context.method == "DELETE" + - nm_remove_route_control_context_idempotency is not changed + - nm_remove_route_control_context_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent diff --git a/tests/integration/targets/aci_route_control_profile/aliases b/tests/integration/targets/aci_route_control_profile/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_route_control_profile/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_route_control_profile/tasks/main.yml b/tests/integration/targets/aci_route_control_profile/tasks/main.yml new file mode 100644 index 000000000..db0022100 --- /dev/null +++ b/tests/integration/targets/aci_route_control_profile/tasks/main.yml @@ -0,0 +1,145 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Gaspard Micol (@gmicol) +# Copyright: (c) 2023, Tim Cragg (@timcragg) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Test that we have an ACI APIC host, ACI username and ACI password + fail: + msg: 'Please define the following variables: aci_hostname, aci_username and aci_password.' + when: aci_hostname is not defined or aci_username is not defined or aci_password is not defined + +- name: Set vars + set_fact: + aci_info: &aci_info + host: "{{ aci_hostname }}" + username: "{{ aci_username }}" + password: "{{ aci_password }}" + validate_certs: '{{ aci_validate_certs | default(false) }}' + use_ssl: '{{ aci_use_ssl | default(true) }}' + use_proxy: '{{ aci_use_proxy | default(true) }}' + output_level: debug + +# CLEAN ENVIRONMENT +- name: Remove the ansible_tenant + aci_tenant: &aci_tenant_absent + <<: *aci_info + tenant: ansible_tenant + state: absent + +- name: Verify Cloud and Non-Cloud Sites in use. + include_tasks: ../../../../../../integration/targets/aci_cloud_provider/tasks/main.yml + +- name: Execute tasks only for non-cloud sites + when: query_cloud.current == [] # This condition will execute only non-cloud sites + block: # block specifies execution of tasks within, based on conditions + - name: Add a new tenant + aci_tenant: &aci_tenant_present + <<: *aci_info + tenant: ansible_tenant + description: Ansible tenant + state: present + + - name: Add a new L3Out + aci_l3out: + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + description: L3Out for ansible_tenant tenant + domain: ansible_dom + vrf: ansible_vrf + state: present + + - name: Add route control profile for l3out (check_mode) + aci_route_control_profile: &aci_route_control_profile_present + <<: *aci_info + tenant: ansible_tenant + l3out: ansible_l3out + route_control_profile: ansible_rtctrl_profile_l3out + description: Route Control Profile for ansible_l3out L3Out + auto_continue: no + policy_type: combinable + state: present + check_mode: true + register: cm_add_route_control_profile + + - name: Add route control profile for l3out (normal_mode) + aci_route_control_profile: + <<: *aci_route_control_profile_present + register: nm_add_route_control_profile + + - name: Add route control profile for l3out again - testing idempotency + aci_route_control_profile: + <<: *aci_route_control_profile_present + register: nm_add_route_control_profile_idempotency + + - name: Add route control profile for tenant (normal_mode) + aci_route_control_profile: + <<: *aci_info + tenant: ansible_tenant + route_control_profile: ansible_rtctrl_profile_tenant + description: Route Control Profile for ansible_tenant tenant + state: present + register: nm_add_route_control_profile_2 + + - name: Asserts for route control profiles creation tasks + assert: + that: + - cm_add_route_control_profile is changed + - cm_add_route_control_profile.previous == [] + - cm_add_route_control_profile.current == [] + - nm_add_route_control_profile is changed + - nm_add_route_control_profile_idempotency is not changed + - nm_add_route_control_profile_2 is changed + - nm_add_route_control_profile_2.previous == [] + + - name: Query all route control profiles + aci_route_control_profile: + <<: *aci_info + state: query + register: query_all_route_control_profile + + - name: Query ansible_rtctrl_profile_l3out + aci_route_control_profile: + <<: *aci_route_control_profile_present + state: query + register: query_ansible_rtctrl_profile_l3out + + - name: Asserts query tasks + assert: + that: + - query_all_route_control_profile is not changed + - query_all_route_control_profile.current|length >= 2 + + - name: Remove route control profile for l3out (check_mode) + aci_route_control_profile: &route_control_profile_absent + <<: *aci_route_control_profile_present + state: absent + check_mode: true + register: cm_remove_route_control_profile + + - name: Remove route control profile for l3out (normal_mode) + aci_route_control_profile: + <<: *route_control_profile_absent + register: nm_remove_route_control_profile + + - name: Remove route control profile for l3out again - testing previous Removal + aci_route_control_profile: + <<: *route_control_profile_absent + register: nm_remove_route_control_profile_idempotency + + - name: Asserts deletion tasks + assert: + that: + - cm_remove_route_control_profile is changed + - cm_remove_route_control_profile.proposed == {} + - nm_remove_route_control_profile is changed + - nm_remove_route_control_profile.previous != [] + - nm_remove_route_control_profile.method == "DELETE" + - nm_remove_route_control_profile_idempotency is not changed + - nm_remove_route_control_profile_idempotency.previous == [] + + - name: Remove the ansible_tenant - cleanup before ending tests + aci_tenant: + <<: *aci_tenant_present + state: absent