Repository: ansible-collections/community.general

Issue:
Added support in nmcli for ipv4.dns-options
##### SUMMARY
<!--- Describe the change below, including rationale and design decisions -->
the nmcli command has an important option "ipv4.dns-options", This PR adds the support for this option in the nmcli  module
<!--- HINT: Include "Fixes #nnn" if you are fixing an existing issue -->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- Feature Pull Request


##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
community.general.nmcli --> plugins/modules/net_tools/nmcli.py

##### ADDITIONAL INFORMATION
<!--- Include additional information to help people understand the change here -->
<!--- A step-by-step reproduction of the problem is helpful if there is no related issue -->
Simple pull request to add the feature to the existing module

<!--- Paste verbatim command output below, e.g. before and after your change -->




Discussion:

Resolving patch:
diff --git a/plugins/modules/nmcli.py b/plugins/modules/nmcli.py
--- a/plugins/modules/nmcli.py
+++ b/plugins/modules/nmcli.py
@@ -191,6 +191,12 @@
             - 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 @@
             - 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 @@ def __init__(self, module):
         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 @@ def __init__(self, module):
         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 @@ def connection_options(self, detect_change=False):
                 '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 @@ def connection_options(self, detect_change=False):
                 '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 @@ def settings_type(setting):
                          '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',


Test patch:
diff --git a/tests/unit/plugins/modules/test_nmcli.py b/tests/unit/plugins/modules/test_nmcli.py
--- a/tests/unit/plugins/modules/test_nmcli.py
+++ b/tests/unit/plugins/modules/test_nmcli.py
@@ -472,6 +472,38 @@
 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',