Correct way to create dynamic lists in Ansible

42,995

Solution 1

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)...

Solution 2

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.

Share:
42,995
cachonfinga
Author by

cachonfinga

Updated on September 15, 2020

Comments

  • cachonfinga
    cachonfinga over 3 years

    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
    cachonfinga almost 8 years
    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
    cachonfinga almost 8 years
    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
    cachonfinga almost 8 years
    Just found a decent page on filters here 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'.
  • nitzmahone
    nitzmahone almost 8 years
    You can update jinja outside Ansible- it should be perfectly happy with 2.8
  • Tj Kellie
    Tj Kellie about 6 years
    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