Repository: ansible/ansible

Issue:
regression in user module on FreeBSD 11.1 target starting with v2.6.0: always 'changed'
##### SUMMARY
the user module always returns 'changed' on FreeBSD targets (does not happen on Debian targets)

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
user module
<!--- Insert, BELOW THIS COMMENT, the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path-->

##### ANSIBLE VERSION
```
ansible 2.6.1
   python version = 2.7.15
```

##### CONFIGURATION

```ansible-config dump --only-changed``` is empty

##### OS / ENVIRONMENT
Target: FreeBSD 11.1

##### STEPS TO REPRODUCE

using ansible < 2.6.0:
clone https://github.com/nusenu/ansible-relayor 
(tested with 730a332b02697be1d93cf1b254b4827443fb5e5b)
```
kitchen test t-guard-freebsd-11
```
--> works as expected

do the same with ansible >= 2.6.0 (confirmed with ansible 2.6.0 and 2.6.1) and get failed idempotency tests:
```
idempotency test [Failed]
```

the relevant task failing the idempotency test after upgrading ansible:

```
- name: Ensure per-instance tor users exist
  become: yes
  user:
    name: _tor-{{ item.0.ipv4 }}_{{ item.1.orport }}
    system: yes
    shell: /bin/false
    createhome: no
    home: "{{ tor_DataDir }}/{{ item.0.ipv4 }}_{{ item.1.orport }}"
  with_nested:
   - "{{ tor_ips }}"
   - "{{ tor_ports }}"
```

##### EXPECTED RESULTS
idempotency should not fail as with ansible versions before 2.6.0

##### ACTUAL RESULTS

```
idempotency test [Failed]
        1> {"changed_module_name": "", "changed_msg": "All items completed", "changed_module_args": "", "host": "guard-freebsd-11", "task_name": "Ensure per-instance tor users exist"}
```



Discussion:
Files identified in the description:
* [lib/ansible/modules/system/user.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/user.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @samdoran @sfromm
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
FWIW.

There is no /bin/false in default FreeBSD installation.

    # /bin/false
     bash: /bin/false: No such file or directory
     # which false 
     /usr/bin/false

When I try to add a user from the command-line with */bin/false* the *adduser* utility silently changes it to */bin/sh*

Parameter name in the example should be quoted, I think.

    name: "_tor-{{ item.0.ipv4 }}_{{ item.1.orport }}"
@vbotka: thank you for your comments, but even after removing the `shell:` line and adding "" to the `name:` the problem persists
With FreeBSD 11.2 both controller and host and Ansible 2.6.1 the playbook [1] succeeded for the 1st time [2] and then crashed [3]. -vvv output is here https://pastebin.com/DfJF30yZ


[1]

    - name: 'Test user module'
      hosts: vm3
      gather_facts: no
      tasks:
        - name: Create user
          user:
            name: test
            system: yes
            createhome: no
            home: /home/test 

[2]

    % ansible-playbook test1.yml 
    PLAY [Test user module] *********************************************************************
    TASK [Create user] **************************************************************************
    changed: [vm3]
    PLAY RECAP **********************************************************************************
    vm3                        : ok=1    changed=1    unreachable=0    failed=0

[3]

    % ansible-playbook test1.yml
    PLAY [Test user module] *********************************************************************
    TASK [Create user] **************************************************************************
    An exception occurred during task execution. To see the full traceback, use -vvv. The error was: 
    AttributeError: 'FreeBsdUser' object has no attribute 'createhome'
    fatal: [vm3]: FAILED! => changed=false 
      module_stderr: |-
    Traceback (most recent call last):
      File "/tmp/ansible_yhmawU/ansible_module_user.py", line 2487, in <module>
        main()
      File "/tmp/ansible_yhmawU/ansible_module_user.py", line 2426, in main
        (rc, out, err) = user.modify_user()
      File "/tmp/ansible_yhmawU/ansible_module_user.py", line 1011, in modify_user
        if (info[5] != self.home and self.move_home) or (not os.path.exists(self.home) and self.createhome):
     AttributeError: 'FreeBsdUser' object has no attribute 'createhome'
    module_stdout: ''
    msg: MODULE FAILURE
    rc: 1
    PLAY RECAP **********************************************************************************
    vm3                        : ok=0    changed=0    unreachable=0    failed=1

@vbotka That is fixed in #42711.

Resolving patch:
diff --git a/lib/ansible/modules/system/user.py b/lib/ansible/modules/system/user.py
--- a/lib/ansible/modules/system/user.py
+++ b/lib/ansible/modules/system/user.py
@@ -1010,8 +1010,9 @@ def modify_user(self):
         if self.home is not None:
             if (info[5] != self.home and self.move_home) or (not os.path.exists(self.home) and self.create_home):
                 cmd.append('-m')
-            cmd.append('-d')
-            cmd.append(self.home)
+            if info[5] != self.home:
+                cmd.append('-d')
+                cmd.append(self.home)
 
             if self.skeleton is not None:
                 cmd.append('-k')


Test patch:
diff --git a/test/integration/targets/user/tasks/main.yml b/test/integration/targets/user/tasks/main.yml
--- a/test/integration/targets/user/tasks/main.yml
+++ b/test/integration/targets/user/tasks/main.yml
@@ -37,7 +37,13 @@
   user:
     name: ansibulluser
     state: present
-  register: user_test0
+  register: user_test0_0
+
+- name: create the user again
+  user:
+    name: ansibulluser
+    state: present
+  register: user_test0_1
 
 - debug:
     var: user_test0
@@ -54,9 +60,50 @@
 - name: validate results for testcase 0
   assert:
     that:
-      - user_test0 is changed
+      - user_test0_0 is changed
+      - user_test0_1 is not changed
       - '"ansibulluser" in user_names.stdout_lines'
 
+# https://github.com/ansible/ansible/issues/42484
+# Skipping macOS for now since there is a bug when changing home directory
+- block:
+    - name: create user specifying home
+      user:
+        name: ansibulluser
+        state: present
+        home: "{{ user_home_prefix[ansible_system] }}/ansibulluser"
+      register: user_test3_0
+
+    - name: create user again specifying home
+      user:
+        name: ansibulluser
+        state: present
+        home: "{{ user_home_prefix[ansible_system] }}/ansibulluser"
+      register: user_test3_1
+
+    - name: change user home
+      user:
+        name: ansibulluser
+        state: present
+        home: "{{ user_home_prefix[ansible_system] }}/ansibulluser-mod"
+      register: user_test3_2
+
+    - name: change user home back
+      user:
+        name: ansibulluser
+        state: present
+        home: "{{ user_home_prefix[ansible_system] }}/ansibulluser"
+      register: user_test3_3
+
+    - name: validate results for testcase 3
+      assert:
+        that:
+          - user_test3_0 is not changed
+          - user_test3_1 is not changed
+          - user_test3_2 is changed
+          - user_test3_3 is changed
+  when: ansible_system != 'Darwin'
+
 
 ## user check
 
@@ -65,7 +112,7 @@
     name: "{{ user_names.stdout_lines | random }}"
     state: present
     create_home: no
-  with_sequence: start=1 end=5
+  loop: "{{ range(1, 5+1) | list }}"
   register: user_test1
 
 - debug:
diff --git a/test/integration/targets/user/vars/main.yml b/test/integration/targets/user/vars/main.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/user/vars/main.yml
@@ -0,0 +1,5 @@
+user_home_prefix:
+  Linux: '/home'
+  FreeBSD: '/home'
+  SunOS: '/home'
+  Darwin: '/Users'