Search code examples
ansiblejinja2

How to properly expand multiple variables in ansible when: statement?


I have the following play working correctly as expected:

- name: Identify next available hostname    
  ansible.builtin.set_fact:      
    NEXT_HOSTNAME: "{{ HOSTNAME_CONVENTION }}{{ '{:02d}'.format(item) }}"    
  loop: "{{ range(1, 99) | reverse | list }}"
  when: "'host-name{:02d}'.format(item) not in EXISTING_HOSTNAMES

Where EXISTING_HOSTNAMES is a list of non-sequential hostnames already assigned and HOSTNAME_CONVENTION is set to 'host-name'.

My goal is to substitute out the "host-name" text in the when statement with the HOSTNAME_CONVENTION variable similar in manner to how the "NEXT_HOSTNAME" variable value is being built.

Substituting out the "when" line to

when: "'HOSTNAME_CONVENTION{:02d}'.format(item) not in EXISTING_HOSTNAMES

allows the play to run, but it does not correctly evaluate, as I expect it to skip host-name01, host-name02, and host-name04 since they are already assigned, whereas with the static "host-name" text on the when line, it does correctly skip host-name01, host-name02, and host-name04.

Substituting out the "when" line to

when: "HOSTNAME_CONVENTION'{:02d}'.format(item) not in EXISTING_HOSTNAMES

fails completely with "template error while templating string: expected token 'end of statement block', got 'string'"


Solution

  • Declare the host convention, start, stop, format, and index

      hc: host-name
      start: 1
      stop: '20'
      frmt: "%0{{ stop|length }}d"
      index: |
        {% for i in range(start, stop|int) %}
        {{ frmt|format(i) }} {% endfor %}
    

    give

      frmt: '%02d'
      index: |-
        01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19
    

    Create all hostnames

      hn: "{{ [hc] | product(index|split) | map('join') }}"
    

    gives

      hn:
      - host-name01
      - host-name02
      - host-name03
      - host-name04
      - host-name05
      - host-name06
      - host-name07
      - host-name08
      - host-name09
      - host-name10
      - host-name11
      - host-name12
      - host-name13
      - host-name14
      - host-name15
      - host-name16
      - host-name17
      - host-name18
      - host-name19
    

    Given the list of existing hostnames. For example,

      existing_hostnames: [host-name01, host-name02, host-name04]
    

    remove them from the list of all hostnames

      result: "{{ hn | difference(existing_hostnames) | sort }}"
    

    gives what you probably want

      result:
      - host-name03
      - host-name05
      - host-name06
      - host-name07
      - host-name08
      - host-name09
      - host-name10
      - host-name11
      - host-name12
      - host-name13
      - host-name14
      - host-name15
      - host-name16
      - host-name17
      - host-name18
      - host-name19
    

    Example of a complete playbook for testing

    - hosts: all
    
      vars:
    
        hc: host-name
        start: 1
        stop: '20'
        frmt: "%0{{ stop|length }}d"
        index: |
          {% for i in range(start, stop|int) %}
          {{ frmt|format(i) }} {% endfor %}
        hn: "{{ [hc] | product(index|split) | map('join') }}"
        existing_hostnames: [host-name01, host-name02, host-name04]
        result: "{{ hn | difference(existing_hostnames) | sort }}"
    
      tasks:
    
        - debug:
            var: hc
        - debug:
            var: frmt
        - debug:
            var: index
        - debug:
            var: hn
        - debug:
            var: result
    

    Q: Addition of the first filter in the result variable.

    A: It's simpler to concatenate the result if you want the first item only

      existing_indexes: "{{ existing_hostnames | map('replace', hc, '') }}"
      result: "{{ hc }}{{ index|split|difference(existing_indexes)|first }}"
    

    give

      existing_indexes:
      - '01'
      - '02'
      - '04'
    
      result: host-name03
    

    Example of a complete playbook for testing

    - hosts: all
    
      vars:
    
        hc: host-name
        start: 1
        stop: '20'
        frmt: "%0{{ stop|length }}d"
        index: |
          {% for i in range(start, stop|int) %}
          {{ frmt|format(i) }} {% endfor %}
    
        existing_hostnames: [host-name01, host-name02, host-name04]
        existing_indexes: "{{ existing_hostnames | map('replace', hc, '') }}"
        result: "{{ hc }}{{ index|split|difference(existing_indexes)|first }}"
    
      tasks:
    
        - debug:
            var: existing_indexes
        - debug:
            var: result