diff --git a/changelogs/fragments/3081-add-wifi-option-to-nmcli-module.yml b/changelogs/fragments/3081-add-wifi-option-to-nmcli-module.yml new file mode 100644 index 0000000000..4425d955fc --- /dev/null +++ b/changelogs/fragments/3081-add-wifi-option-to-nmcli-module.yml @@ -0,0 +1,3 @@ +minor_changes: + - nmcli - add ``wifi`` option to support managing Wi-Fi settings such as ``hidden`` or ``mode`` + (https://github.com/ansible-collections/community.general/pull/3081). diff --git a/plugins/modules/net_tools/nmcli.py b/plugins/modules/net_tools/nmcli.py index 1750f9f99f..90fd5bbd0c 100644 --- a/plugins/modules/net_tools/nmcli.py +++ b/plugins/modules/net_tools/nmcli.py @@ -342,6 +342,14 @@ options: - Name of the Wireless router or the access point. type: str version_added: 3.0.0 + wifi: + description: + - 'The configuration of the Wifi connection. The valid attributes are listed on: + U(https://networkmanager.dev/docs/api/latest/settings-802-11-wireless.html).' + - 'For instance to create a hidden AP mode Wifi connection: + C({hidden: true, mode: ap}).' + type: dict + version_added: 3.5.0 ''' EXAMPLES = r''' @@ -658,6 +666,18 @@ EXAMPLES = r''' autoconnect: true state: present +- name: Create a hidden AP mode wifi connection + community.general.nmcli: + type: wifi + conn_name: ChocoMaster + ifname: wlo1 + ssid: ChocoMaster + wifi: + hidden: true + mode: ap + autoconnect: true + state: present + ''' RETURN = r"""# @@ -750,6 +770,7 @@ class Nmcli(object): self.dhcp_client_id = module.params['dhcp_client_id'] self.zone = module.params['zone'] self.ssid = module.params['ssid'] + self.wifi = module.params['wifi'] self.wifi_sec = module.params['wifi_sec'] if self.method4: @@ -878,8 +899,17 @@ class Nmcli(object): }) elif self.type == 'wifi': options.update({ + '802-11-wireless.ssid': self.ssid, 'connection.slave-type': 'bond' if self.master else None, }) + if self.wifi: + for name, value in self.wifi.items(): + # Disregard 'ssid' via 'wifi.ssid' + if name == 'ssid': + continue + options.update({ + '802-11-wireless.%s' % name: value + }) # Convert settings values based on the situation. for setting, value in options.items(): setting_type = self.settings_type(setting) @@ -978,7 +1008,8 @@ class Nmcli(object): 'ipv4.ignore-auto-routes', 'ipv4.may-fail', 'ipv6.ignore-auto-dns', - 'ipv6.ignore-auto-routes'): + 'ipv6.ignore-auto-routes', + '802-11-wireless.hidden'): return bool elif setting in ('ipv4.dns', 'ipv4.dns-search', @@ -1030,6 +1061,12 @@ class Nmcli(object): if self.type == "wifi": cmd.append('ssid') cmd.append(self.ssid) + if self.wifi: + for name, value in self.wifi.items(): + # Disallow setting 'ssid' via 'wifi.ssid' + if name == 'ssid': + continue + cmd += ['802-11-wireless.%s' % name, value] if self.wifi_sec: for name, value in self.wifi_sec.items(): cmd += ['wifi-sec.%s' % name, value] @@ -1255,6 +1292,7 @@ def main(): ip_tunnel_local=dict(type='str'), ip_tunnel_remote=dict(type='str'), ssid=dict(type='str'), + wifi=dict(type='dict'), wifi_sec=dict(type='dict', no_log=True), ), mutually_exclusive=[['never_default4', 'gw4']], diff --git a/tests/unit/plugins/modules/net_tools/test_nmcli.py b/tests/unit/plugins/modules/net_tools/test_nmcli.py index 63ec60537c..6df320a0c7 100644 --- a/tests/unit/plugins/modules/net_tools/test_nmcli.py +++ b/tests/unit/plugins/modules/net_tools/test_nmcli.py @@ -469,6 +469,22 @@ ipv6.ignore-auto-dns: no ipv6.ignore-auto-routes: no """ +TESTCASE_WIRELESS = [ + { + 'type': 'wifi', + 'conn_name': 'non_existent_nw_device', + 'ifname': 'wireless_non_existant', + 'ip4': '10.10.10.10/24', + 'ssid': 'Brittany', + 'wifi': { + 'hidden': True, + 'mode': 'ap', + }, + 'state': 'present', + '_ansible_check_mode': False, + } +] + def mocker_set(mocker, connection_exists=False, @@ -1530,3 +1546,38 @@ def test_ethernet_connection_static_unchanged(mocked_ethernet_connection_static_ results = json.loads(out) assert not results.get('failed') assert not results['changed'] + + +@pytest.mark.parametrize('patch_ansible_module', TESTCASE_WIRELESS, indirect=['patch_ansible_module']) +def test_create_wireless(mocked_generic_connection_create, capfd): + """ + Test : Create wireless connection + """ + + with pytest.raises(SystemExit): + nmcli.main() + + assert nmcli.Nmcli.execute_command.call_count == 1 + arg_list = nmcli.Nmcli.execute_command.call_args_list + add_args, add_kw = arg_list[0] + + assert add_args[0][0] == '/usr/bin/nmcli' + assert add_args[0][1] == 'con' + assert add_args[0][2] == 'add' + assert add_args[0][3] == 'type' + assert add_args[0][4] == 'wifi' + assert add_args[0][5] == 'con-name' + assert add_args[0][6] == 'non_existent_nw_device' + + add_args_text = list(map(to_text, add_args[0])) + for param in ['connection.interface-name', 'wireless_non_existant', + 'ipv4.addresses', '10.10.10.10/24', + '802-11-wireless.ssid', 'Brittany', + '802-11-wireless.mode', 'ap', + '802-11-wireless.hidden', 'yes']: + assert param in add_args_text + + out, err = capfd.readouterr() + results = json.loads(out) + assert not results.get('failed') + assert results['changed']