From b99de25f32f0e1b6798a22001f03aa0130a23079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20BAROTIN?= Date: Mon, 11 Feb 2019 12:05:35 +0100 Subject: [PATCH] Enable changed var with ufw check mode (#49948) * Enable 'changed' var with ufw check mode * Fix from comment of the PR + Unit Test * Fix on ufw module after the second review - delete rules change works in check mode - simplify execute def & use it on every call process - improved regexp - rename vars defaults to current_default_values * Add ignore error to execute() and use it in get_current_rules() * Update after third code review (introduce change in changed status) * Adjust tests and fix some problems (#1) * 'active' also appears in 'inactive'. * 'reject' is also a valid option here. * For example for reloaded, changed will be set back to False here. * Improve and adjust tests. * Fix after merging integration test * handle "disabled" on default routed * Add /var/lib/ufw/.. rules files * add unit test * Fix pep8 formatting error * Separate ipv6 and ipv4 rules process from checkmode * fix non-ascii error on ci * Some change after review * Add unit test with sub network mask * rename is_match function by is_starting * add changelog fragment --- ...48-enable-chenged-status-in-check-mode.yml | 2 + lib/ansible/modules/system/ufw.py | 166 ++++++++-- .../targets/ufw/tasks/tests/basic.yml | 35 +- .../targets/ufw/tasks/tests/global-state.yml | 35 +- test/units/modules/system/test_ufw.py | 301 ++++++++++++++++++ 5 files changed, 498 insertions(+), 41 deletions(-) create mode 100644 changelogs/fragments/ufw-49948-enable-chenged-status-in-check-mode.yml create mode 100644 test/units/modules/system/test_ufw.py diff --git a/changelogs/fragments/ufw-49948-enable-chenged-status-in-check-mode.yml b/changelogs/fragments/ufw-49948-enable-chenged-status-in-check-mode.yml new file mode 100644 index 00000000000..9ae7d59c1d3 --- /dev/null +++ b/changelogs/fragments/ufw-49948-enable-chenged-status-in-check-mode.yml @@ -0,0 +1,2 @@ +minor_changes: + - ufw - enable "changed" status while check mode is enabled diff --git a/lib/ansible/modules/system/ufw.py b/lib/ansible/modules/system/ufw.py index db081991e8c..652fd35179d 100644 --- a/lib/ansible/modules/system/ufw.py +++ b/lib/ansible/modules/system/ufw.py @@ -207,11 +207,37 @@ EXAMPLES = ''' ''' import re + from operator import itemgetter from ansible.module_utils.basic import AnsibleModule +def compile_ipv4_regexp(): + r = r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}" + r += r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" + return re.compile(r) + + +def compile_ipv6_regexp(): + """ + validation pattern provided by : + https://stackoverflow.com/questions/53497/regular-expression-that-matches- + valid-ipv6-addresses#answer-17871737 + """ + r = r"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:" + r += r"|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}" + r += r"(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4})" + r += r"{1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]" + r += r"{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]" + r += r"{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4})" + r += r"{0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]" + r += r"|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}" + r += r"[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}" + r += r"[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" + return re.compile(r) + + def main(): module = AnsibleModule( argument_spec=dict( @@ -241,24 +267,62 @@ def main(): cmds = [] - def execute(cmd): + ipv4_regexp = compile_ipv4_regexp() + ipv6_regexp = compile_ipv6_regexp() + + def filter_line_that_not_start_with(pattern, content): + return ''.join([line for line in content.splitlines(True) if line.startswith(pattern)]) + + def filter_line_that_contains(pattern, content): + return [line for line in content.splitlines(True) if pattern in line] + + def filter_line_that_not_contains(pattern, content): + return ''.join([line for line in content.splitlines(True) if not line.contains(pattern)]) + + def filter_line_that_match_func(match_func, content): + return ''.join([line for line in content.splitlines(True) if match_func(line) is not None]) + + def filter_line_that_contains_ipv4(content): + return filter_line_that_match_func(ipv4_regexp.search, content) + + def filter_line_that_contains_ipv6(content): + return filter_line_that_match_func(ipv6_regexp.search, content) + + def is_starting_by_ipv4(ip): + return ipv4_regexp.match(ip) is not None + + def is_starting_by_ipv6(ip): + return ipv6_regexp.match(ip) is not None + + def execute(cmd, ignore_error=False): cmd = ' '.join(map(itemgetter(-1), filter(itemgetter(0), cmd))) cmds.append(cmd) - (rc, out, err) = module.run_command(cmd) + (rc, out, err) = module.run_command(cmd, environ_update={"LANG": "C"}) - if rc != 0: - module.fail_json(msg=err or out) + if rc != 0 and not ignore_error: + module.fail_json(msg=err or out, commands=cmds) + + return out + + def get_current_rules(): + user_rules_files = ["/lib/ufw/user.rules", + "/lib/ufw/user6.rules", + "/etc/ufw/user.rules", + "/etc/ufw/user6.rules", + "/var/lib/ufw/user.rules", + "/var/lib/ufw/user6.rules"] + + cmd = [[grep_bin], ["-h"], ["'^### tuple'"]] + + cmd.extend([[f] for f in user_rules_files]) + return execute(cmd, ignore_error=True) def ufw_version(): """ Returns the major and minor version of ufw installed on the system. """ - rc, out, err = module.run_command("%s --version" % ufw_bin) - if rc != 0: - module.fail_json( - msg="Failed to get ufw version.", rc=rc, out=out, err=err - ) + out = execute([[ufw_bin], ["--version"]]) lines = [x for x in out.split('\n') if x.strip() != ''] if len(lines) == 0: @@ -291,25 +355,65 @@ def main(): # Ensure ufw is available ufw_bin = module.get_bin_path('ufw', True) + grep_bin = module.get_bin_path('grep', True) # Save the pre state and rules in order to recognize changes - (_, pre_state, _) = module.run_command(ufw_bin + ' status verbose') - (_, pre_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules") + pre_state = execute([[ufw_bin], ['status verbose']]) + pre_rules = get_current_rules() - # Execute commands + changed = False + + # Execute filter for (command, value) in commands.items(): + cmd = [[ufw_bin], [module.check_mode, '--dry-run']] if command == 'state': states = {'enabled': 'enable', 'disabled': 'disable', 'reloaded': 'reload', 'reset': 'reset'} - execute(cmd + [['-f'], [states[value]]]) + + if value in ['reloaded', 'reset']: + changed = True + + if module.check_mode: + # "active" would also match "inactive", hence the space + ufw_enabled = pre_state.find(" active") != -1 + if (value == 'disabled' and ufw_enabled) or (value == 'enabled' and not ufw_enabled): + changed = True + else: + execute(cmd + [['-f'], [states[value]]]) elif command == 'logging': - execute(cmd + [[command], [value]]) + extract = re.search(r'Logging: (on|off) \(([a-z]+)\)', pre_state) + if extract: + current_level = extract.group(2) + current_on_off_value = extract.group(1) + if value != "off": + if value != "on" and (value != current_level or current_on_off_value == "off"): + changed = True + elif current_on_off_value != "off": + changed = True + else: + changed = True + + if not module.check_mode: + execute(cmd + [[command], [value]]) elif command == 'default': - execute(cmd + [[command], [value], [params['direction']]]) + if module.check_mode: + regexp = r'Default: (deny|allow|reject) \(incoming\), (deny|allow|reject) \(outgoing\), (deny|allow|reject|disabled) \(routed\)' + extract = re.search(regexp, pre_state) + if extract is not None: + current_default_values = {} + current_default_values["incoming"] = extract.group(1) + current_default_values["outgoing"] = extract.group(2) + current_default_values["routed"] = extract.group(3) + if current_default_values[params['direction']] != value: + changed = True + else: + changed = True + else: + execute(cmd + [[command], [value], [params['direction']]]) elif command == 'rule': # Rules are constructed according to the long format @@ -336,14 +440,34 @@ def main(): if (ufw_major == 0 and ufw_minor >= 35) or ufw_major > 0: cmd.append([params['comment'], "comment '%s'" % params['comment']]) - execute(cmd) + rules_dry = execute(cmd) + + if module.check_mode: + + nb_skipping_line = len(filter_line_that_contains("Skipping", rules_dry)) + + if not (nb_skipping_line > 0 and nb_skipping_line == len(rules_dry.splitlines(True))): + + rules_dry = filter_line_that_not_start_with("### tuple", rules_dry) + # ufw dry-run doesn't send all rules so have to compare ipv4 or ipv6 rules + if is_starting_by_ipv4(params['from_ip']) or is_starting_by_ipv4(params['to_ip']): + if filter_line_that_contains_ipv4(pre_rules) != filter_line_that_contains_ipv4(rules_dry): + changed = True + elif is_starting_by_ipv6(params['from_ip']) or is_starting_by_ipv6(params['to_ip']): + if filter_line_that_contains_ipv6(pre_rules) != filter_line_that_contains_ipv6(rules_dry): + changed = True + elif pre_rules != rules_dry: + changed = True # Get the new state - (_, post_state, _) = module.run_command(ufw_bin + ' status verbose') - (_, post_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules") - changed = (pre_state != post_state) or (pre_rules != post_rules) - - return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip()) + if module.check_mode: + return module.exit_json(changed=changed, commands=cmds) + else: + post_state = execute([[ufw_bin], ['status'], ['verbose']]) + if not changed: + post_rules = get_current_rules() + changed = (pre_state != post_state) or (pre_rules != post_rules) + return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip()) if __name__ == '__main__': diff --git a/test/integration/targets/ufw/tasks/tests/basic.yml b/test/integration/targets/ufw/tasks/tests/basic.yml index ea726c016af..3c625112f36 100644 --- a/test/integration/targets/ufw/tasks/tests/basic.yml +++ b/test/integration/targets/ufw/tasks/tests/basic.yml @@ -1,5 +1,8 @@ --- # ############################################ +- name: Make sure it is off + ufw: + state: disabled - name: Enable (check mode) ufw: state: enabled @@ -20,7 +23,7 @@ register: enable_idem_check - assert: that: - # FIXME - enable_check is changed + - enable_check is changed - enable is changed - enable_idem is not changed - enable_idem_check is not changed @@ -54,7 +57,7 @@ register: ipv4_allow_idem_check - assert: that: - # FIXME - ipv4_allow_check is changed + - ipv4_allow_check is changed - ipv4_allow is changed - ipv4_allow_idem is not changed - ipv4_allow_idem_check is not changed @@ -92,7 +95,7 @@ register: delete_ipv4_allow_idem_check - assert: that: - # FIXME - delete_ipv4_allow_check is changed + - delete_ipv4_allow_check is changed - delete_ipv4_allow is changed - delete_ipv4_allow_idem is not changed - delete_ipv4_allow_idem_check is not changed @@ -126,7 +129,7 @@ register: ipv6_allow_idem_check - assert: that: - # FIXME - ipv6_allow_check is changed + - ipv6_allow_check is changed - ipv6_allow is changed - ipv6_allow_idem is not changed - ipv6_allow_idem_check is not changed @@ -164,7 +167,7 @@ register: delete_ipv6_allow_idem_check - assert: that: - # FIXME - delete_ipv6_allow_check is changed + - delete_ipv6_allow_check is changed - delete_ipv6_allow is changed - delete_ipv6_allow_idem is not changed - delete_ipv6_allow_idem_check is not changed @@ -199,7 +202,7 @@ register: ipv4_allow_idem_check - assert: that: - # FIXME - ipv4_allow_check is changed + - ipv4_allow_check is changed - ipv4_allow is changed - ipv4_allow_idem is not changed - ipv4_allow_idem_check is not changed @@ -237,7 +240,7 @@ register: delete_ipv4_allow_idem_check - assert: that: - # FIXME - delete_ipv4_allow_check is changed + - delete_ipv4_allow_check is changed - delete_ipv4_allow is changed - delete_ipv4_allow_idem is not changed - delete_ipv4_allow_idem_check is not changed @@ -271,7 +274,7 @@ register: ipv6_allow_idem_check - assert: that: - # FIXME - ipv6_allow is_check changed + - ipv6_allow_check is changed - ipv6_allow is changed - ipv6_allow_idem is not changed - ipv6_allow_idem_check is not changed @@ -309,7 +312,7 @@ register: delete_ipv6_allow_idem_check - assert: that: - # FIXME - delete_ipv6_allow_check is changed + - delete_ipv6_allow_check is changed - delete_ipv6_allow is changed - delete_ipv6_allow_idem is not changed - delete_ipv6_allow_idem_check is not changed @@ -326,8 +329,8 @@ register: reload_check - assert: that: - - reload is not changed # NOT as expected! - - reload_check is not changed # NOT as expected! + - reload is changed + - reload_check is changed # ############################################ - name: Disable (check mode) @@ -350,7 +353,7 @@ register: disable_idem_check - assert: that: - # FIXME - disable_check is changed + - disable_check is changed - disable is changed - disable_idem is not changed - disable_idem_check is not changed @@ -393,7 +396,7 @@ register: reset_idem_check - assert: that: - - reset_check is not changed # NOT as expected! - - reset is not changed # NOT as expected! - - reset_idem is not changed - - reset_idem_check is not changed + - reset_check is changed + - reset is changed + - reset_idem is changed + - reset_idem_check is changed diff --git a/test/integration/targets/ufw/tasks/tests/global-state.yml b/test/integration/targets/ufw/tasks/tests/global-state.yml index 0a6967eebca..28c9156295b 100644 --- a/test/integration/targets/ufw/tasks/tests/global-state.yml +++ b/test/integration/targets/ufw/tasks/tests/global-state.yml @@ -4,6 +4,9 @@ state: enabled # ############################################ +- name: Make sure logging is off + ufw: + logging: no - name: Logging (check mode) ufw: logging: yes @@ -17,6 +20,8 @@ shell: | ufw status verbose | grep "^Logging:" register: ufw_logging + environment: + LC_ALL: C - name: Logging (idempotency) ufw: logging: yes @@ -26,13 +31,31 @@ logging: yes check_mode: yes register: logging_idem_check +- name: Logging (change, check mode) + ufw: + logging: full + check_mode: yes + register: logging_change_check +- name: Logging (change) + ufw: + logging: full + register: logging_change +- name: Get logging + shell: | + ufw status verbose | grep "^Logging:" + register: ufw_logging_change + environment: + LC_ALL: C - assert: that: - - logging_check is not changed # NOT as expected! - - logging is not changed # NOT as expected! + - logging_check is changed + - logging is changed - "ufw_logging.stdout == 'Logging: on (low)'" - logging_idem is not changed - logging_idem_check is not changed + - "ufw_logging_change.stdout == 'Logging: on (full)'" + - logging_change is changed + - logging_change_check is changed # ############################################ - name: Default (check mode) @@ -50,6 +73,8 @@ shell: | ufw status verbose | grep "^Default:" register: ufw_defaults + environment: + LC_ALL: C - name: Default (idempotency) ufw: default: reject @@ -76,13 +101,15 @@ shell: | ufw status verbose | grep "^Default:" register: ufw_defaults_change + environment: + LC_ALL: C - assert: that: - # FIXME - default_check is changed + - default_check is changed - default is changed - "'reject (incoming)' in ufw_defaults.stdout" - default_idem is not changed - default_idem_check is not changed - # FIXME - default_change_check is changed + - default_change_check is changed - default_change is changed - "'allow (incoming)' in ufw_defaults_change.stdout" diff --git a/test/units/modules/system/test_ufw.py b/test/units/modules/system/test_ufw.py new file mode 100644 index 00000000000..1cfbac27821 --- /dev/null +++ b/test/units/modules/system/test_ufw.py @@ -0,0 +1,301 @@ + +from units.compat import unittest +from units.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes +import ansible.modules.system.ufw as module + +import json + + +# mock ufw messages + +ufw_version_35 = """ufw 0.35\nCopyright 2008-2015 Canonical Ltd.\n""" + +ufw_verbose_header = """Status: active +Logging: on (low) +Default: deny (incoming), allow (outgoing), deny (routed) +New profiles: skip + +To Action From +-- ------ ----""" + + +ufw_status_verbose_with_port_7000 = ufw_verbose_header + """ +7000/tcp ALLOW IN Anywhere +7000/tcp (v6) ALLOW IN Anywhere (v6) +""" + +user_rules_with_port_7000 = """### tuple ### allow tcp 7000 0.0.0.0/0 any 0.0.0.0/0 in +### tuple ### allow tcp 7000 ::/0 any ::/0 in +""" + +user_rules_with_ipv6 = """### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.251 in +### tuple ### allow udp 5353 ::/0 any ff02::fb in +""" + +ufw_status_verbose_with_ipv6 = ufw_verbose_header + """ +5353/udp ALLOW IN 224.0.0.251 +5353/udp ALLOW IN ff02::fb +""" + +ufw_status_verbose_nothing = ufw_verbose_header + +skippg_adding_existing_rules = "Skipping adding existing rule\nSkipping adding existing rule (v6)\n" + +grep_config_cli = "grep -h '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules " +grep_config_cli += "/var/lib/ufw/user.rules /var/lib/ufw/user6.rules" + +dry_mode_cmd_with_port_700 = { + "ufw status verbose": ufw_status_verbose_with_port_7000, + "ufw --version": ufw_version_35, + "ufw --dry-run allow from any to any port 7000 proto tcp": skippg_adding_existing_rules, + "ufw --dry-run delete allow from any to any port 7000 proto tcp": "", + "ufw --dry-run delete allow from any to any port 7001 proto tcp": user_rules_with_port_7000, + grep_config_cli: user_rules_with_port_7000 +} + +# setup configuration : +# ufw reset +# ufw enable +# ufw allow proto udp to any port 5353 from 224.0.0.251 +# ufw allow proto udp to any port 5353 from ff02::fb +dry_mode_cmd_with_ipv6 = { + "ufw status verbose": ufw_status_verbose_with_ipv6, + "ufw --version": ufw_version_35, + # CONTENT of the command sudo ufw --dry-run delete allow in from ff02::fb port 5353 proto udp | grep -E "^### tupple" + "ufw --dry-run delete allow from ff02::fb to any port 5353 proto udp": "### tuple ### allow udp any ::/0 5353 ff02::fb in", + grep_config_cli: user_rules_with_ipv6, + "ufw --dry-run allow from ff02::fb to any port 5353 proto udp": skippg_adding_existing_rules, + "ufw --dry-run allow from 224.0.0.252 to any port 5353 proto udp": """### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.251 in +### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.252 in +""", + "ufw --dry-run allow from 10.0.0.0/24 to any port 1577 proto udp": "### tuple ### allow udp 1577 0.0.0.0/0 any 10.0.0.0/24 in" +} + +dry_mode_cmd_nothing = { + "ufw status verbose": ufw_status_verbose_nothing, + "ufw --version": ufw_version_35, + grep_config_cli: "", + "ufw --dry-run allow from any to :: port 23": "### tuple ### allow any 23 :: any ::/0 in" +} + + +def do_nothing_func_nothing(*args, **kwarg): + return 0, dry_mode_cmd_nothing[args[0]], "" + + +def do_nothing_func_ipv6(*args, **kwarg): + return 0, dry_mode_cmd_with_ipv6[args[0]], "" + + +def do_nothing_func_port_7000(*args, **kwarg): + return 0, dry_mode_cmd_with_port_700[args[0]], "" + + +def set_module_args(args): + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + """prepare arguments so that they will be picked up during module creation""" + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + """Exception class to be raised by module.exit_json and caught by the test case""" + pass + + +class AnsibleFailJson(Exception): + """Exception class to be raised by module.fail_json and caught by the test case""" + pass + + +def exit_json(*args, **kwargs): + """function to patch over exit_json; package return data into an exception""" + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + """function to patch over fail_json; package return data into an exception""" + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +def get_bin_path(self, arg, required=False): + """Mock AnsibleModule.get_bin_path""" + return arg + + +class TestUFW(unittest.TestCase): + + def setUp(self): + self.mock_module_helper = patch.multiple(basic.AnsibleModule, + exit_json=exit_json, + fail_json=fail_json, + get_bin_path=get_bin_path) + self.mock_module_helper.start() + self.addCleanup(self.mock_module_helper.stop) + + def test_filter_line_that_contains_ipv4(self): + reg = module.compile_ipv4_regexp() + + self.assertTrue(reg.search("### tuple ### allow udp 5353 ::/0 any ff02::fb in") is None) + self.assertTrue(reg.search("### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.251 in") is not None) + + self.assertTrue(reg.match("ff02::fb") is None) + self.assertTrue(reg.match("224.0.0.251") is not None) + self.assertTrue(reg.match("10.0.0.0/8") is not None) + self.assertTrue(reg.match("somethingElse") is None) + self.assertTrue(reg.match("::") is None) + self.assertTrue(reg.match("any") is None) + + def test_filter_line_that_contains_ipv6(self): + reg = module.compile_ipv6_regexp() + self.assertTrue(reg.search("### tuple ### allow udp 5353 ::/0 any ff02::fb in") is not None) + self.assertTrue(reg.search("### tuple ### allow udp 5353 0.0.0.0/0 any 224.0.0.251 in") is None) + self.assertTrue(reg.search("### tuple ### allow any 23 :: any ::/0 in") is not None) + self.assertTrue(reg.match("ff02::fb") is not None) + self.assertTrue(reg.match("224.0.0.251") is None) + self.assertTrue(reg.match("::") is not None) + + def test_check_mode_add_rules(self): + set_module_args({ + 'rule': 'allow', + 'proto': 'tcp', + 'port': '7000', + '_ansible_check_mode': True + }) + result = self.__getResult(do_nothing_func_port_7000) + self.assertFalse(result.exception.args[0]['changed']) + + def test_check_mode_delete_existing_rules(self): + + set_module_args({ + 'rule': 'allow', + 'proto': 'tcp', + 'port': '7000', + 'delete': 'yes', + '_ansible_check_mode': True, + }) + + self.assertTrue(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed']) + + def test_check_mode_delete_not_existing_rules(self): + + set_module_args({ + 'rule': 'allow', + 'proto': 'tcp', + 'port': '7001', + 'delete': 'yes', + '_ansible_check_mode': True, + }) + + self.assertFalse(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed']) + + def test_enable_mode(self): + set_module_args({ + 'state': 'enabled', + '_ansible_check_mode': True + }) + + self.assertFalse(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed']) + + def test_disable_mode(self): + set_module_args({ + 'state': 'disabled', + '_ansible_check_mode': True + }) + + self.assertTrue(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed']) + + def test_logging_off(self): + set_module_args({ + 'logging': 'off', + '_ansible_check_mode': True + }) + + self.assertTrue(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed']) + + def test_logging_on(self): + set_module_args({ + 'logging': 'on', + '_ansible_check_mode': True + }) + + self.assertFalse(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed']) + + def test_default_changed(self): + set_module_args({ + 'default': 'allow', + "direction": "incoming", + '_ansible_check_mode': True + }) + self.assertTrue(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed']) + + def test_default_not_changed(self): + set_module_args({ + 'default': 'deny', + "direction": "incoming", + '_ansible_check_mode': True + }) + self.assertFalse(self.__getResult(do_nothing_func_port_7000).exception.args[0]['changed']) + + def test_ipv6_remove(self): + set_module_args({ + 'rule': 'allow', + 'proto': 'udp', + 'port': '5353', + 'from': 'ff02::fb', + 'delete': 'yes', + '_ansible_check_mode': True, + }) + self.assertTrue(self.__getResult(do_nothing_func_ipv6).exception.args[0]['changed']) + + def test_ipv6_add_existing(self): + set_module_args({ + 'rule': 'allow', + 'proto': 'udp', + 'port': '5353', + 'from': 'ff02::fb', + '_ansible_check_mode': True, + }) + self.assertFalse(self.__getResult(do_nothing_func_ipv6).exception.args[0]['changed']) + + def test_add_not_existing_ipv4_submask(self): + set_module_args({ + 'rule': 'allow', + 'proto': 'udp', + 'port': '1577', + 'from': '10.0.0.0/24', + '_ansible_check_mode': True, + }) + self.assertTrue(self.__getResult(do_nothing_func_ipv6).exception.args[0]['changed']) + + def test_ipv4_add_with_existing_ipv6(self): + set_module_args({ + 'rule': 'allow', + 'proto': 'udp', + 'port': '5353', + 'from': '224.0.0.252', + '_ansible_check_mode': True, + }) + self.assertTrue(self.__getResult(do_nothing_func_ipv6).exception.args[0]['changed']) + + def test_ipv6_add_from_nothing(self): + set_module_args({ + 'rule': 'allow', + 'port': '23', + 'to': '::', + '_ansible_check_mode': True, + }) + result = self.__getResult(do_nothing_func_nothing).exception.args[0] + print(result) + self.assertTrue(result['changed']) + + def __getResult(self, cmd_fun): + with patch.object(basic.AnsibleModule, 'run_command') as mock_run_command: + mock_run_command.side_effect = cmd_fun + with self.assertRaises(AnsibleExitJson) as result: + module.main() + return result