Repository: ansible/ansible

Issue:
junos_user with purge option enabled fails
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report


##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
junos_user

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
2.3
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->

##### SUMMARY
<!--- Explain the problem briefly -->

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
      junos_user:
        name: user
        purge: yes
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
It should delete all the login users configured on the device except the user mentioned in playbook ie. logged-in user.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
Fails with error
<!--- Paste verbatim command output between quotes below -->
```
fatal: [vsrx01]: FAILED! => {
    "changed": false,
    "err": "[Errno 2] No such file or directory",
    "failed": true,
    "invocation": {
        "module_args": {
            "full_name": null,
            "host": null,
            "name": "ansible",
            "password": null,
            "port": null,
            "provider": {
                "host": "vsrx",
                "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
                "port": null,
                "ssh_keyfile": null,
                "timeout": null,
                "transport": null,
                "username": "ansible"
            },
            "purge": true,
            "role": "unauthorized",
            "ssh_keyfile": null,
            "sshkey": null,
            "state": "present",
            "timeout": 10,
            "transport": null,
            "username": null,
            "users": null
        }
    },
    "msg": "unable to connect to socket"
}
```



Discussion:
cc @Qalthos
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @privateip
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/module_utils/junos.py b/lib/ansible/module_utils/junos.py
--- a/lib/ansible/module_utils/junos.py
+++ b/lib/ansible/module_utils/junos.py
@@ -211,7 +211,13 @@ def load_config(module, candidate, warnings, action='merge', format='xml'):
 
 
 def get_param(module, key):
-    return module.params[key] or module.params['provider'].get(key)
+    if module.params.get(key):
+        value = module.params[key]
+    elif module.params.get('provider'):
+        value = module.params['provider'].get(key)
+    else:
+        value = None
+    return value
 
 
 def map_params_to_obj(module, param_to_xpath_map, param=None):
diff --git a/lib/ansible/modules/network/junos/junos_user.py b/lib/ansible/modules/network/junos/junos_user.py
--- a/lib/ansible/modules/network/junos/junos_user.py
+++ b/lib/ansible/modules/network/junos/junos_user.py
@@ -57,7 +57,6 @@
         remote system.  User accounts can have more than one role
         configured.
     required: false
-    default: read-only
     choices: ['operator', 'read-only', 'super-user', 'unauthorized']
   sshkey:
     description:
@@ -71,7 +70,7 @@
       - The C(purge) argument instructs the module to consider the
         users definition absolute.  It will remove any previously configured
         users on the device with the exception of the current defined
-        set of users.
+        set of aggregate.
     required: false
     default: false
   state:
@@ -113,7 +112,8 @@
 
 - name: remove all user accounts except ansible
   junos_user:
-    name: ansible
+    aggregate:
+    - name: ansible
     purge: yes
 
 - name: Create list of users
@@ -147,6 +147,7 @@
 
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.network_common import remove_default_spec
+from ansible.module_utils.netconf import send_request
 from ansible.module_utils.junos import junos_argument_spec, check_args
 from ansible.module_utils.junos import commit_configuration, discard_changes
 from ansible.module_utils.junos import load_config, locked_config
@@ -161,27 +162,47 @@
 USE_PERSISTENT_CONNECTION = True
 
 
-def map_obj_to_ele(want):
+def handle_purge(module, want):
+    want_users = [item['name'] for item in want]
     element = Element('system')
-    login = SubElement(element, 'login', {'replace': 'replace'})
+    login = SubElement(element, 'login')
+
+    reply = send_request(module, Element('get-configuration'), ignore_warning=False)
+    users = reply.xpath('configuration/system/login/user/name')
+    if users:
+        for item in users:
+            name = item.text
+            if name not in want_users and name != 'root':
+                user = SubElement(login, 'user', {'operation': 'delete'})
+                SubElement(user, 'name').text = name
+    if element.xpath('/system/login/user/name'):
+        return element
+
+
+def map_obj_to_ele(module, want):
+    element = Element('system')
+    login = SubElement(element, 'login')
 
     for item in want:
         if item['state'] != 'present':
+            if item['name'] == 'root':
+                module.fail_json(msg="cannot delete the 'root' account.")
             operation = 'delete'
         else:
-            operation = 'replace'
+            operation = 'merge'
 
         user = SubElement(login, 'user', {'operation': operation})
 
         SubElement(user, 'name').text = item['name']
 
-        if operation == 'replace':
+        if operation == 'merge':
             if item['active']:
                 user.set('active', 'active')
             else:
                 user.set('inactive', 'inactive')
 
-            SubElement(user, 'class').text = item['role']
+            if item['role']:
+                SubElement(user, 'class').text = item['role']
 
             if item.get('full_name'):
                 SubElement(user, 'full-name').text = item['full_name']
@@ -262,7 +283,7 @@ def main():
     element_spec = dict(
         name=dict(),
         full_name=dict(),
-        role=dict(choices=ROLES, default='unauthorized'),
+        role=dict(choices=ROLES),
         sshkey=dict(),
         state=dict(choices=['present', 'absent'], default='present'),
         active=dict(type='bool', default=True)
@@ -294,16 +315,16 @@ def main():
     result = {'changed': False, 'warnings': warnings}
 
     want = map_params_to_obj(module)
-    ele = map_obj_to_ele(want)
+    ele = map_obj_to_ele(module, want)
 
-    kwargs = {}
+    purge_request = None
     if module.params['purge']:
-        kwargs['action'] = 'replace'
-    else:
-        kwargs['action'] = 'merge'
+        purge_request = handle_purge(module, want)
 
     with locked_config(module):
-        diff = load_config(module, tostring(ele), warnings, **kwargs)
+        if purge_request:
+            load_config(module, tostring(purge_request), warnings, action='replace')
+        diff = load_config(module, tostring(ele), warnings, action='merge')
 
         commit = not module.check_mode
         if diff:


Test patch:
diff --git a/test/integration/targets/junos_user/tests/netconf/basic.yaml b/test/integration/targets/junos_user/tests/netconf/basic.yaml
--- a/test/integration/targets/junos_user/tests/netconf/basic.yaml
+++ b/test/integration/targets/junos_user/tests/netconf/basic.yaml
@@ -168,3 +168,26 @@
       - "result.changed == true"
       - "'<name>test_user1</name>' not in config.xml"
       - "'<name>test_user2</name>' not in config.xml"
+
+- name: Create list of users
+  junos_user:
+    aggregate:
+      - name: ansible
+      - {name: test_user1, full_name: test_user2, role: operator}
+      - {name: test_user2, full_name: test_user2, role: read-only}
+    provider: "{{ netconf }}"
+  register: result
+
+- name: Purge users except the users in aggregate
+  junos_user:
+    aggregate:
+      - name: ansible
+    purge: True
+    provider: "{{ netconf }}"
+  register: result
+
+- assert:
+    that:
+      - "result.changed == true"
+      - result.diff.prepared | search("\- *user test_user1")
+      - result.diff.prepared | search("\- *user test_user2")