ansiblejinja2

Get a list of users with UID > 14000 - string to int conversion


In my playbook I want to get a list of all local users with UI above 14000

I used ansible.builtin.slurp on /etc/passwd or ansible.builtin.getent but the real issue is the UID value that I get is string, not int so I can't filter properly and all results I get are empty, or complain because is not an integer.

- name: Get all users from /etc/passwd
  ansible.builtin.getent:
    database: passwd
  register: users_info

- name: Filter users with UID greater than 14000
  set_fact:
    filtered_users: >-
      {{
        users_info.ansible_facts.getent_passwd |
        dict2items |
        selectattr('value.1', 'int') |
        selectattr('value.1', >, 14000) |
        map(attribute='key') |
        list
      }}

I thought selectattr('value.1', 'int') would convert to int, but the result is always empty. (I do have some UID above 14000 in the dict)

Here the user dict that getent provides, all UID are quoted so they are string

root:
- x
- '0'
- '0'
- root
- /root
- /bin/bash
shutdown:
- x
- '6'
- '0'
- shutdown
- /sbin
- /sbin/shutdown
sshd:
- x
- '74'
- '74'
- Privilege-separated SSH
- /usr/share/empty.sshd
- /sbin/nologin

I run out of idea to try, except run some shell cmd which I would avoid.


Solution

  • I can see several problems with your filter chain:

    filtered_users: >-
      {{
        users_info.ansible_facts.getent_passwd |
        dict2items |
        selectattr('value.1', 'int') |
        selectattr('value.1', >, 14000) |
        map(attribute='key') |
        list
      }}
    

    First, there is no test named int, although there is a filter named integer. Unfortunately, this is a test, not a conversion filter, so since in all cases the value of value.1 is a string, this selectattr() filter will reject all of your entries, leading to an empty list. Everything after this point is irrelevant because there is nothing left to filter.

    For example:

    - hosts: localhost
      gather_facts: false
      vars:
        example:
          - val: '1'
          - val: '2'
          - val: '3'
      tasks:
        - debug:
            msg: "{{ example | selectattr('val', 'integer') }}"
    

    Results in:

    ok: [localhost] => {
        "msg": []
    }
    

    I think you'll find a solution using the json_query filter is simpler. For what you want, we could do this:

    - name: Filter users with UID > 1000
      set_fact:
        filtered_users: >-
            {{
              users_info.ansible_facts.getent_passwd |
              dict2items |
              json_query('[?to_number(value[1]) > `1000`].key')
            }}
    

    Here, we're using the to_number function to convert the string values into integers for comparison.

    On my system, this produces:

    ok: [localhost] => {
        "filtered_users": [
            "nobody"
        ]
    }