The Horrors of Ansible Complex Variables: User Dictionaries With a Group List Added

This is part of the series "The Horrors of Ansible Complex Variables." The results below were achieved with Ansible version 2.8.0.

I was happy to have a "dictionary" format that worked in both Ansible and Jinja2 (in quotes because it's a bit more complex than that, see the previous entry in this series), but I wanted not only a list of users but also each user to contain a list of the groups they were in. Here's a demonstration:

---
# filename: groups.yaml

- name: lists
  hosts: localhost
  connection: local
  vars:
    # you don't have to align this stuff at all: I like it this way
    # because it's more readable (but it's a pain to maintain when you
    # add accounts)
    #
    # I also apologize that I didn't mention [] as the Nth way of creating
    # lists in Ansible in the blog entry on lists ...
    users:
        - { name: giles,      groups: ['readonly', 'admin'], fullname: "Giles Orr"      }
        - { name: dilbert,    groups: ['main', 'admin'],     fullname: "Dilbert"        }
        - { name: jenkins,    groups: ['readonly'],          fullname: "Butler Jenkins" }
        - { name: dogbert,    groups: ['main', 'trolls'],    fullname: "Dogbert"        }
        - { name: deploy,     groups: ['readonly'],          fullname: "Deploy Account" }

  tasks:
    - name: Print users (full object)
      debug:
        msg: "{{ item }}"
      loop: "{{ users }}"

    - name: Print users
      debug:
        msg: "User {{ item.name }} has {{ item.groups }} perms"
      loop: "{{ users }}"

    - name: template with the dictionary
      template:
        src=template.j2
        dest=template.txt

Once again, a (more complex) template file to go with it:

# {{ ansible_managed }}
{# vim: syntax=jinja2 sr et ts=4 sw=4 sts=4: #}

main     = {% for user in users %}{% if "main"     in user.groups %}{{ user.name }}, {% endif %}{% endfor %}

admin    = {% for user in users %}{% if "admin"    in user.groups %}{{ user.name }}, {% endif %}{% endfor %}

readonly = {% for user in users %}{% if "readonly" in user.groups %}{{ user.name }}, {% endif %}{% endfor %}

trolls   = {% for user in users %}{% if "trolls"   in user.groups %}{{ user.name }}, {% endif %}{% endfor %}


And now for your /etc/passwd file (needs more details):
{% for user in users %}
{{ user.name }}::::{{ user.fullname}}::
{% endfor %}

   {# make a list of all groups (including users, who are usually their own
      group on Linux).

      The dashes inside the percent signs in the statements are to control
      the whitespace: we would otherwise have a bunch of empty lines.
   #}
   {%- set groups = [] -%}
   {%- for user in users -%}
       {{ groups.append( user.name ) }}
       {%- for group in user.groups -%}
           {%- if group not in groups -%}
               {{ groups.append( group ) }}
           {%- endif -%}
       {%- endfor -%}
   {%- endfor -%}


   All groups: {{ groups|join(',') }}

   And for /etc/group (again, more details needed):
   {% for group in groups %}
   {{ group }}:x::{% for user in users %}{% if group in user.groups %}{{ user.name }},{% endif %}{% endfor %}

   {% endfor %}

This includes a couple different ways of handling these usernames depending on the output format you want.

When run, it looks like this:

$ ansible-playbook groups.yml
 [WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'


PLAY [lists] *******************************************************************************

TASK [Gathering Facts] *********************************************************************
ok: [localhost]

TASK [Print users (full object)] ***********************************************************
ok: [localhost] => (item={'name': 'giles', 'groups': ['readonly', 'admin'], 'fullname': 'Giles Orr'}) => {
    "msg": {
        "fullname": "Giles Orr",
        "groups": [
            "readonly",
            "admin"
        ],
        "name": "giles"
    }
}
ok: [localhost] => (item={'name': 'dilbert', 'groups': ['main', 'admin'], 'fullname': 'Dilbert'}) => {
    "msg": {
        "fullname": "Dilbert",
        "groups": [
            "main",
            "admin"
        ],
        "name": "dilbert"
    }
}
ok: [localhost] => (item={'name': 'jenkins', 'groups': ['readonly'], 'fullname': 'Butler Jenkins'}) => {
    "msg": {
        "fullname": "Butler Jenkins",
        "groups": [
            "readonly"
        ],
        "name": "jenkins"
    }
}
ok: [localhost] => (item={'name': 'dogbert', 'groups': ['main', 'trolls'], 'fullname': 'Dogbert'}) => {
    "msg": {
        "fullname": "Dogbert",
        "groups": [
            "main",
            "trolls"
        ],
        "name": "dogbert"
    }
}
ok: [localhost] => (item={'name': 'deploy', 'groups': ['readonly'], 'fullname': 'Deploy Account'}) => {
    "msg": {
        "fullname": "Deploy Account",
        "groups": [
            "readonly"
        ],
        "name": "deploy"
    }
}

TASK [Print users] *************************************************************************
ok: [localhost] => (item={'name': 'giles', 'groups': ['readonly', 'admin'], 'fullname': 'Giles Orr'}) => {
    "msg": "User giles has ['readonly', 'admin'] perms"
}
ok: [localhost] => (item={'name': 'dilbert', 'groups': ['main', 'admin'], 'fullname': 'Dilbert'}) => {
    "msg": "User dilbert has ['main', 'admin'] perms"
}
ok: [localhost] => (item={'name': 'jenkins', 'groups': ['readonly'], 'fullname': 'Butler Jenkins'}) => {
    "msg": "User jenkins has ['readonly'] perms"
}
ok: [localhost] => (item={'name': 'dogbert', 'groups': ['main', 'trolls'], 'fullname': 'Dogbert'}) => {
    "msg": "User dogbert has ['main', 'trolls'] perms"
}
ok: [localhost] => (item={'name': 'deploy', 'groups': ['readonly'], 'fullname': 'Deploy Account'}) => {
    "msg": "User deploy has ['readonly'] perms"
}

TASK [template with the dictionary] ********************************************************
changed: [localhost]

PLAY RECAP *********************************************************************************
localhost                  : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

And the output of the template looks like this:

# Ansible managed

main     = dilbert, dogbert,
admin    = giles, dilbert,
readonly = giles, jenkins, deploy,
trolls   = dogbert,

And now for your /etc/passwd file (needs more details):
giles::::Giles Orr::
dilbert::::Dilbert::
jenkins::::Butler Jenkins::
dogbert::::Dogbert::
deploy::::Deploy Account::

All groups: giles,readonly,admin,dilbert,main,jenkins,dogbert,trolls,deploy

And for /etc/group (again, more details needed):
giles:x::
readonly:x::giles,jenkins,deploy,
admin:x::giles,dilbert,
dilbert:x::
main:x::dilbert,dogbert,
jenkins:x::
dogbert:x::
trolls:x::dogbert,
deploy:x::