I have written this a simplified example but it should be enough as a minimal reproducible code. I want to be able to generate a list of hosts from my inventory file based on 2 rules.
cdsre
foo
defined with a value of baz
OR the host doesn't define an attribute foo
I have been at this for a few hours and can achieve this with a long winded jinja2 string loop that uses an if
expression with a side effect which i think is pretty ugly. However I cant help think this should be achievable using just jinja filters.
sample inventory
all:
children:
cdsre:
children:
ovh_vm:
hosts:
ovh-vm[1:3]:
ovh-vm[6:7]:
foo: baz
oracle_vm:
hosts:
oracle-vm[1:3]:
foo: bar
oracle-vm[4:5]:
foo: baz
Playbook
---
- hosts: localhost
gather_facts: false
tasks:
- set_fact:
some_servers: |
{% set servers = [] %}
{% for host in groups['cdsre'] %}
{% set foo = hostvars[host]['foo'] | default('baz', true) %}
{% if foo == 'baz' %}
{% if servers.append(hostvars[host]['inventory_hostname']) %}{% endif %}
{% endif %}
{% endfor %}
{{ servers }}
foo_matched_servers: "{{ groups['cdsre'] | map('extract', hostvars) | selectattr('foo', 'defined') | selectattr('foo', '==', 'baz') | map(attribute='inventory_hostname') | list}}"
- debug:
var: some_servers
- debug:
var: foo_matched_servers
OUTPUT
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [set_fact] ************************************************************************************************************************************************************************************************************************
Tuesday 10 January 2023 23:57:00 +0000 (0:00:00.073) 0:00:00.073 *******
ok: [localhost]
TASK [debug] ***************************************************************************************************************************************************************************************************************************
Tuesday 10 January 2023 23:57:01 +0000 (0:00:00.885) 0:00:00.958 *******
ok: [localhost] => {
"some_servers": [
"ovh-vm1",
"ovh-vm2",
"ovh-vm3",
"ovh-vm6",
"ovh-vm7",
"oracle-vm4",
"oracle-vm5"
]
}
TASK [debug] ***************************************************************************************************************************************************************************************************************************
Tuesday 10 January 2023 23:57:01 +0000 (0:00:00.061) 0:00:01.019 *******
ok: [localhost] => {
"foo_matched_servers": [
"ovh-vm6",
"ovh-vm7",
"oracle-vm4",
"oracle-vm5"
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Tuesday 10 January 2023 23:57:01 +0000 (0:00:00.062) 0:00:01.082 *******
===============================================================================
set_fact ---------------------------------------------------------------- 0.89s
debug ------------------------------------------------------------------- 0.12s
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
total ------------------------------------------------------------------- 1.01s
So i can produce the list of hosts matching condition 1 but What I cant seem to work out is how I can capture in a single fact the hosts that match condition 1 and condition 2. Is this even possible? or do i need to write an additional fact to capture the hosts matching condition 2 then join both facts (this also feels a bit long winded)
There are more options:
Declare the variables below
my_hosts_str: |
[
{% for host in groups.cdsre %}
{% if hostvars[host]['foo']|default('baz') == 'baz' %}
{{ host }},
{% endif %}
{% endfor %}
]
my_hosts: "{{ my_hosts_str|from_yaml }}"
gives the valid YAML list in the string my_hosts_str
my_hosts_str: |-
[
ovh-vm1,
ovh-vm2,
ovh-vm3,
ovh-vm6,
ovh-vm7,
oracle-vm4,
oracle-vm5,
]
gives the expected result. Converts the string to the list.
my_hosts:
- ovh-vm1
- ovh-vm2
- ovh-vm3
- ovh-vm6
- ovh-vm7
- oracle-vm4
- oracle-vm5
Example of a playbook for testing
- hosts: all
vars:
my_hosts_str: |
[
{% for host in groups.cdsre %}
{% if hostvars[host]['foo']|default('baz') == 'baz' %}
{{ host }},
{% endif %}
{% endfor %}
]
my_hosts: "{{ my_hosts_str|from_yaml }}"
tasks:
- block:
- debug:
var: my_hosts_str
- debug:
var: my_hosts
run_once: true
Declare the variables below, e.g. in group_vars/all
shell> cat group_vars/all
csdr_foo_und: "{{ groups.cdsre|map('extract', hostvars)|
selectattr('foo', 'undefined')|
map(attribute='inventory_hostname') }}"
csdr_foo_baz: "{{ groups.cdsre|map('extract', hostvars)|
selectattr('foo', 'defined')|
selectattr('foo', '==', 'baz')|
map(attribute='inventory_hostname') }}"
my_hosts: "{{ csdr_foo_baz + csdr_foo_und }}"
gives the list of hosts in the group csdr with undefined foo
csdr_foo_und:
- ovh-vm1
- ovh-vm2
- ovh-vm3
gives the list of hosts in the group csdr with foo equal to baz
csdr_foo_baz:
- ovh-vm6
- ovh-vm7
- oracle-vm4
- oracle-vm5
gives the expected result. Concatenates the lists csdr_foo_und and csdr_foo_baz
my_hosts:
- oracle-vm4
- oracle-vm5
- ovh-vm1
- ovh-vm2
- ovh-vm3
- ovh-vm6
- ovh-vm7
Example of a playbook for testing
- hosts: all
tasks:
- block:
- debug:
var: csdr_foo_und
- debug:
var: csdr_foo_baz
- debug:
var: my_hosts|sort
run_once: true
Use the inventory plugin constructed. See
shell> ansible-doc -t inventory ansible.builtin.constructed
For example, create the project below for testing
shell> tree .
.
├── ansible.cfg
├── inventory
│ ├── 01-hosts.yml
│ └── 02-constructed.yml
└── pb.yml
1 directory, 4 files
shell> cat ansible.cfg
[defaults]
gathering = explicit
inventory = $PWD/inventory
retry_files_enabled = false
stdout_callback = yaml
shell> cat inventory/01-hosts.yml
all:
children:
cdsre:
children:
ovh_vm:
hosts:
ovh-vm[1:3]:
ovh-vm[6:7]:
foo: baz
oracle_vm:
hosts:
oracle-vm[1:3]:
foo: bar
oracle-vm[4:5]:
foo: baz
Create a new group my_group
shell> cat inventory/02-constructed.yml
plugin: ansible.builtin.constructed
use_vars_plugins: true
use_extra_vars: true
groups:
# host belongs to group 'cdsre' and
# foo is either undefined or 'baz'
my_group: group_names is contains 'cdsre' and
foo|default('baz') == 'baz'
Either reference the group groups.my_group or use it in hosts
shell> cat pb.yml
- hosts: all
tasks:
- debug:
var: groups.my_group
run_once: true
- hosts: my_group
tasks:
- debug:
var: ansible_play_hosts_all
run_once: true
gives
shell> ansible-playbook pb.yml
PLAY [all] ***********************************************************************************
TASK [debug] *********************************************************************************
ok: [ovh-vm1] =>
groups.my_group:
- ovh-vm1
- ovh-vm2
- ovh-vm3
- ovh-vm6
- ovh-vm7
- oracle-vm4
- oracle-vm5
PLAY [my_group] ******************************************************************************
TASK [debug] *********************************************************************************
ok: [ovh-vm1] =>
ansible_play_hosts_all:
- ovh-vm1
- ovh-vm2
- ovh-vm3
- ovh-vm6
- ovh-vm7
- oracle-vm4
- oracle-vm5
PLAY RECAP ***********************************************************************************
ovh-vm1: ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0