ansibleansible-2.x

How to concatenate variable and the string inside filter?


I'm trying to write a role that finds difference between records in DN servers and records in Git. Some of them were added manually via Web interface. For that purpose in my Ansible playbook I dynamically generate variables like this:

- hosts: dn_servers
  vars:
    domains:
      int_domain: int.domain.com
      ext_domain: domain.com

  tasks:
    - name: Generates list of hostnames for every domain present in var domain
      ansible.builtin.set_fact:
        "{{ item.key }}_git_records": "{{ list of A records generated with the help of item.value }}"
      loop: "{{ domains | dict2items }}"

Then I try reuse these vars inside the Ansible filter rejectattr:

    - name: Find difference between remote hosts and Git file
      ansible.builtin.set_fact:
        "{{ item.key }}_inconsistent_hostnames": "{{ remote_records | selectattr('domain', '==', item.value) | rejectattr('hostname', 'in', (item.key ~ '_git_records')) }}"
      loop: "{{ domains | dict2items }}"

But filter rejectattr fail filtering. I'm sure that problem is here (item.key ~ '_gitlab_hostnames'), because it works as expected after I hardcoded third argument.

Maybe the issue is that after concatenation of variable and string rejectattr perceives third argument as a string instead of variable? If yes, how it could be fixed?

UPD remote_records list example:

    - domain: int.domain.com
      enabled: true
      hostname: hostname1
      record_type: A
      value: 172.16.1.11
    - domain: domain.com
      enabled: true
      hostname: hostname2
      record_type: A
      value: 172.16.1.22

Solution

  • You're looking for indirect addressing. The expression item.key ~ '_git_records' creates the name of the variable, but doesn't deliver the value

    rejectattr('hostname', 'in', (item.key ~ '_git_records'))
    

    The third argument must be a list. For example, given the data

        domains:
          int_domain: int.domain.com
          ext_domain: domain.com
    
        remote_records:
          - {domain: int.domain.com, hostname: h1}
          - {domain: int.domain.com, hostname: h9}
    
        int_domain_git_records: [h1, h2, h3]
        ext_domain_git_records: [h6, h7, h8]
    

    You have to use the vars lookup to address the lists int_domain_git_records, ext_domain_git_records, ... in the loop below

        - debug:
            msg: "{{ remote_records |
                     selectattr('domain', '==', item.value) |
                     rejectattr('hostname', 'in', lookup('vars', item.key ~ '_git_records')) }}"
          loop: "{{ domains | dict2items }}"
    

    gives (abridged)

      msg:
      - domain: int.domain.com
        hostname: h9
    
      msg: []
    

    Example of a complete playbook for testing

    - hosts: localhost
    
      vars:
    
        domains:
          int_domain: int.domain.com
          ext_domain: domain.com
    
        remote_records:
          - {domain: int.domain.com, hostname: h1}
          - {domain: int.domain.com, hostname: h9}
    
        int_domain_git_records: [h1, h2, h3]
        ext_domain_git_records: [h6, h7, h8]
    
      tasks:
    
        - debug:
            msg: "{{ lookup('vars', 'int_domain' ~ '_git_records') }}"
    
        - debug:
            msg: "{{ remote_records |
                     selectattr('domain', '==', item.value) |
                     rejectattr('hostname', 'in', lookup('vars', item.key ~ '_git_records')) }}"
          loop: "{{ domains | dict2items }}"
    

    Optionally, you can create a dictionary to speed up the searching

      git_records: "{{ dict(domains.values() |
                            zip(q('vars', *q('varnames', domains.keys() | join('|'))))) }}"
    

    gives

      git_records:
        domain.com: [h6, h7, h8]
        int.domain.com: [h1, h2, h3]
    

    Then, use this dictionary instead of the lookup. The iteration below gives the same result

        - debug:
            msg: "{{ remote_records |
                     selectattr('domain', '==', item.value ) |
                     rejectattr('hostname', 'in', git_records[item.value]) }}"
          loop: "{{ domains | dict2items }}"
    

    Ideally, replace the *_git_records with the dictionary git_records and forget all this lookup stuff.