Repository: ansible/ansible-modules-core

Issue:
User module makes changes in check mode on OS X
##### Issue Type:

Bug Report
##### Ansible Version:

```
ansible 2.1.0 (devel 699a854bf3) last updated 2016/02/03 23:35:36 (GMT -700)
  lib/ansible/modules/core: (detached HEAD 93d02189f6) last updated 2016/02/02 23:37:28 (GMT -700)
  lib/ansible/modules/extras: (detached HEAD fff5ae6994) last updated 2016/02/02 23:37:31 (GMT -700)
  config file =
  configured module search path = Default w/o overrides
```
##### Ansible Configuration:

N/A
##### Environment:

OS X 10.10.5 (Yosemite)
##### Summary:

The user module makes changes to users, such as changing the UID, in check mode.
##### Steps To Reproduce:

Assuming a user test1 already exists with a UID other than 3333:

```
dscl . -read /Users/test1 UniqueID
ansible localhost -m user -a 'user=test1 uid=3333' --check
dscl . -read /Users/test1 UniqueID
```
##### Expected Results:

No changes should be made.
##### Actual Results:

The UID of the user is changed:

```
sh-3.2# dscl . -read /Users/test1 UniqueID
UniqueID: 9999
sh-3.2# ansible localhost -m user -a 'user=test1 uid=3333' --check
localhost | SUCCESS => {
    "append": false,
    "changed": true,
    "comment": "",
    "group": 4294967295,
    "home": "/Users/test1",
    "move_home": false,
    "name": "test1",
    "shell": "/usr/bin/false",
    "state": "present",
    "uid": 3333
}
sh-3.2# dscl . -read /Users/test1 UniqueID
UniqueID: 3333
```



Discussion:
The user module will also set the password in check mode.

It also adds and removes groups, as well as changing users to be hidden/not hidden.


Resolving patch:
diff --git a/system/user.py b/system/user.py
--- a/system/user.py
+++ b/system/user.py
@@ -296,8 +296,12 @@ def __init__(self, module):
             self.ssh_file = os.path.join('.ssh', 'id_%s' % self.ssh_type)
 
 
-    def execute_command(self, cmd, use_unsafe_shell=False, data=None):
-        return self.module.run_command(cmd, use_unsafe_shell=use_unsafe_shell, data=data)
+    def execute_command(self, cmd, use_unsafe_shell=False, data=None, obey_checkmode=True):
+        if self.module.check_mode and obey_checkmode:
+            self.module.debug('In check mode, would have run: "%s"' % cmd)
+            return (0, '','')
+        else:
+            return self.module.run_command(cmd, use_unsafe_shell=use_unsafe_shell, data=data)
 
     def remove_user_userdel(self):
         cmd = [self.module.get_bin_path('userdel', True)]
@@ -392,9 +396,8 @@ def _check_usermod_append(self):
         if not os.access(usermod_path, os.X_OK):
             return False
 
-        cmd = [usermod_path]
-        cmd.append('--help')
-        rc, data1, data2 = self.execute_command(cmd)
+        cmd = [usermod_path, '--help']
+        (rc, data1, data2) = self.execute_command(cmd, obey_checkmode=False)
         helpout = data1 + data2
 
         # check if --append exists
@@ -484,8 +487,6 @@ def modify_user_usermod(self):
         # skip if no changes to be made
         if len(cmd) == 1:
             return (None, '', '')
-        elif self.module.check_mode:
-            return (0, '', '')
 
         cmd.append(self.name)
         return self.execute_command(cmd)
@@ -592,8 +593,6 @@ def ssh_key_gen(self):
                 return (1, '', 'Failed to create %s: %s' % (ssh_dir, str(e)))
         if os.path.exists(ssh_key_file):
             return (None, 'Key already exists', '')
-        if self.module.check_mode:
-            return (0, '', '')
         cmd = [self.module.get_bin_path('ssh-keygen', True)]
         cmd.append('-t')
         cmd.append(self.ssh_type)
@@ -626,7 +625,7 @@ def ssh_key_fingerprint(self):
         cmd.append('-f')
         cmd.append(ssh_key_file)
 
-        return self.execute_command(cmd)
+        return self.execute_command(cmd, obey_checkmode=False)
 
     def get_ssh_public_key(self):
         ssh_public_key_file = '%s.pub' % self.get_ssh_key_path()
@@ -864,8 +863,6 @@ def modify_user(self):
 
         # modify the user if cmd will do anything
         if cmd_len != len(cmd):
-            if self.module.check_mode:
-                return (0, '', '')
             (rc, out, err) = self.execute_command(cmd)
             if rc is not None and rc != 0:
                 self.module.fail_json(name=self.name, msg=err, rc=rc)
