From b7de5cce22a085c251b1b1a0843239bf0fa1e2a9 Mon Sep 17 00:00:00 2001 From: akinross Date: Tue, 4 Apr 2023 14:01:35 +0200 Subject: [PATCH 1/7] [minor_change] add aci_access_span_filter_group module for access span filter group support --- .../modules/aci_access_span_filter_group.py | 246 ++++++++++++++++++ .../aci_access_span_filter_group/aliases | 2 + .../tasks/main.yml | 143 ++++++++++ 3 files changed, 391 insertions(+) create mode 100644 plugins/modules/aci_access_span_filter_group.py create mode 100644 tests/integration/targets/aci_access_span_filter_group/aliases create mode 100644 tests/integration/targets/aci_access_span_filter_group/tasks/main.yml diff --git a/plugins/modules/aci_access_span_filter_group.py b/plugins/modules/aci_access_span_filter_group.py new file mode 100644 index 000000000..6e8b22892 --- /dev/null +++ b/plugins/modules/aci_access_span_filter_group.py @@ -0,0 +1,246 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_access_span_filter_group +short_description: Manage Access SPAN filter groups (span:FilterGrp) +description: +- Manage Access SPAN filter groups on Cisco ACI fabrics. +options: + filter_group: + description: + - The name of the Access SPAN filter group. + type: str + aliases: [ name ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +seealso: +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:DestGrp). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a Access SPAN filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: present + delegate_to: localhost + +- name: Remove a Access SPAN filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: absent + delegate_to: localhost + +- name: Query a Access SPAN filter group + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + state: query + delegate_to: localhost + +- name: Query all Access SPAN filter groups + cisco.aci.aci_access_span_filter_group: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + filter_group=dict(type="str", aliases=["name"]), # Not required for querying all objects + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["filter_group"]], + ["state", "present", ["filter_group"]], + ], + ) + + aci = ACIModule(module) + + filter_group = module.params.get("filter_group") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="infra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="spanFilterGrp", + aci_rn="filtergrp-{0}".format(filter_group), + module_object=filter_group, + target_filter={"name": filter_group}, + ), + child_classes=["spanFilterEntry"], + ) + + aci.get_existing() + + if state == "present": + aci.payload(aci_class="spanFilterGrp", class_config=dict(name=filter_group, nameAlias=name_alias)) + aci.get_diff(aci_class="spanFilterGrp") + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_access_span_filter_group/aliases b/tests/integration/targets/aci_access_span_filter_group/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_access_span_filter_group/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml b/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml new file mode 100644 index 000000000..11ce6aa57 --- /dev/null +++ b/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml @@ -0,0 +1,143 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (akinross@cisco.com) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- 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 + ansible.builtin.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: '{{ aci_output_level | default("info") }}' + +- 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 == [] + block: + + # CLEAN TEST ENVIRONMENT + + - name: Query all access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_for_clean + + - name: Clean access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item.spanFilterGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + + # TEST CREATE FILTER GROUPS + + - name: Add access span filter group ( check mode ) + cisco.aci.aci_access_span_filter_group: &add_ansible_filter_group_1 + <<: *aci_info + filter_group: ansible_group_1 + state: present + check_mode: true + register: cm_add_ansible_group_1 + + - name: Add access span filter group + cisco.aci.aci_access_span_filter_group: + <<: *add_ansible_filter_group_1 + register: nm_add_ansible_group_1 + + - name: Add access span filter group again + cisco.aci.aci_access_span_filter_group: + <<: *add_ansible_filter_group_1 + register: nm_add_ansible_group_1_again + + - name: Verify add access span filter group + ansible.builtin.assert: + that: + - cm_add_ansible_group_1 is changed + - cm_add_ansible_group_1.current == [] + - cm_add_ansible_group_1.previous == [] + - cm_add_ansible_group_1.proposed.spanFilterGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1 is changed + - nm_add_ansible_group_1.previous == [] + - nm_add_ansible_group_1.current.0.spanFilterGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1_again is not changed + - nm_add_ansible_group_1_again.previous.0.spanFilterGrp.attributes.name == "ansible_group_1" + - nm_add_ansible_group_1_again.current.0.spanFilterGrp.attributes.name == "ansible_group_1" + + - name: Add two more access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item }}" + state: present + loop: + - ansible_group_2 + - ansible_group_3 + + # TEST QUERY FILTER GROUPS + + - name: Query access span filter group + cisco.aci.aci_access_span_filter_group: + <<: *add_ansible_filter_group_1 + state: query + register: query_one + + - name: Query all access span filter group ( class query ) + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_all + + - name: Verify access span filter group queries + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanFilterGrp.attributes.name == "ansible_group_1" + - query_all is not changed + - query_all.current | length == 3 + - query_all.current.0.spanFilterGrp.attributes.name == "ansible_group_1" + - query_all.current.1.spanFilterGrp.attributes.name == "ansible_group_2" + - query_all.current.2.spanFilterGrp.attributes.name == "ansible_group_3" + + # TEST REMOVAL FILTER GROUPS + + - name: Remove access span filter group ( check mode ) + cisco.aci.aci_access_span_filter_group: &remove_ansible_filter_group_1 + <<: *add_ansible_filter_group_1 + state: absent + check_mode: true + register: cm_remove_ansible_filter_group_1 + + - name: Remove access span filter group + cisco.aci.aci_access_span_filter_group: + <<: *remove_ansible_filter_group_1 + register: nm_remove_ansible_filter_group_1 + + - name: Remove access span filter group again + cisco.aci.aci_access_span_filter_group: + <<: *remove_ansible_filter_group_1 + register: nm_remove_ansible_filter_group_1_again + + - name: Verify access span filter group removal + ansible.builtin.assert: + that: + - cm_remove_ansible_filter_group_1 is changed + - cm_remove_ansible_filter_group_1.proposed == {} + - nm_remove_ansible_filter_group_1 is changed + - nm_remove_ansible_filter_group_1.previous.0.spanFilterGrp.attributes.name == "ansible_group_1" + - nm_remove_ansible_filter_group_1.current == [] + - nm_remove_ansible_filter_group_1_again is not changed + - nm_remove_ansible_filter_group_1_again.previous == [] + - nm_remove_ansible_filter_group_1_again.current == [] From 2af8a397d17fa32c63770d1d460f6e048a437bb9 Mon Sep 17 00:00:00 2001 From: akinross Date: Tue, 4 Apr 2023 14:53:48 +0200 Subject: [PATCH 2/7] [minor_change] add aci_access_span_filter_group_entry module for access span filter group support --- .../aci_access_span_filter_group_entry.py | 346 ++++++++++++++++++ .../aliases | 2 + .../tasks/main.yml | 250 +++++++++++++ 3 files changed, 598 insertions(+) create mode 100644 plugins/modules/aci_access_span_filter_group_entry.py create mode 100644 tests/integration/targets/aci_access_span_filter_group_entry/aliases create mode 100644 tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml diff --git a/plugins/modules/aci_access_span_filter_group_entry.py b/plugins/modules/aci_access_span_filter_group_entry.py new file mode 100644 index 000000000..6af77e594 --- /dev/null +++ b/plugins/modules/aci_access_span_filter_group_entry.py @@ -0,0 +1,346 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2023, Akini Ross +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "certified"} + +DOCUMENTATION = r""" +--- +module: aci_access_span_filter_group_entry +short_description: Manage Access SPAN filter group entries (span:FilterEntry) +description: +- Manage Access SPAN filter group entries on Cisco ACI fabrics. +options: + filter_group: + description: + - The name of the Access SPAN filter group. + type: str + source_ip: + description: + - The source IP Prefix. + type: str + destination_ip: + description: + - The destination IP Prefix. + type: str + first_src_port: + description: + - The first source port (from port). + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ unspecified, dns, ftp, http, https, pop3, rtsp, smtp, ssh ] + last_src_port: + description: + - The last source port (to port). + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ unspecified, dns, ftp, http, https, pop3, rtsp, smtp, ssh ] + first_dest_port: + description: + - The first destination port (from port). + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ unspecified, dns, ftp, http, https, pop3, rtsp, smtp, ssh ] + last_dest_port: + description: + - The last destination port (to port). + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ unspecified, dns, ftp, http, https, pop3, rtsp, smtp, ssh ] + ip_protocol: + description: + - The IP Protocol. + - The APIC defaults to C(unspecified) when unset during creation. + type: str + choices: [ unspecified, egp, eigrp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp ] + state: + description: + - Use C(present) or C(absent) for adding or removing. + - Use C(query) for listing an object or multiple objects. + type: str + choices: [ absent, present, query ] + default: present + name_alias: + description: + - The alias for the current object. This relates to the nameAlias field in ACI. + type: str +extends_documentation_fragment: +- cisco.aci.aci +- cisco.aci.annotation + +notes: +- The C(filter_group) used must exist before using this module in your playbook. + The M(cisco.aci.aci_access_span_filter_group) module can be used for this. +seealso: +- module: cisco.aci.aci_access_span_filter_group +- name: APIC Management Information Model reference + description: More information about the internal APIC class B(span:FilterEntry). + link: https://developer.cisco.com/docs/apic-mim-ref/ +author: +- Akini Ross (@akinross) +""" + +EXAMPLES = r""" +- name: Add a Access SPAN filter entry + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: present + delegate_to: localhost + +- name: Remove a Access SPAN filter entry + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: absent + delegate_to: localhost + +- name: Query a Access SPAN filter group + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + filter_group: group1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + state: query + delegate_to: localhost + +- name: Query all Access SPAN filter groups + cisco.aci.aci_access_span_filter_group_entry: + host: apic + username: admin + password: SomeSecretPassword + state: query + delegate_to: localhost +""" + +RETURN = r""" +current: + description: The existing configuration from the APIC after the module has finished + returned: success + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +error: + description: The error information as returned from the APIC + returned: failure + type: dict + sample: + { + "code": "122", + "text": "unknown managed object class foo" + } +raw: + description: The raw output returned by the APIC REST API (xml or json) + returned: parse error + type: str + sample: '' +sent: + description: The actual/minimal configuration pushed to the APIC + returned: info + type: list + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment" + } + } + } +previous: + description: The original configuration from the APIC before the module has started + returned: info + type: list + sample: + [ + { + "fvTenant": { + "attributes": { + "descr": "Production", + "dn": "uni/tn-production", + "name": "production", + "nameAlias": "", + "ownerKey": "", + "ownerTag": "" + } + } + } + ] +proposed: + description: The assembled configuration from the user-provided parameters + returned: info + type: dict + sample: + { + "fvTenant": { + "attributes": { + "descr": "Production environment", + "name": "production" + } + } + } +filter_string: + description: The filter string used for the request + returned: failure or debug + type: str + sample: ?rsp-prop-include=config-only +method: + description: The HTTP method used for the request to the APIC + returned: failure or debug + type: str + sample: POST +response: + description: The HTTP response from the APIC + returned: failure or debug + type: str + sample: OK (30 bytes) +status: + description: The HTTP status from the APIC + returned: failure or debug + type: int + sample: 200 +url: + description: The HTTP url used for the request to the APIC + returned: failure or debug + type: str + sample: https://10.11.12.13/api/mo/uni/tn-production.json +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec + + +PORT_CHOICES = ["unspecified", "dns", "ftp", "http", "https", "pop3", "rtsp", "smtp", "ssh"] +PROTOCOL_CHOICES = ["unspecified", "egp", "eigrp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp"] +CHOICE_MAPPING = {"ftp": "ftpData"} + + +def main(): + argument_spec = aci_argument_spec() + argument_spec.update(aci_annotation_spec()) + argument_spec.update( + filter_group=dict(type="str"), # Not required for querying all objects + source_ip=dict(type="str"), # Not required for querying all objects + destination_ip=dict(type="str"), # Not required for querying all objects + first_src_port=dict(type="str", choices=PORT_CHOICES), + last_src_port=dict(type="str", choices=PORT_CHOICES), + first_dest_port=dict(type="str", choices=PORT_CHOICES), + last_dest_port=dict(type="str", choices=PORT_CHOICES), + ip_protocol=dict(type="str", choices=PROTOCOL_CHOICES), + name_alias=dict(type="str"), + state=dict(type="str", default="present", choices=["absent", "present", "query"]), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_if=[ + ["state", "absent", ["filter_group", "source_ip", "destination_ip"]], + ["state", "present", ["filter_group", "source_ip", "destination_ip"]], + ], + ) + + aci = ACIModule(module) + + filter_group = module.params.get("filter_group") + source_ip = module.params.get("source_ip") + destination_ip = module.params.get("destination_ip") + first_src_port = module.params.get("first_src_port") + last_src_port = module.params.get("last_src_port") + first_dest_port = module.params.get("first_dest_port") + last_dest_port = module.params.get("last_dest_port") + ip_protocol = module.params.get("ip_protocol") + state = module.params.get("state") + name_alias = module.params.get("name_alias") + + aci.construct_url( + root_class=dict( + aci_class="infra", + aci_rn="infra", + ), + subclass_1=dict( + aci_class="spanFilterGrp", + aci_rn="filtergrp-{0}".format(filter_group), + module_object=filter_group, + target_filter={"name": filter_group}, + ), + subclass_2=dict( + aci_class="spanFilterEntry", + aci_rn="proto-{0}-src-[{1}]-dst-[{2}]-srcPortFrom-{3}-srcPortTo-{4}-dstPortFrom-{5}-dstPortTo-{6}".format( + ip_protocol if module.params.get("ip_protocol") else "unspecified", + source_ip, + destination_ip, + CHOICE_MAPPING.get(first_src_port, first_src_port) if first_src_port else "unspecified", + CHOICE_MAPPING.get(last_src_port, last_src_port) if last_src_port else "unspecified", + CHOICE_MAPPING.get(first_dest_port, first_dest_port) if first_dest_port else "unspecified", + CHOICE_MAPPING.get(last_dest_port, last_dest_port) if last_dest_port else "unspecified", + ), + target_filter={ + "dstAddr": destination_ip, + "dstPortFrom": first_dest_port, + "dstPortTo": last_dest_port, + "ipProto": ip_protocol, + "srcAddr": source_ip, + "srcPortFrom": first_src_port, + "srcPortTo": last_src_port, + }, + ), + ) + + aci.get_existing() + + if state == "present": + aci.payload( + aci_class="spanFilterEntry", + class_config=dict( + dstAddr=destination_ip, + dstPortFrom=first_dest_port, + dstPortTo=last_dest_port, + ipProto=ip_protocol, + nameAlias=name_alias, + srcAddr=source_ip, + srcPortFrom=first_src_port, + srcPortTo=last_src_port, + ), + ) + + aci.get_diff(aci_class="spanFilterEntry") + + aci.post_config() + + elif state == "absent": + aci.delete_config() + + aci.exit_json() + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/aci_access_span_filter_group_entry/aliases b/tests/integration/targets/aci_access_span_filter_group_entry/aliases new file mode 100644 index 000000000..209b793f9 --- /dev/null +++ b/tests/integration/targets/aci_access_span_filter_group_entry/aliases @@ -0,0 +1,2 @@ +# No ACI simulator yet, so not enabled +# unsupported diff --git a/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml b/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml new file mode 100644 index 000000000..df976d5a6 --- /dev/null +++ b/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml @@ -0,0 +1,250 @@ +# Test code for the ACI modules +# Copyright: (c) 2023, Akini Ross (akinross@cisco.com) + +# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt) + +- 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 + ansible.builtin.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: '{{ aci_output_level | default("info") }}' + +- 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 == [] + block: + + # CLEAN TEST ENVIRONMENT + + - name: Query all access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + state: query + register: query_for_clean + + - name: Clean access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item.spanFilterGrp.attributes.name }}" + state: absent + loop: "{{ query_for_clean.current }}" + + - name: Add access span filter group + cisco.aci.aci_access_span_filter_group: &add_ansible_filter_group_1 + <<: *aci_info + filter_group: ansible_group_1 + state: present + + - name: Add access span filter group + cisco.aci.aci_access_span_filter_group: &add_ansible_filter_group_2 + <<: *aci_info + filter_group: ansible_group_2 + state: present + + # TEST CREATE FILTER GROUP ENTRIES + + - name: Add access span filter group entry ( checkmode ) + cisco.aci.aci_access_span_filter_group_entry: &add_ansible_filter_group_entry_1 + <<: *add_ansible_filter_group_1 + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + first_src_port: http + last_src_port: https + check_mode: true + register: cm_add_ansible_filter_group_entry_1 + + - name: Add access span filter group entry + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_entry_1 + register: nm_add_ansible_filter_group_entry_1 + + - name: Add access span filter group entry again + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_entry_1 + register: nm_add_ansible_filter_group_entry_1_again + + - name: Add access span filter group entry + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_1 + source_ip: 1.1.1.1 + destination_ip: 3.3.3.3 + first_src_port: http + last_src_port: https + first_dest_port: http + last_dest_port: https + ip_protocol: tcp + register: nm_add_ansible_filter_group_entry_2 + + - name: Add access span filter group entry + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_1 + source_ip: 2.2.2.2 + destination_ip: 5.5.5.5 + register: nm_add_ansible_filter_group_entry_3 + + - name: Add access span filter group entry in different filter group + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_2 + source_ip: 2.2.2.2 + destination_ip: 5.5.5.5 + register: nm_add_ansible_filter_group_entry_4 + + - name: Verify add access span filter group entires + ansible.builtin.assert: + that: + - cm_add_ansible_filter_group_entry_1 is changed + - cm_add_ansible_filter_group_entry_1.previous == [] + - cm_add_ansible_filter_group_entry_1.current == [] + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.dstAddr == "2.2.2.2" + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.srcPortFrom == "http" + - cm_add_ansible_filter_group_entry_1.proposed.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_1 is changed + - nm_add_ansible_filter_group_entry_1.previous == [] + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.dstAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.srcPortFrom == "http" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_1.current.0.spanFilterEntry.attributes.ipProto == "unspecified" + - nm_add_ansible_filter_group_entry_1_again is not changed + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.dstAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.srcPortFrom == "http" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.previous.0.spanFilterEntry.attributes.ipProto == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.dstAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.srcPortFrom == "http" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_1_again.current.0.spanFilterEntry.attributes.ipProto == "unspecified" + - nm_add_ansible_filter_group_entry_2 is changed + - nm_add_ansible_filter_group_entry_2.previous == [] + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-tcp-src-[1.1.1.1]-dst-[3.3.3.3]-srcPortFrom-http-srcPortTo-https-dstPortFrom-http-dstPortTo-https" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.dstAddr == "3.3.3.3" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.srcAddr == "1.1.1.1" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.srcPortFrom == "http" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.srcPortTo == "https" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.dstPortFrom == "http" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.dstPortTo == "https" + - nm_add_ansible_filter_group_entry_2.current.0.spanFilterEntry.attributes.ipProto == "tcp" + - nm_add_ansible_filter_group_entry_3 is changed + - nm_add_ansible_filter_group_entry_3.previous == [] + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[2.2.2.2]-dst-[5.5.5.5]-srcPortFrom-unspecified-srcPortTo-unspecified-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.dstAddr == "5.5.5.5" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.srcAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.srcPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.srcPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_3.current.0.spanFilterEntry.attributes.ipProto == "unspecified" + - nm_add_ansible_filter_group_entry_4 is changed + - nm_add_ansible_filter_group_entry_4.previous == [] + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_2/proto-unspecified-src-[2.2.2.2]-dst-[5.5.5.5]-srcPortFrom-unspecified-srcPortTo-unspecified-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.dstAddr == "5.5.5.5" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.srcAddr == "2.2.2.2" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.srcPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.srcPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.dstPortFrom == "unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.dstPortTo == "unspecified" + - nm_add_ansible_filter_group_entry_4.current.0.spanFilterEntry.attributes.ipProto == "unspecified" + + # TEST QUERY FILTER GROUP ENTRIES + + - name: Query one specific access span filter group entry + cisco.aci.aci_access_span_filter_group_entry: + <<: *aci_info + source_ip: 1.1.1.1 + destination_ip: 2.2.2.2 + first_src_port: http + last_src_port: https + state: query + register: query_one + + - name: Query all access span filter group entries of one entire group + cisco.aci.aci_access_span_filter_group_entry: + <<: *add_ansible_filter_group_1 + state: query + register: query_one_group + + - name: Query all access span filter group entries ( class query ) + cisco.aci.aci_access_span_filter_group_entry: + <<: *aci_info + state: query + register: query_all + + - name: Query access span filter group entries that match source_ip and first_dest_port + cisco.aci.aci_access_span_filter_group_entry: + <<: *aci_info + source_ip: 1.1.1.1 + first_src_port: http + state: query + register: query_match + + - name: Verify access span filter group queries + ansible.builtin.assert: + that: + - query_one is not changed + - query_one.current | length == 1 + - query_one.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - query_one_group is not changed + - query_one_group.current.0.spanFilterGrp.children | length == 3 + - query_all is not changed + - query_all.current | length == 4 + - query_match is not changed + - query_match.current | length == 2 + - query_match.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - query_match.current.1.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-tcp-src-[1.1.1.1]-dst-[3.3.3.3]-srcPortFrom-http-srcPortTo-https-dstPortFrom-http-dstPortTo-https" + + # TEST REMOVAL FILTER GROUP ENTRIES + + - name: Remove access span filter group ( checkmode ) + cisco.aci.aci_access_span_filter_group_entry: &remove_ansible_filter_group_entry_1 + <<: *add_ansible_filter_group_entry_1 + state: absent + check_mode: true + register: cm_remove_ansible_filter_group_1_entry + + - name: Remove access span filter group + cisco.aci.aci_access_span_filter_group_entry: + <<: *remove_ansible_filter_group_entry_1 + register: nm_remove_ansible_filter_group_1_entry + + - name: Remove access span filter group again + cisco.aci.aci_access_span_filter_group_entry: + <<: *remove_ansible_filter_group_entry_1 + register: nm_remove_ansible_filter_group_1_entry_again + + - name: Verify access span filter group entries removal + ansible.builtin.assert: + that: + - cm_remove_ansible_filter_group_1_entry is changed + - cm_remove_ansible_filter_group_1_entry.proposed == {} + - nm_remove_ansible_filter_group_1_entry is changed + - nm_remove_ansible_filter_group_1_entry.current == [] + - nm_remove_ansible_filter_group_1_entry.previous.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - nm_remove_ansible_filter_group_1_entry_again is not changed + - nm_remove_ansible_filter_group_1_entry_again.current == [] + - nm_remove_ansible_filter_group_1_entry_again.previous == [] From 2854937b0ffa85ca46f4fe7f6069e57a8853bb10 Mon Sep 17 00:00:00 2001 From: akinross Date: Thu, 6 Apr 2023 11:07:33 +0200 Subject: [PATCH 3/7] [ignore] add function for getting port value from mapping --- .../modules/aci_access_span_filter_group_entry.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/modules/aci_access_span_filter_group_entry.py b/plugins/modules/aci_access_span_filter_group_entry.py index 6af77e594..548b67e6b 100644 --- a/plugins/modules/aci_access_span_filter_group_entry.py +++ b/plugins/modules/aci_access_span_filter_group_entry.py @@ -243,6 +243,10 @@ CHOICE_MAPPING = {"ftp": "ftpData"} +def get_port_value(port): + return CHOICE_MAPPING.get(port, port) if port else "unspecified" + + def main(): argument_spec = aci_argument_spec() argument_spec.update(aci_annotation_spec()) @@ -298,10 +302,10 @@ def main(): ip_protocol if module.params.get("ip_protocol") else "unspecified", source_ip, destination_ip, - CHOICE_MAPPING.get(first_src_port, first_src_port) if first_src_port else "unspecified", - CHOICE_MAPPING.get(last_src_port, last_src_port) if last_src_port else "unspecified", - CHOICE_MAPPING.get(first_dest_port, first_dest_port) if first_dest_port else "unspecified", - CHOICE_MAPPING.get(last_dest_port, last_dest_port) if last_dest_port else "unspecified", + get_port_value(first_src_port), + get_port_value(last_src_port), + get_port_value(first_dest_port), + get_port_value(last_dest_port), ), target_filter={ "dstAddr": destination_ip, From ce34498e78e5a02f0b3839cf512b3363bd418386 Mon Sep 17 00:00:00 2001 From: akinross Date: Thu, 6 Apr 2023 11:08:36 +0200 Subject: [PATCH 4/7] [ignore] adjust task descriptions and clean up filter groups at test completion --- .../tasks/main.yml | 11 ++++++++ .../tasks/main.yml | 27 +++++++++++++------ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml b/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml index 11ce6aa57..c071f747a 100644 --- a/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml +++ b/tests/integration/targets/aci_access_span_filter_group/tasks/main.yml @@ -141,3 +141,14 @@ - nm_remove_ansible_filter_group_1_again is not changed - nm_remove_ansible_filter_group_1_again.previous == [] - nm_remove_ansible_filter_group_1_again.current == [] + + # CLEAN TEST ENVIRONMENT + + - name: Clean created access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item }}" + state: absent + loop: + - ansible_group_2 + - ansible_group_3 \ No newline at end of file diff --git a/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml b/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml index df976d5a6..f94be31d9 100644 --- a/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml +++ b/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml @@ -42,13 +42,13 @@ state: absent loop: "{{ query_for_clean.current }}" - - name: Add access span filter group + - name: Add access span filter group 1 cisco.aci.aci_access_span_filter_group: &add_ansible_filter_group_1 <<: *aci_info filter_group: ansible_group_1 state: present - - name: Add access span filter group + - name: Add access span filter group 2 cisco.aci.aci_access_span_filter_group: &add_ansible_filter_group_2 <<: *aci_info filter_group: ansible_group_2 @@ -56,7 +56,7 @@ # TEST CREATE FILTER GROUP ENTRIES - - name: Add access span filter group entry ( checkmode ) + - name: Add access span filter group entry 1 to ansible_group_1 ( checkmode ) cisco.aci.aci_access_span_filter_group_entry: &add_ansible_filter_group_entry_1 <<: *add_ansible_filter_group_1 source_ip: 1.1.1.1 @@ -66,17 +66,17 @@ check_mode: true register: cm_add_ansible_filter_group_entry_1 - - name: Add access span filter group entry + - name: Add access span filter group entry 1 to ansible_group_1 cisco.aci.aci_access_span_filter_group_entry: <<: *add_ansible_filter_group_entry_1 register: nm_add_ansible_filter_group_entry_1 - - name: Add access span filter group entry again + - name: Add access span filter group entry 1 to ansible_group_1 again cisco.aci.aci_access_span_filter_group_entry: <<: *add_ansible_filter_group_entry_1 register: nm_add_ansible_filter_group_entry_1_again - - name: Add access span filter group entry + - name: Add access span filter group entry 2 to ansible_group_1 cisco.aci.aci_access_span_filter_group_entry: <<: *add_ansible_filter_group_1 source_ip: 1.1.1.1 @@ -88,14 +88,14 @@ ip_protocol: tcp register: nm_add_ansible_filter_group_entry_2 - - name: Add access span filter group entry + - name: Add access span filter group entry 3 to ansible_group_1 cisco.aci.aci_access_span_filter_group_entry: <<: *add_ansible_filter_group_1 source_ip: 2.2.2.2 destination_ip: 5.5.5.5 register: nm_add_ansible_filter_group_entry_3 - - name: Add access span filter group entry in different filter group + - name: Add access span filter group entry 4 to ansible_group_2 cisco.aci.aci_access_span_filter_group_entry: <<: *add_ansible_filter_group_2 source_ip: 2.2.2.2 @@ -248,3 +248,14 @@ - nm_remove_ansible_filter_group_1_entry_again is not changed - nm_remove_ansible_filter_group_1_entry_again.current == [] - nm_remove_ansible_filter_group_1_entry_again.previous == [] + + # CLEAN TEST ENVIRONMENT + + - name: Clean created access span filter groups + cisco.aci.aci_access_span_filter_group: + <<: *aci_info + filter_group: "{{ item }}" + state: absent + loop: + - ansible_group_1 + - ansible_group_2 From 3c0e307dd50e13facf041e9435c625298c5833ea Mon Sep 17 00:00:00 2001 From: akinross Date: Tue, 11 Apr 2023 21:29:13 +0200 Subject: [PATCH 5/7] [ignore] allow port inputs as digits --- plugins/module_utils/constants.py | 3 ++ .../aci_access_span_filter_group_entry.py | 28 ++++++++----------- .../tasks/main.yml | 19 +++++++++++-- 3 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 plugins/module_utils/constants.py diff --git a/plugins/module_utils/constants.py b/plugins/module_utils/constants.py new file mode 100644 index 000000000..72d7585a5 --- /dev/null +++ b/plugins/module_utils/constants.py @@ -0,0 +1,3 @@ +VALID_IP_PROTOCOLS = ["eigrp", "egp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp", "unspecified"] + +FILTER_PORT_MAPPING = {"443": "https", "25": "smtp", "80": "http", "53": "dns", "22": "ssh", "110": "pop3", "554": "rtsp", "20": "ftpData", "ftp": "ftpData"} diff --git a/plugins/modules/aci_access_span_filter_group_entry.py b/plugins/modules/aci_access_span_filter_group_entry.py index 548b67e6b..5d5516751 100644 --- a/plugins/modules/aci_access_span_filter_group_entry.py +++ b/plugins/modules/aci_access_span_filter_group_entry.py @@ -32,33 +32,33 @@ first_src_port: description: - The first source port (from port). + - Accepted values are any valid TCP/UDP port range. - The APIC defaults to C(unspecified) when unset during creation. type: str - choices: [ unspecified, dns, ftp, http, https, pop3, rtsp, smtp, ssh ] last_src_port: description: - The last source port (to port). + - Accepted values are any valid TCP/UDP port range. - The APIC defaults to C(unspecified) when unset during creation. type: str - choices: [ unspecified, dns, ftp, http, https, pop3, rtsp, smtp, ssh ] first_dest_port: description: - The first destination port (from port). + - Accepted values are any valid TCP/UDP port range. - The APIC defaults to C(unspecified) when unset during creation. type: str - choices: [ unspecified, dns, ftp, http, https, pop3, rtsp, smtp, ssh ] last_dest_port: description: - The last destination port (to port). + - Accepted values are any valid TCP/UDP port range. - The APIC defaults to C(unspecified) when unset during creation. type: str - choices: [ unspecified, dns, ftp, http, https, pop3, rtsp, smtp, ssh ] ip_protocol: description: - The IP Protocol. - The APIC defaults to C(unspecified) when unset during creation. type: str - choices: [ unspecified, egp, eigrp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp ] + choices: [ eigrp, egp, icmp, icmpv6, igmp, igp, l2tp, ospfigp, pim, tcp, udp, unspecified ] state: description: - Use C(present) or C(absent) for adding or removing. @@ -236,15 +236,11 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec - - -PORT_CHOICES = ["unspecified", "dns", "ftp", "http", "https", "pop3", "rtsp", "smtp", "ssh"] -PROTOCOL_CHOICES = ["unspecified", "egp", "eigrp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp"] -CHOICE_MAPPING = {"ftp": "ftpData"} +from ansible_collections.cisco.aci.plugins.module_utils.constants import VALID_IP_PROTOCOLS, FILTER_PORT_MAPPING def get_port_value(port): - return CHOICE_MAPPING.get(port, port) if port else "unspecified" + return FILTER_PORT_MAPPING.get(port, port) if port else "unspecified" def main(): @@ -254,11 +250,11 @@ def main(): filter_group=dict(type="str"), # Not required for querying all objects source_ip=dict(type="str"), # Not required for querying all objects destination_ip=dict(type="str"), # Not required for querying all objects - first_src_port=dict(type="str", choices=PORT_CHOICES), - last_src_port=dict(type="str", choices=PORT_CHOICES), - first_dest_port=dict(type="str", choices=PORT_CHOICES), - last_dest_port=dict(type="str", choices=PORT_CHOICES), - ip_protocol=dict(type="str", choices=PROTOCOL_CHOICES), + first_src_port=dict(type="str"), + last_src_port=dict(type="str"), + first_dest_port=dict(type="str"), + last_dest_port=dict(type="str"), + ip_protocol=dict(type="str", choices=VALID_IP_PROTOCOLS), name_alias=dict(type="str"), state=dict(type="str", default="present", choices=["absent", "present", "query"]), ) diff --git a/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml b/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml index f94be31d9..788658f13 100644 --- a/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml +++ b/tests/integration/targets/aci_access_span_filter_group_entry/tasks/main.yml @@ -76,15 +76,15 @@ <<: *add_ansible_filter_group_entry_1 register: nm_add_ansible_filter_group_entry_1_again - - name: Add access span filter group entry 2 to ansible_group_1 + - name: Add access span filter group entry 2 to ansible_group_1 with ports as digits cisco.aci.aci_access_span_filter_group_entry: <<: *add_ansible_filter_group_1 source_ip: 1.1.1.1 destination_ip: 3.3.3.3 first_src_port: http last_src_port: https - first_dest_port: http - last_dest_port: https + first_dest_port: 80 + last_dest_port: 443 ip_protocol: tcp register: nm_add_ansible_filter_group_entry_2 @@ -203,6 +203,15 @@ state: query register: query_match + - name: Query access span filter group entries that match source_ip and first_dest_port with ports as digits + cisco.aci.aci_access_span_filter_group_entry: + <<: *aci_info + source_ip: 1.1.1.1 + first_src_port: 80 + last_src_port: 443 + state: query + register: query_match_port_number + - name: Verify access span filter group queries ansible.builtin.assert: that: @@ -217,6 +226,10 @@ - query_match.current | length == 2 - query_match.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" - query_match.current.1.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-tcp-src-[1.1.1.1]-dst-[3.3.3.3]-srcPortFrom-http-srcPortTo-https-dstPortFrom-http-dstPortTo-https" + - query_match_port_number is not changed + - query_match_port_number.current | length == 2 + - query_match_port_number.current.0.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-unspecified-src-[1.1.1.1]-dst-[2.2.2.2]-srcPortFrom-http-srcPortTo-https-dstPortFrom-unspecified-dstPortTo-unspecified" + - query_match_port_number.current.1.spanFilterEntry.attributes.dn == "uni/infra/filtergrp-ansible_group_1/proto-tcp-src-[1.1.1.1]-dst-[3.3.3.3]-srcPortFrom-http-srcPortTo-https-dstPortFrom-http-dstPortTo-https" # TEST REMOVAL FILTER GROUP ENTRIES From 19c5812b6192239cd61b6ee575bfde6d2b1a1629 Mon Sep 17 00:00:00 2001 From: akinross Date: Tue, 11 Apr 2023 21:30:29 +0200 Subject: [PATCH 6/7] [ignore] leverage mapping from constants.py for valid_ip_protocols, filter_port_mapping --- plugins/modules/aci_filter_entry.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/modules/aci_filter_entry.py b/plugins/modules/aci_filter_entry.py index c029442b9..50587e198 100644 --- a/plugins/modules/aci_filter_entry.py +++ b/plugins/modules/aci_filter_entry.py @@ -271,6 +271,8 @@ from ansible.module_utils.basic import AnsibleModule from ansible_collections.cisco.aci.plugins.module_utils.aci import ACIModule, aci_argument_spec, aci_annotation_spec +from ansible_collections.cisco.aci.plugins.module_utils.constants import VALID_IP_PROTOCOLS, FILTER_PORT_MAPPING + VALID_ARP_FLAGS = ["arp_reply", "arp_request", "unspecified"] VALID_ETHER_TYPES = ["arp", "fcoe", "ip", "ipv4", "ipv6", "mac_security", "mpls_ucast", "trill", "unspecified"] @@ -285,11 +287,10 @@ "time_exceeded", "unspecified", ] -VALID_IP_PROTOCOLS = ["eigrp", "egp", "icmp", "icmpv6", "igmp", "igp", "l2tp", "ospfigp", "pim", "tcp", "udp", "unspecified"] # mapping dicts are used to normalize the proposed data to what the APIC expects, which will keep diffs accurate ARP_FLAG_MAPPING = dict(arp_reply="reply", arp_request="req", unspecified=None) -FILTER_PORT_MAPPING = {"443": "https", "25": "smtp", "80": "http", "20": "ftpData", "53": "dns", "110": "pop3", "554": "rtsp"} + ICMP_MAPPING = { "dst_unreachable": "dst-unreach", "echo": "echo", From 9bbd54f34731209102dfa5078c99dac3409a6352 Mon Sep 17 00:00:00 2001 From: akinross Date: Tue, 18 Apr 2023 21:15:32 +0200 Subject: [PATCH 7/7] [ignore] add empty lines between functions for consistency with other modules --- plugins/modules/aci_access_span_filter_group.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/modules/aci_access_span_filter_group.py b/plugins/modules/aci_access_span_filter_group.py index 6e8b22892..c715027f6 100644 --- a/plugins/modules/aci_access_span_filter_group.py +++ b/plugins/modules/aci_access_span_filter_group.py @@ -233,7 +233,9 @@ def main(): if state == "present": aci.payload(aci_class="spanFilterGrp", class_config=dict(name=filter_group, nameAlias=name_alias)) + aci.get_diff(aci_class="spanFilterGrp") + aci.post_config() elif state == "absent":