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.

Thank you for your reply.

I changed my playbook to skip the problematic tasks in --check mode, and hope that the user module will be “fixed” for consistency.

There is nothing to fix here, Ansible is working as designed as @bvitnik explained above.

An alternative to skipping the task in check mode would be to add a check for the user and group existing before the task to add the user to the group and only run the task when the user and group exist.

There is nothing to fix here

Behaviour should be the same with and without --check

When Ansible tries to check that adding a user to a group, that doesn’t exist, works, it correctly fails because the group doesn’t exist and the group doesn’t exist because the group creation task was run in check mode therefore no changes were made to the system, as I suggested above you could add a check that the group exists before running the tasks to check that a user can be added to a group.

OP talks about something else. It’s the CREATE NEW USER AND ADD IT TO THE NEW GROUP task in his example playbook. In it, Ansible tries to create a user with nonexisting group because group creation task did not run (check mode). The task succeeds in check mode even though the group is missing. In regular run it would fail if group creation was somehow skipped.

So, if the group is missing for whatever reason, both CREATE NEW USER AND ADD IT TO THE NEW GROUP and MODIFY EXISTING USER BY ADDING IT TO THE NEW GROUP should fail but in check mode only the last one fails.

This leads me to suspect that user module does not test for group existence in check mode when it is about to create a new user. It only tests the group existence if the user already exists.