@@ -874,8 +871,6 @@ def modify_user(self):
 
         # we have to set the password in a second command
         if self.update_password == 'always' and self.password is not None and info[1] != self.password:
-            if self.module.check_mode:
-                return (0, '', '')
             cmd = [
                 self.module.get_bin_path('chpass', True),
                 '-p',
@@ -1027,7 +1022,7 @@ def modify_user(self):
             # find current login class
             user_login_class = None
             userinfo_cmd = [self.module.get_bin_path('userinfo', True), self.name]
-            (rc, out, err) = self.execute_command(userinfo_cmd)
+            (rc, out, err) = self.execute_command(userinfo_cmd, obey_checkmode=False)
 
             for line in out.splitlines():
                 tokens = line.split()
@@ -1048,8 +1043,6 @@ def modify_user(self):
         # skip if no changes to be made
         if len(cmd) == 1:
             return (None, '', '')
-        elif self.module.check_mode:
-            return (0, '', '')
 
         cmd.append(self.name)
         return self.execute_command(cmd)
@@ -1207,8 +1200,6 @@ def modify_user(self):
         # skip if no changes to be made
         if len(cmd) == 1:
             return (None, '', '')
-        elif self.module.check_mode:
-            return (0, '', '')
 
         cmd.append(self.name)
         return self.execute_command(cmd)
@@ -1283,14 +1274,12 @@ def create_user(self):
 
         cmd.append(self.name)
 
-        if self.module.check_mode:
-            return (0, '', '')
-        else:
-            (rc, out, err) = self.execute_command(cmd)
-            if rc is not None and rc != 0:
-                self.module.fail_json(name=self.name, msg=err, rc=rc)
+        (rc, out, err) = self.execute_command(cmd)
+        if rc is not None and rc != 0:
+            self.module.fail_json(name=self.name, msg=err, rc=rc)
 
-            # we have to set the password by editing the /etc/shadow file 
+        if not self.module.check_mode:
+            # we have to set the password by editing the /etc/shadow file
             if self.password is not None:
                 try:
                     lines = []
@@ -1307,7 +1296,7 @@ def create_user(self):
                 except Exception, err:
                     self.module.fail_json(msg="failed to update users password: %s" % str(err))
 
-            return (rc, out, err)
+        return (rc, out, err)
 
     def modify_user_usermod(self):
         cmd = [self.module.get_bin_path('usermod', True)]
@@ -1367,16 +1356,14 @@ def modify_user_usermod(self):
 
         # modify the user if cmd will do anything
         if cmd_len != len(cmd):
-            (rc, out, err) = (0, '', '')
-            if not self.module.check_mode:
-                cmd.append(self.name)
-                (rc, out, err) = self.execute_command(cmd)
-                if rc is not None and rc != 0:
-                    self.module.fail_json(name=self.name, msg=err, rc=rc)
+            cmd.append(self.name)
+            (rc, out, err) = self.execute_command(cmd)
+            if rc is not None and rc != 0:
+                self.module.fail_json(name=self.name, msg=err, rc=rc)
         else:
             (rc, out, err) = (None, '', '')
 
-        # we have to set the password by editing the /etc/shadow file 
+        # we have to set the password by editing the /etc/shadow file
         if self.update_password == 'always' and self.password is not None and info[1] != self.password:
             (rc, out, err) = (0, '', '')
             if not self.module.check_mode:
@@ -1388,7 +1375,7 @@ def modify_user_usermod(self):
                             lines.append(line)
                             continue
                         fields[1] = self.password
-                        fields[2] = str(int(time.time() / 86400))	
+                        fields[2] = str(int(time.time() / 86400))
                         line = ':'.join(fields)
                         lines.append('%s\n' % line)
                     open(self.SHADOWFILE, 'w+').writelines(lines)
@@ -1436,7 +1423,7 @@ def _get_dscl(self):
     def _list_user_groups(self):
         cmd = self._get_dscl()
         cmd += [ '-search', '/Groups', 'GroupMembership', self.name ]
-        (rc, out, err) = self.execute_command(cmd)
+        (rc, out, err) = self.execute_command(cmd, obey_checkmode=False)
         groups = []
         for line in out.splitlines():
             if line.startswith(' ') or line.startswith(')'):
@@ -1448,7 +1435,7 @@ def _get_user_property(self, property):
         '''Return user PROPERTY as given my dscl(1) read or None if not found.'''
         cmd = self._get_dscl()
         cmd += [ '-read', '/Users/%s' % self.name, property ]
-        (rc, out, err) = self.execute_command(cmd)
+        (rc, out, err) = self.execute_command(cmd, obey_checkmode=False)
         if rc != 0:
             return None
         # from dscl(1)
@@ -1471,7 +1458,7 @@ def _get_next_uid(self):
         '''Return the next available uid'''
         cmd = self._get_dscl()
         cmd += ['-list', '/Users', 'UniqueID']
-        (rc, out, err) = self.execute_command(cmd)
+        (rc, out, err) = self.execute_command(cmd, obey_checkmode=False)
         if rc != 0:
             self.module.fail_json(
                 msg="Unable to get the next available uid",
@@ -1504,8 +1491,7 @@ def _change_user_password(self):
             cmd += [ '-create', '/Users/%s' % self.name, 'Password', '*']
         (rc, out, err) = self.execute_command(cmd)
         if rc != 0:
-            self.module.fail_json(msg='Error when changing password',
-                                  err=err, out=out, rc=rc)
+            self.module.fail_json(msg='Error when changing password', err=err, out=out, rc=rc)
         return (rc, out, err)
 
     def _make_group_numerical(self):
@@ -1526,13 +1512,11 @@ def __modify_group(self, group, action):
             option = '-a'
         else:
             option = '-d'
-        cmd = [ 'dseditgroup', '-o', 'edit', option, self.name,
-                '-t', 'user', group ]
+        cmd = [ 'dseditgroup', '-o', 'edit', option, self.name, '-t', 'user', group ]
         (rc, out, err) = self.execute_command(cmd)
         if rc != 0:
             self.module.fail_json(msg='Cannot %s user "%s" to group "%s".'
-                                  % (action, self.name, group),
-                                  err=err, out=out, rc=rc)
+                                  % (action, self.name, group), err=err, out=out, rc=rc)
         return (rc, out, err)
 
     def _modify_group(self):
@@ -1551,8 +1535,6 @@ def _modify_group(self):
             target = set([])
 
         for remove in current - target:
-            if self.module.check_mode:
-                return (0, '', '', True)
             (_rc, _err, _out) = self.__modify_group(remove, 'delete')
             rc += rc
             out += _out
@@ -1560,8 +1542,6 @@ def _modify_group(self):
             changed = True
 
         for add in target - current:
-            if self.module.check_mode:
-                return (0, '', '', True)
             (_rc, _err, _out) = self.__modify_group(add, 'add')
             rc += _rc
             out += _out
@@ -1579,7 +1559,7 @@ def _update_system_user(self):
 
         # http://support.apple.com/kb/HT5017?viewlocale=en_US
         cmd = [ 'defaults', 'read', plist_file, 'HiddenUsersList' ]
-        (rc, out, err) = self.execute_command(cmd)
+        (rc, out, err) = self.execute_command(cmd, obey_checkmode=False)
         # returned value is
         # (
         #   "_userA",
@@ -1598,34 +1578,25 @@ def _update_system_user(self):
             if not self.name in hidden_users:
                 cmd = [ 'defaults', 'write', plist_file,
                         'HiddenUsersList', '-array-add', self.name ]
-                if self.module.check_mode:
-                    return 0
                 (rc, out, err) = self.execute_command(cmd)
                 if rc != 0:
-                    self.module.fail_json(
-                        msg='Cannot user "%s" to hidden user list.'
-                        % self.name, err=err, out=out, rc=rc)
+                    self.module.fail_json( msg='Cannot user "%s" to hidden user list.' % self.name, err=err, out=out, rc=rc)
                 return 0
         else:
             if self.name in hidden_users:
                 del(hidden_users[hidden_users.index(self.name)])
 
-                cmd = [ 'defaults', 'write', plist_file,
-                        'HiddenUsersList', '-array' ] +  hidden_users
-                if self.module.check_mode:
-                    return 0
+                cmd = [ 'defaults', 'write', plist_file, 'HiddenUsersList', '-array' ] +  hidden_users
                 (rc, out, err) = self.execute_command(cmd)
                 if rc != 0:
-                    self.module.fail_json(
-                        msg='Cannot remove user "%s" from hidden user list.'
-                        % self.name, err=err, out=out, rc=rc)
+                    self.module.fail_json( msg='Cannot remove user "%s" from hidden user list.' % self.name, err=err, out=out, rc=rc)
                 return 0
 
     def user_exists(self):
         '''Check is SELF.NAME is a known user on the system.'''
         cmd = self._get_dscl()
         cmd += [ '-list', '/Users/%s' % self.name]
-        (rc, out, err) = self.execute_command(cmd)
+        (rc, out, err) = self.execute_command(cmd, obey_checkmode=False)
         return rc == 0
 
     def remove_user(self):
@@ -1637,9 +1608,7 @@ def remove_user(self):
         (rc, out, err) = self.execute_command(cmd)
 
         if rc != 0:
-            self.module.fail_json(
-                msg='Cannot delete user "%s".'
-                % self.name, err=err, out=out, rc=rc)
+            self.module.fail_json( msg='Cannot delete user "%s".' % self.name, err=err, out=out, rc=rc)
 
         if self.force:
             if os.path.exists(info[5]):
@@ -1653,9 +1622,7 @@ def create_user(self, command_name='dscl'):
         cmd += [ '-create', '/Users/%s' % self.name]
         (rc, err, out) = self.execute_command(cmd)
         if rc != 0:
-            self.module.fail_json(
-                msg='Cannot create user "%s".'
-                % self.name, err=err, out=out, rc=rc)
+            self.module.fail_json( msg='Cannot create user "%s".' % self.name, err=err, out=out, rc=rc)
 
 
         self._make_group_numerical()
@@ -1666,20 +1633,19 @@ def create_user(self, command_name='dscl'):
         if self.createhome:
             if self.home is None:
                 self.home = '/Users/%s' % self.name
-            if not os.path.exists(self.home):
-                os.makedirs(self.home)
-            self.chown_homedir(int(self.uid), int(self.group), self.home)
+            if not self.module.check_mode:
+                if not os.path.exists(self.home):
+                    os.makedirs(self.home)
+                self.chown_homedir(int(self.uid), int(self.group), self.home)
 
         for field in self.fields:
             if self.__dict__.has_key(field[0]) and self.__dict__[field[0]]:
 
                 cmd = self._get_dscl()
-                cmd += [ '-create', '/Users/%s' % self.name,
-                         field[1], self.__dict__[field[0]]]
+                cmd += [ '-create', '/Users/%s' % self.name, field[1], self.__dict__[field[0]]]
                 (rc, _err, _out) = self.execute_command(cmd)
                 if rc != 0:
-                    self.module.fail_json(
-                        msg='Cannot add property "%s" to user "%s".'
+                    self.module.fail_json( msg='Cannot add property "%s" to user "%s".'
                         % (field[0], self.name), err=err, out=out, rc=rc)
 
                 out += _out
@@ -1714,10 +1680,7 @@ def modify_user(self):
                 current = self._get_user_property(field[1])
                 if current is None or current != self.__dict__[field[0]]:
                     cmd = self._get_dscl()
-                    cmd += [ '-create', '/Users/%s' % self.name,
-                             field[1], self.__dict__[field[0]]]
-                    if self.module.check_mode:
-                        return (0, '', '')
+                    cmd += [ '-create', '/Users/%s' % self.name, field[1], self.__dict__[field[0]]]
                     (rc, _err, _out) = self.execute_command(cmd)
                     if rc != 0:
                         self.module.fail_json(
@@ -1727,8 +1690,6 @@ def modify_user(self):
                     out += _out
                     err += _err
         if self.update_password == 'always' and self.password is not None:
-            if self.module.check_mode:
-                return (0, '', '')
             (rc, _err, _out) = self._change_user_password()
             out += _out
             err += _err
@@ -1880,16 +1841,12 @@ def modify_user_usermod(self):
         # skip if no changes to be made
         if len(cmd) == 1:
             (rc, out, err) = (None, '', '')
-        elif self.module.check_mode:
-            return (0, '', '')
         else:
             cmd.append(self.name)
             (rc, out, err) = self.execute_command(cmd)
 
         # set password with chpasswd
         if self.update_password == 'always' and self.password is not None and info[1] != self.password:
-            if self.module.check_mode:
-                return (0, '', '')
             cmd = []
             cmd.append(self.module.get_bin_path('chpasswd', True))
             cmd.append('-e')
@@ -2049,8 +2006,6 @@ def modify_user(self):
         # skip if no changes to be made
         if len(cmd) == 1:
             return (None, '', '')
-        elif self.module.check_mode:
-            return (0, '', '')
 
         cmd.append(self.name)
         return self.execute_command(cmd)
@@ -2128,8 +2083,11 @@ def main():
             if module.check_mode:
                 module.exit_json(changed=True)
             (rc, out, err) = user.create_user()
-            result['system'] = user.system
-            result['createhome'] = user.createhome
+            if module.check_mode:
+                result['system'] = user.name
+            else:
+                result['system'] = user.system
+                result['createhome'] = user.createhome
         else:
             # modify user (note: this function is check mode aware)
             (rc, out, err) = user.modify_user()


Test patch: