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'"
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