Bram
Bram

Reputation: 825

Ansible: How do I test/filter items in a list using a regex?

In an ansible playbook I want to loop a task over a set of usernames from one list only if the username or username followed by a suffix '.A' occurs in another list.

Sample playbook that fails:

---
- hosts: localhost
  vars:
    - users1:
        - alice
        - alice.A
        - bob
        - santa
    - users2:
        - alice
  tasks:
    - debug:
        msg: "Do something for {{ item }} realname {{ item | regex_replace('^([a-z]+)\\.[aA]$', '\\1') }}"
      loop: "{{ users1 }}"
      when:
        - "item | regex_replace('^([a-z]+)\\.[aA]$', '\\1') in users2"

This version skips alice.A while the regex_replace filter in the task itself returns alice as expected.

Actual output:

TASK [debug] ***************
ok: [localhost] => (item=alice) => {}

MSG:

Do something for alice realname alice
skipping: [localhost] => (item=alice.A)
skipping: [localhost] => (item=bob)
skipping: [localhost] => (item=santa)

Desired output:

TASK [debug] ***************
ok: [localhost] => (item=alice) => {}

MSG:

Do something for alice realname alice
Do something for alice.A realname alice
skipping: [localhost] => (item=bob)
skipping: [localhost] => (item=santa)

Is it possible to apply a filter to the item to be tested? If not is there a way to use something like map() to duplicate the entries in the users2 list appending the '.A' suffix to each item?

When conditions I have tried that all fail:

    when: ("item in users2 | flatten(1)") or
      ("item|regex_search('\.A$')")

    when: 
      - "item | regex_replace('^([a-z]+\\.[a-z]+).[aA]$', '\\1') in users | flatten(1)"

    when: 
      - "item in users | flatten(1) | map('regex_replace','^([a-z]+\\.[a-z]+)$', '\\1.A')"

    when: 
      - "item | map('regex_replace','^([a-z]+\\.[a-z]+).[aA]$', '\\1') in users | flatten(1)"

    when: 
      - "item|map('upper') in users | flatten(1)"

The last one just to check whether the item tested was modified by the filter or not. (It is not AFAICT.)

Upvotes: 1

Views: 1060

Answers (1)

Vladimir Botka
Vladimir Botka

Reputation: 67984

Regex is not needed. Use splitext to get the realname and test its presence in the list users2

    - debug:
        msg: "Do something for {{ item }} realname {{ realname }}."
      loop: "{{ users1 }}"
      when: realname in users2
      vars:
        realname: "{{ item|splitext|first }}"

gives the expected results

TASK [debug] ********************************************************
ok: [localhost] => (item=alice) => 
  msg: Do something for alice realname alice.
ok: [localhost] => (item=alice.A) => 
  msg: Do something for alice.A realname alice.
skipping: [localhost] => (item=bob) 
skipping: [localhost] => (item=santa)

You say you

want to loop a task over a set of usernames from one list only if the username or username is followed by a suffix '.A'

The task above will accept any suffix not only '.A'. Test also the suffix to fix it. The task below gives the same result

    - debug:
        msg: "Do something for {{ item }} realname {{ realname }}."
      loop: "{{ users1 }}"
      when:
        - realname in users2
        - suffix == '.A'
      vars:
        arr: "{{ item|splitext|list }}"
        realname: "{{ arr.0 }}"
        suffix: "{{ arr.1|default('.A', true) }}"

See Providing default values.


If you want to use regex change the variables below. This will give the same results.

    - debug:
        msg: "Do something for {{ item }} realname {{ realname }}."
      loop: "{{ users1 }}"
      when: realname in users2
      vars:
        realname: "{{ item|regex_replace(regex, replace) }}"
        regex: '^(.*?)(\.A)*$'
        replace: '\1'

Update

Optionally, create the structure first and simplify the code in the tasks

    users3_str: |
      {% for u in users1 %}
      {% set arr = u|splitext|list %}
      - name: {{ u }}
        realname: {{ arr.0 }}
        suffix: {{ arr.1|default('.A', true) }}
      {% endfor %}
    users3: "{{ users3_str|from_yaml }}"

gives

  users3:
    - {name: alice, realname: alice, suffix: .A}
    - {name: alice.A, realname: alice, suffix: .A}
    - {name: bob, realname: bob, suffix: .A}
    - {name: santa, realname: santa, suffix: .A}

Then, the tasks below give the same results

    - debug:
        msg: "Do something for {{ item.name }} realname {{ item.realname }}."
      loop: "{{ users3|selectattr('realname', 'in', users2) }}"


    - debug:
        msg: "Do something for {{ item.name }} realname {{ item.realname }}."
      loop: "{{ users3|selectattr('realname', 'in', users2)|
                       selectattr('suffix', '==', '.A') }}"

Example of a complete playbook for testing

- hosts: localhost

  vars:

    users1:
      - alice
      - alice.A
      - bob
      - santa
    users2:
      - alice

    users3_str: |
      {% for u in users1 %}
      {% set arr = u|splitext|list %}
      - name: {{ u }}
        realname: {{ arr.0 }}
        suffix: {{ arr.1|default('.A', true) }}
      {% endfor %}
    users3: "{{ users3_str|from_yaml }}"
      
  tasks:

    - debug:
        var: users3|to_yaml

    - debug:
        msg: "Do something for {{ item.name }} realname {{ item.realname }}."
      loop: "{{ users3|selectattr('realname', 'in', users2) }}"

    - debug:
        msg: "Do something for {{ item.name }} realname {{ item.realname }}."
      loop: "{{ users3|selectattr('realname', 'in', users2)|
                       selectattr('suffix', '==', '.A') }}"

Upvotes: 2

Related Questions