User modification task --check fails if user already exists but its groups do not

User modification task --check fails if user already exists but its groups do not

Hi !

New Ansible user here, nice to meet you :blush:

My problem

ansible-core version used: 2.19.8

My playbook creates groups and creates (or modifies) users on Linux remote servers:

  • it loops on a CSV file
  • creates groups with ansible.builtin.group
  • then creates (or modifies ) the users with ansible.builtin.user

Everything was nice and fancy until I had to add existing users to new groups.

The playbook --check has a wierd behaviour on the user task:

  • it succeeds if the user and groups do not already exist (both are flagged as changed)
  • it fails if the user already exists but at least one of the groups doesn’t, with the message: Group <whatever> does not exist

I feel like this behaviour isn’t normal : the --check should flag the user task as a change whether the user already exists or not.

Steps to reproduce

  • create a test_existinguser on a target server
  • run a playbook with the following tasks in --check mode against the target server:
tasks:
    - name: CREATE NEW GROUP
      ansible.builtin.group:
        name: test_newgroup
        state: present

    - name: CREATE NEW USER AND ADD IT TO THE NEW GROUP
      ansible.builtin.user:
        name: test_newuser
        groups: test_newgroup

    - name: MODIFY EXISTING USER BY ADDING IT TO THE NEW GROUP
      ansible.builtin.user:
        name: test_existinguser
        groups: test_newgroup
  • outputs to this:
TASK [CREATE NEW GROUP] **************************************************************************************************************************
changed: [<target_server>]

TASK [CREATE NEW USER] ***************************************************************************************************************************
changed: [<target_server>]

TASK [CHANGE EXISTING USER] **********************************************************************************************************************
fatal: [<target_server>]: FAILED! => {"changed": false, "msg": "Group test_newgroup does not exist"}

PLAY RECAP ***************************************************************************************************************************************
<target_server>            : ok=3    changed=2    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Debug tentative

The Group does not exist message:

The useradd source shows it returns this message from it’s error 6 :

#define E_NOTFOUND  6  /* specified user/group doesn't exist */

        grp = prefix_getgr_nam_gid (optarg);
        if (NULL == grp) {
          fprintf (stderr,
                   _("%s: group '%s' does not exist\n"),
                   Prog, optarg);
          exit (E_NOTFOUND);
        }

So imagine a situation like this. You are just adding the user to an existing group but you have made a typo in the group name. Running Ansible with --check mode will discover this error for you and you can fix it. If the logic is changed the way you are proposing, the --check mode will run just fine but your actual run will fail. This defeats the purpose of the --check mode.

What you are having problem with is that the --check mode is not a perfect simulation of the Ansible run and will never be. The reason is causality. You cannot have something do it’s job properly if it depends on something else before it doing the job properly too. In the --check mode no task does it’s job really.

Therefore: no group gets created → user cannot be created

There is no way around it. If you want your run to always be successful in the --check mode, you can use when: not ansible_check_mode to skip the tasks that are interdependent.

What can possibly be improved in the user module is that it tests for the existence of the group in the --check mode even when the user does not exist. That would make both “create user” and “modify user” tasks fail in --check mode.