diff --git a/changelogs/fragments/6902-added-support-in-nmcli-for-ipvx-dns-options.yml b/changelogs/fragments/6902-added-support-in-nmcli-for-ipvx-dns-options.yml new file mode 100644 index 0000000000..34617dcf6b --- /dev/null +++ b/changelogs/fragments/6902-added-support-in-nmcli-for-ipvx-dns-options.yml @@ -0,0 +1,2 @@ +minor_changes: + - nmcli - add support for ``ipv4.dns-options`` and ``ipv6.dns-options`` (https://github.com/ansible-collections/community.general/pull/6902). diff --git a/plugins/modules/nmcli.py b/plugins/modules/nmcli.py index 921fd1fc9c..9f8d6ede1d 100644 --- a/plugins/modules/nmcli.py +++ b/plugins/modules/nmcli.py @@ -191,6 +191,12 @@ options: - A list of DNS search domains. elements: str type: list + dns4_options: + description: + - A list of DNS options. + elements: str + type: list + version_added: 7.2.0 dns4_ignore_auto: description: - Ignore automatically configured IPv4 name servers. @@ -290,6 +296,12 @@ options: - A list of DNS search domains. elements: str type: list + dns6_options: + description: + - A list of DNS options. + elements: str + type: list + version_added: 7.2.0 dns6_ignore_auto: description: - Ignore automatically configured IPv6 name servers. @@ -1537,6 +1549,7 @@ class Nmcli(object): self.never_default4 = module.params['never_default4'] self.dns4 = module.params['dns4'] self.dns4_search = module.params['dns4_search'] + self.dns4_options = module.params['dns4_options'] self.dns4_ignore_auto = module.params['dns4_ignore_auto'] self.method4 = module.params['method4'] self.may_fail4 = module.params['may_fail4'] @@ -1548,6 +1561,7 @@ class Nmcli(object): self.route_metric6 = module.params['route_metric6'] self.dns6 = module.params['dns6'] self.dns6_search = module.params['dns6_search'] + self.dns6_options = module.params['dns6_options'] self.dns6_ignore_auto = module.params['dns6_ignore_auto'] self.method6 = module.params['method6'] self.ip_privacy6 = module.params['ip_privacy6'] @@ -1654,6 +1668,7 @@ class Nmcli(object): 'ipv4.dhcp-client-id': self.dhcp_client_id, 'ipv4.dns': self.dns4, 'ipv4.dns-search': self.dns4_search, + 'ipv4.dns-options': self.dns4_options, 'ipv4.ignore-auto-dns': self.dns4_ignore_auto, 'ipv4.gateway': self.gw4, 'ipv4.ignore-auto-routes': self.gw4_ignore_auto, @@ -1666,6 +1681,7 @@ class Nmcli(object): 'ipv6.addresses': self.enforce_ipv6_cidr_notation(self.ip6), 'ipv6.dns': self.dns6, 'ipv6.dns-search': self.dns6_search, + 'ipv6.dns-options': self.dns6_options, 'ipv6.ignore-auto-dns': self.dns6_ignore_auto, 'ipv6.gateway': self.gw6, 'ipv6.ignore-auto-routes': self.gw6_ignore_auto, @@ -2042,10 +2058,12 @@ class Nmcli(object): 'ipv6.addresses', 'ipv4.dns', 'ipv4.dns-search', + 'ipv4.dns-options', 'ipv4.routes', 'ipv4.routing-rules', 'ipv6.dns', 'ipv6.dns-search', + 'ipv6.dns-options', 'ipv6.routes', '802-11-wireless-security.group', '802-11-wireless-security.leap-password-flags', @@ -2404,6 +2422,7 @@ def main(): never_default4=dict(type='bool', default=False), dns4=dict(type='list', elements='str'), dns4_search=dict(type='list', elements='str'), + dns4_options=dict(type='list', elements='str'), dns4_ignore_auto=dict(type='bool', default=False), method4=dict(type='str', choices=['auto', 'link-local', 'manual', 'shared', 'disabled']), may_fail4=dict(type='bool', default=True), @@ -2413,6 +2432,7 @@ def main(): gw6_ignore_auto=dict(type='bool', default=False), dns6=dict(type='list', elements='str'), dns6_search=dict(type='list', elements='str'), + dns6_options=dict(type='list', elements='str'), dns6_ignore_auto=dict(type='bool', default=False), routes6=dict(type='list', elements='str'), routes6_extended=dict(type='list', diff --git a/tests/unit/plugins/modules/test_nmcli.py b/tests/unit/plugins/modules/test_nmcli.py index 3231d4f331..893caa0db6 100644 --- a/tests/unit/plugins/modules/test_nmcli.py +++ b/tests/unit/plugins/modules/test_nmcli.py @@ -472,6 +472,38 @@ ipv6.ignore-auto-dns: no ipv6.ignore-auto-routes: no """ +TESTCASE_GENERIC_DNS4_OPTIONS = [ + { + 'type': 'generic', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'generic_non_existant', + 'ip4': '10.10.10.10/24', + 'gw4': '10.10.10.1', + 'state': 'present', + 'dns4_options': [], + 'dns6_options': [], + '_ansible_check_mode': False, + } +] + +TESTCASE_GENERIC_DNS4_OPTIONS_SHOW_OUTPUT = """\ +connection.id: non_existent_nw_device +connection.interface-name: generic_non_existant +connection.autoconnect: yes +ipv4.method: manual +ipv4.addresses: 10.10.10.10/24 +ipv4.gateway: 10.10.10.1 +ipv4.ignore-auto-dns: no +ipv4.ignore-auto-routes: no +ipv4.never-default: no +ipv4.dns-options: -- +ipv4.may-fail: yes +ipv6.dns-options: -- +ipv6.method: auto +ipv6.ignore-auto-dns: no +ipv6.ignore-auto-routes: no +""" + TESTCASE_GENERIC_ZONE = [ { 'type': 'generic', @@ -1533,6 +1565,13 @@ def mocked_generic_connection_dns_search_unchanged(mocker): execute_return=(0, TESTCASE_GENERIC_DNS4_SEARCH_SHOW_OUTPUT, "")) +@pytest.fixture +def mocked_generic_connection_dns_options_unchanged(mocker): + mocker_set(mocker, + connection_exists=True, + execute_return=(0, TESTCASE_GENERIC_DNS4_OPTIONS_SHOW_OUTPUT, "")) + + @pytest.fixture def mocked_generic_connection_zone_unchanged(mocker): mocker_set(mocker, @@ -2097,6 +2136,62 @@ def test_generic_connection_dns_search_unchanged(mocked_generic_connection_dns_s assert not results['changed'] +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC_DNS4_OPTIONS, indirect=['patch_ansible_module']) +def test_generic_connection_create_dns_options(mocked_generic_connection_create, capfd): + """ + Test : Generic connection created with dns options + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert 'ipv4.dns-options' in args[0] + assert 'ipv6.dns-options' in args[0] + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC_DNS4_OPTIONS, indirect=['patch_ansible_module']) +def test_generic_connection_modify_dns_options(mocked_generic_connection_create, capfd): + """ + Test : Generic connection modified with dns options + """ + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + args, kwargs = arg_list[0] + + assert 'ipv4.dns-options' in args[0] + assert 'ipv6.dns-options' in args[0] + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_GENERIC_DNS4_OPTIONS, indirect=['patch_ansible_module']) +def test_generic_connection_dns_options_unchanged(mocked_generic_connection_dns_options_unchanged, capfd): + """ + Test : Generic connection with dns options unchanged + """ + with pytest.raises(SystemExit): + nmcli.main() + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert not results['changed'] + + @pytest.mark.parametrize('patch_ansible_module', TESTCASE_CONNECTION, indirect=['patch_ansible_module']) def test_dns4_none(mocked_connection_exists, capfd): """ @@ -4141,6 +4236,7 @@ def test_bond_connection_unchanged(mocked_generic_connection_diff_check, capfd): never_default4=dict(type='bool', default=False), dns4=dict(type='list', elements='str'), dns4_search=dict(type='list', elements='str'), + dns4_options=dict(type='list', elements='str'), dns4_ignore_auto=dict(type='bool', default=False), method4=dict(type='str', choices=['auto', 'link-local', 'manual', 'shared', 'disabled']), may_fail4=dict(type='bool', default=True), @@ -4150,6 +4246,7 @@ def test_bond_connection_unchanged(mocked_generic_connection_diff_check, capfd): gw6_ignore_auto=dict(type='bool', default=False), dns6=dict(type='list', elements='str'), dns6_search=dict(type='list', elements='str'), + dns6_options=dict(type='list', elements='str'), dns6_ignore_auto=dict(type='bool', default=False), routes6=dict(type='list', elements='str'), routes6_extended=dict(type='list',