diff --git a/plugins/module_utils/helper/alias.py b/plugins/module_utils/helper/alias.py index dd97d9f..aa815c9 100644 --- a/plugins/module_utils/helper/alias.py +++ b/plugins/module_utils/helper/alias.py @@ -1,21 +1,38 @@ from re import match as regex_match from typing import Callable +from ipaddress import ip_network, ip_address from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \ BUILTIN_ALIASES, BUILTIN_INTERFACE_ALIASES_REG from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.validate import \ - is_valid_mac_address, is_valid_url + is_valid_mac_address, is_valid_url, is_valid_domain +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ + get_selected -def validate_values(cnf: dict, error_func: Callable) -> None: +def validate_values(cnf: dict, error_func: Callable, existing_entries: dict) -> None: v_type = cnf['type'] + if isinstance(existing_entries, dict): + existing_entries = { + a['name']: get_selected(a['type']) + for a in existing_entries.values() + } + else: + existing_entries = { + a['name']: a['type'] + for a in existing_entries + } + for value in cnf['content']: error = f"Value '{value}' is invalid for type '{v_type}'!" if v_type == 'port': + if value in existing_entries and existing_entries[value] == 'port': + continue + if str(value).find(':') != -1: to_check = value.split(':') @@ -39,24 +56,32 @@ def validate_values(cnf: dict, error_func: Callable) -> None: if not is_valid_url(value): error_func(error) - # unable to check because of alias-nesting support - # if v_type == 'network': - # value = value[1:] if value.startswith('!') else value - # - # try: - # ip_network(value) - # - # except ValueError: - # error_func(error) - - # unable to check because of alias-nesting support and ip-ranges - # if v_type == 'host': - # try: - # ip_address(value) - # - # except ValueError: - # if not is_valid_domain(value): - # error_func(error) + elif v_type == 'network': + if value in existing_entries and existing_entries[value] in ['network', 'host']: + continue + + value = value[1:] if value.startswith('!') else value + + try: + ip_network(value) + + except ValueError: + error_func(error) + + elif v_type == 'host': + if value in existing_entries and existing_entries[value] in ['network', 'host']: + continue + + if is_valid_domain(value): + continue + + for _value in value.split('-', 1): + _value = _value[1:] if _value.startswith('!') else _value + try: + ip_address(_value) + + except ValueError: + error_func(error) def check_purge_filter(module: AnsibleModule, existing_rule: dict) -> bool: diff --git a/plugins/module_utils/main/alias.py b/plugins/module_utils/main/alias.py index f8f3ab7..c0d1ed2 100644 --- a/plugins/module_utils/main/alias.py +++ b/plugins/module_utils/main/alias.py @@ -66,11 +66,11 @@ def check(self) -> None: f"must be shorter than {self.MAX_ALIAS_LEN} characters", ) - if self.p['state'] == 'present': - validate_values(error_func=self._error, cnf=self.p) - self.b.find(match_fields=[self.FIELD_ID]) + if self.p['state'] == 'present': + validate_values(error_func=self._error, cnf=self.p, existing_entries=self.existing_entries) + if self.p['state'] == 'present': self.r['diff']['after'] = self.b.build_diff(data=self.p) diff --git a/tests/alias.yml b/tests/alias.yml index bedf291..4d52484 100644 --- a/tests/alias.yml +++ b/tests/alias.yml @@ -138,6 +138,8 @@ name: 'ANSIBLE_TEST_1_2_HOST3' type: 'host' content: ['ANSIBLE_TEST_1_2_HOST2', 'ANSIBLE_TEST_1_2_HOST1'] + register: opnhost2 + failed_when: opnhost2.failed != ansible_check_mode - name: Adding ip range ansibleguy.opnsense.alias: @@ -145,16 +147,21 @@ type: 'host' content: '1.1.1.1-1.1.1.4' - # NOTE: cannot fail as validation was disabled because of alias-nesting and ip-range support - # - name: Adding invalid host - # ansibleguy.opnsense.alias: - # name: 'ANSIBLE_TEST_1_2_HOST3' - # type: 'host' - # content: "{{ item }}" - # register: opnhost1 - # failed_when: not opnhost1.failed - # loop: - # - ['1.1.1.n'] + - name: Adding exclusion + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_HOST5' + type: 'host' + content: '!1.1.1.1' + + - name: Adding invalid host + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_HOST3' + type: 'host' + content: "{{ item }}" + register: opnhost1 + failed_when: not opnhost1.failed + loop: + - ['1.1.1.n'] # networks @@ -170,16 +177,23 @@ type: 'network' content: ['192.168.0.0/24', '192.168.1.0/24'] - # NOTE: cannot fail as validation was disabled because of alias-nesting support - # - name: Adding invalid network - # ansibleguy.opnsense.alias: - # name: 'ANSIBLE_TEST_1_2_NET3' - # type: 'network' - # content: "{{ item }}" - # register: opnnet1 - # failed_when: not opnnet1.failed - # loop: - # - ['192.168.0.0/240'] + - name: Adding invalid network + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_NET3' + type: 'network' + content: "{{ item }}" + register: opnnet1 + failed_when: not opnnet1.failed + loop: + - ['192.168.0.0/240'] + + - name: Adding nested network + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_NET3' + type: 'network' + content: ['ANSIBLE_TEST_1_2_NET1', 'ANSIBLE_TEST_1_2_NET2'] + register: opnnet2 + failed_when: opnnet2.failed != ansible_check_mode # ports @@ -211,6 +225,30 @@ type: 'port' content: '9000:9500' + - name: Adding nested ports + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_PORT5' + type: 'port' + content: + - 'ANSIBLE_TEST_1_2_PORT1' + - 'ANSIBLE_TEST_1_2_PORT2' + register: opnport2 + failed_when: opnport2.failed != ansible_check_mode + + # mac + + - name: Adding mac + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_MAC1' + type: 'mac' + content: f4:90:ea:00:00:01 + + - name: Adding multiple macs + ansibleguy.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_MAC2' + type: 'mac' + content: ['f4:90:ea:00:00:01', 'f4:90:ea:00:00:02'] + # urls - name: Adding url @@ -336,13 +374,18 @@ - 'ANSIBLE_TEST_1_2_URL1' - 'ANSIBLE_TEST_1_2_URL2' - 'ANSIBLE_TEST_1_2_URL3' + - 'ANSIBLE_TEST_1_2_PORT5' - 'ANSIBLE_TEST_1_2_PORT1' - 'ANSIBLE_TEST_1_2_PORT2' - 'ANSIBLE_TEST_1_2_PORT3' - 'ANSIBLE_TEST_1_2_PORT4' + - 'ANSIBLE_TEST_1_2_MAC1' + - 'ANSIBLE_TEST_1_2_MAC2' + - 'ANSIBLE_TEST_1_2_NET3' - 'ANSIBLE_TEST_1_2_NET1' - 'ANSIBLE_TEST_1_2_NET2' - - 'ANSIBLE_TEST_1_2_NET3' + - 'ANSIBLE_TEST_1_2_NET4' + - 'ANSIBLE_TEST_1_2_HOST5' - 'ANSIBLE_TEST_1_2_HOST4' - 'ANSIBLE_TEST_1_2_HOST3' - 'ANSIBLE_TEST_1_2_HOST2' diff --git a/tests/alias_multi.yml b/tests/alias_multi.yml index c387165..606e1a0 100644 --- a/tests/alias_multi.yml +++ b/tests/alias_multi.yml @@ -26,6 +26,10 @@ ANSIBLE_TEST_2_8: ANSIBLE_TEST_2_9: ANSIBLE_TEST_2_10: + ANSIBLE_TEST_2_11: + ANSIBLE_TEST_2_12: + ANSIBLE_TEST_2_13: + ANSIBLE_TEST_2_14: state: 'absent' register: opn1 failed_when: > @@ -157,12 +161,46 @@ opn6.changed when: not ansible_check_mode + - name: Adding nested aliases + ansibleguy.opnsense.alias_multi: + aliases: + # hosts + ANSIBLE_TEST_2_12: + content: ['ANSIBLE_TEST_2_1', 'ANSIBLE_TEST_2_2'] + + # networks + ANSIBLE_TEST_2_13: + type: 'network' + content: ['ANSIBLE_TEST_2_3', 'ANSIBLE_TEST_2_4'] + + # ports + ANSIBLE_TEST_2_14: + type: 'port' + content: ['ANSIBLE_TEST_2_5', 'ANSIBLE_TEST_2_6'] + + reload: false # geoip and urltable take LONG time + + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + - name: Listing aliases ansibleguy.opnsense.list: register: opn4 failed_when: > 'data' not in opn4 or - opn4.data | length != 11 + opn4.data | length != 14 + when: not ansible_check_mode + + - name: Removing nesting + ansibleguy.opnsense.alias_multi: + aliases: + ANSIBLE_TEST_2_12: + ANSIBLE_TEST_2_13: + ANSIBLE_TEST_2_14: + state: 'absent' when: not ansible_check_mode - name: Removing