15

I'm looking for advice. I have the following code that creates a list dynamically that I can then later use in a template.

This is a copy of the test code I put together - for the actual role I just added the admins|regex_replace variable into the j2 template.

    ---

  - hosts: localhost
    gather_facts: false

    vars:
      # define empty admins var first so ansible doesn't complain
      admins:

      admin_accounts:
      - name: john
        uid: 1000
        group: sysadmin
        shell: /bin/bash
        comment: "Unix Administrator"
      - name: paul
        uid: 1001
        group: sysadmin
        shell: /bin/bash
        comment: "Unix Administrator"
      - name: george
        uid: 1002
        group: sysadmin
        shell: /bin/bash
        comment: "Unix Administrator"
      - name: ringo
        uid: 1003
        group: sysadmin
        shell: /bin/bash
        comment: "Unix Administrator"

    tasks:

      - name: build array of admin user names
        set_fact: admins="{{ admins}} {{ item.name }}"
        with_items: "{{ admin_accounts }}"

      # print out the fact piping through two jinja2 filters
      # careful with word wrapping
      - debug: msg={{ admins | regex_replace( '\s+',', ' ) | regex_replace`(',\s(.*)','\\1') }}`

This gives me the following:

    PLAY [localhost] ***************************************************************

TASK [build array of admin user names] *****************************************
ok: [localhost] => (item={u'comment': u'Unix Administrator', u'shell': u'/bin/bash', u'group': u'sysadmin', u'name': u'john', u'uid': 1000})
ok: [localhost] => (item={u'comment': u'Unix Administrator', u'shell': u'/bin/bash', u'group': u'sysadmin', u'name': u'paul', u'uid': 1001})
ok: [localhost] => (item={u'comment': u'Unix Administrator', u'shell': u'/bin/bash', u'group': u'sysadmin', u'name': u'george', u'uid': 1002})
ok: [localhost] => (item={u'comment': u'Unix Administrator', u'shell': u'/bin/bash', u'group': u'sysadmin', u'name': u'ringo', u'uid': 1003})

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "john, paul, george, ringo"
}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0

So...I get what I need, but am I going about it the right way?

Ansible version is 2.0.2.0 running on Centos 7.2.

Thanks in advance.


Edit: The resultant filter ended up looking like this:

  - name: build list of admin user names
    set_fact:
      admin_list: "{{ admin_accounts | selectattr('state', 'equalto', 'present') | map(attribute='name') | join(', ') }}"
  - debug: msg={{ admin_list }}

Having added another parameter to the yaml:

state: absent

Ringo was left out, as desired.

cachonfinga
  • 860
  • 2
  • 8
  • 14

2 Answers2

20

Filters will operate on lists, so the with_items is really wasteful, and the regex stuff is pretty obtuse for what you're doing. Do you really want a comma-separated string, or do you just want a list of the usernames extracted from the admin_accounts list?

If you just want the list, why not:

set_fact:
  admin_usernames: "{{ admin_accounts | map(attribute='name') | list }}"

... and if you really want the comma-separated list as a flat string, just add a join filter:

set_fact:
  admin_usernames: "{{ admin_accounts | map(attribute='name') | join(', ') }}"

If your ultimate target is a template, though, I'd suggest doing this inside the template, since this looks pretty formatting-related as opposed to logic-related (unless you're just simplifying for Stack Overflow purposes)...

nitzmahone
  • 13,720
  • 2
  • 36
  • 39
  • Exactly what I didn't know I was looking for, thank you so very much! The code example was created for the post, you are correct in assuming that this was for a template however I had no idea how to get the formatting I needed in the j2. Many thanks, greatly appreciated! – cachonfinga Jun 08 '16 at 10:21
  • Sorry to ask, but off that back of that, is there a filter available that would allow me to conditionally map? I'm referencing some json that is used for creating user accounts and using the "state" attribute to figure out account aliases for sudoers. Using with_items and when together achieved the desired effect. I cannot see in the ansible filters doc how I might do that without writing my own filter. I'm a little ways off that just now... – cachonfinga Jun 08 '16 at 12:24
  • Just found a decent page on filters [here](http://jinja.pocoo.org/docs/dev/templates/#builtin-filters) and discovered selectattr and rejectattr. Unfortunately it looks like my current version of ansible uses jinja2.7, not 2.8 therefore I get "no test named 'equalto'. – cachonfinga Jun 08 '16 at 13:01
  • You can update jinja outside Ansible- it should be perfectly happy with 2.8 – nitzmahone Jun 08 '16 at 18:20
  • Would up vote twice if I could for steering to filters for basic outputs like this, by far the best way to generate a simple csv – Tj Kellie Apr 09 '18 at 19:26
3

When there is a need to add both prefix and suffix (and making everything a list), look at:

  set_fact:
    extended_etcd_endpoints_list: "{{ groups['etcd'] | map('extract', hostvars, ['ansible_default_ipv4','address']) | map('regex_replace', '^(.*)$','https://\\1:2379') | list  }}"

What is does: takes the list of all machines in the group etcd, extracts the ipv4 address, adds a prefix of 'https://' and a suffix of ':2379'. At the end, everything is transformed to a list.

ReSearchIT Eng
  • 111
  • 1
  • 3