Ansible nested loop over hostvars - loops

I am trying to loop over all of the hosts that I've in the host_vars folder and get their corresponding interfaces, the interfaces var itself is a list.
problem: I can access the host_vars and get the desired data, except that the interface variable is a list and I want to loop over it.
What it looks like:
Loop through host_vars
Get the first host on the list
Loop over the interfaces
Repeat
To simplify things I am using debug in my example:
- name: TEST_101
debug:
var:
hostvars[item]['interfaces'][X]['name']
loop: "{{ groups['all'] }}"
X: is the corresponding interface index
The following are two of the host_vars files.
core_01
---
ansible_host: 192.168.1.202
site: XX-DC
role: CORE
model: CSR1000v
interfaces:
- name: vlan 1
description: "EDGE_RTR IF"
ipv4: 192.168.100.3/24
state: merged
enabled: true
- name: vlan 100
description: "IT IF"
ipv4: 172.31.1.1/24
state: merged
enabled: true
core_02
---
ansible_host: 192.168.12.210
interfaces:
- name: ethernet 0/0
description: "ISP_01 IF PRIMARY" #The discription on the interface
ipv4: 10.0.0.2/24
state: merged
enabled: true
- name: ethernet 0/1
description: "CORE_SW IF PRIMARY" #The discription on the interface
ipv4: 192.168.100.1/24
state: merged
enabled: true
The output when the script is run:
PLAY [Populate NetBox DataBase] ************************************************************************************************************************************************************
TASK [build_netbox_db : Create interface within Netbox with only required information] *****************************************************************************************************
ok: [localhost] => (item=edge_01) => {
"ansible_loop_var": "item",
"hostvars[item]['interfaces'][0]['name']": "ethernet 0/0",
"item": "edge_01"
}
ok: [localhost] => (item=edge_02) => {
"ansible_loop_var": "item",
"hostvars[item]['interfaces'][0]['name']": "ethernet 0/0",
"item": "edge_02"
}
ok: [localhost] => (item=csr1k_01) => {
"ansible_loop_var": "item",
"hostvars[item]['interfaces'][0]['name']": "ethernet 0/0",
"item": "csr1k_01"
}
ok: [localhost] => (item=core_01) => {
"ansible_loop_var": "item",
"hostvars[item]['interfaces'][0]['name']": "vlan 1",
"item": "core_01"
}
PLAY RECAP *********************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
X is replaced with an index number.
In a sense, I want to loop twice, once over the host, then over the interfaces of that particular host.

As soon as you delegate the task back to your local, then you don't need to loop on the groups['all'] anymore, and you can let Ansible do the normal process of targeting all the hosts defined in the hosts directive.
Then, you just have to loop on the interfaces variable of all hosts.
Given the playbook:
- hosts: core_01,core_02
## I am limiting myslef to two hosts here,
## but `all` would do just fine if you want to target all
## hosts in your inventory
gather_facts: no
tasks:
- debug:
msg: "{{ item.name }}"
loop: "{{ interfaces }}"
delegate_to: 127.0.0.1
loop_control:
label: "{{ item.name }}"
This will yield the recap:
PLAY [core_01,core_02] ********************************************************************************************
TASK [debug] ******************************************************************************************************
ok: [core_01] => (item=vlan 1) =>
msg: vlan 1
ok: [core_01] => (item=vlan 100) =>
msg: vlan 100
ok: [core_02] => (item=ethernet 0/0) =>
msg: ethernet 0/0
ok: [core_02] => (item=ethernet 0/1) =>
msg: ethernet 0/1
PLAY RECAP ********************************************************************************************************
core_01 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
core_02 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

Loop through subelements of subelements

I am using the following data structure in Ansible:
datacenters:
- name: Datacenter1
clusters:
- name: ClusterA
hosts:
- 192.168.0.1
- 192.168.0.2
- name: ClusterB
hosts:
- 192.168.1.1
- 192.168.1.2
- name: Datacenter2
clusters:
- name: ClusterC
hosts:
- 192.168.2.1
- 192.168.2.2
In a task, I want to iterate over each host while having access to the data of all the parent layers. If there is only one nesting level, it can easily be done with the subelements filter:
loop: '{{ datacenters | subelements(''clusters'') }}'
This will give me access to the data like this:
'Datacenter: {{ item.0.name }}, Cluster: {{ item.1.name }}'
I was hoping to be able to extend this concept like this:
loop: '{{ datacenters | subelements(''clusters'') | subelements(''hosts'') }}'
And being able to access the data like this:
'Datacenter: {{ item.0.name }}, Cluster: {{ item.1.name }}, Host: {{ item.2 }}'
But that does not work and I get the following error message instead:
Unexpected templating type error occurred on ({{ datacenters | subelements('clusters') | subelements('hosts') }}): the key hosts should point to a dictionary, got ...(the result of the first layer)
I found this question, which solves a similar problem, but relies on having distinct dict keys on all nesting levels, which I don't, because datacenters and clusters have the same name key.
So, how can I iterate over subelements of subelements in Ansible?
A bit far-fetched but the following playbook will achieve your goal:
---
- hosts: localhost
gather_facts: false
vars:
datacenters:
- name: Datacenter1
clusters:
- name: ClusterA
hosts:
- 192.168.0.1
- 192.168.0.2
- name: ClusterB
hosts:
- 192.168.1.1
- 192.168.1.2
- name: Datacenter2
clusters:
- name: ClusterC
hosts:
- 192.168.2.1
- 192.168.2.2
# Get the list of datacenters
_dcs: "{{ datacenters | map(attribute='name') }}"
# Get the corresponding list of clusters with subelements on hosts
_clusters: "{{ datacenters | map(attribute='clusters') | map('subelements', 'hosts') }}"
# Recreate a list with the sublisted hosts per clusters and create subelements on that final result
_overall: "{{ dict(_dcs | zip(_clusters)) | dict2items(key_name='datacenter', value_name='clusters') | subelements('clusters') }}"
tasks:
- name: Loop on the result
debug:
msg:
- "DC: {{ item.0.datacenter }}"
- "Cluster: {{ item.1.0.name }}"
- "Host: {{ item.1.1 }}"
loop: "{{ _overall }}"
loop_control:
label: "{{ item.0.datacenter }} - {{ item.1.0.name }}"
This gives:
PLAY [localhost] **************************************************************************************************************************************************
TASK [Loop on the result] *****************************************************************************************************************************************
ok: [localhost] => (item=Datacenter1 - ClusterA) => {
"msg": [
"DC: Datacenter1",
"Cluster: ClusterA",
"Host: 192.168.0.1"
]
}
ok: [localhost] => (item=Datacenter1 - ClusterA) => {
"msg": [
"DC: Datacenter1",
"Cluster: ClusterA",
"Host: 192.168.0.2"
]
}
ok: [localhost] => (item=Datacenter1 - ClusterB) => {
"msg": [
"DC: Datacenter1",
"Cluster: ClusterB",
"Host: 192.168.1.1"
]
}
ok: [localhost] => (item=Datacenter1 - ClusterB) => {
"msg": [
"DC: Datacenter1",
"Cluster: ClusterB",
"Host: 192.168.1.2"
]
}
ok: [localhost] => (item=Datacenter2 - ClusterC) => {
"msg": [
"DC: Datacenter2",
"Cluster: ClusterC",
"Host: 192.168.2.1"
]
}
ok: [localhost] => (item=Datacenter2 - ClusterC) => {
"msg": [
"DC: Datacenter2",
"Cluster: ClusterC",
"Host: 192.168.2.2"
]
}
PLAY RECAP ********************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Assigning IP to multiple hosts

I am trying to assign a range of IP to different hosts, but first I am checking if the IP is already attributed. If it is, it gives me an error, so I would like to avoid that too(actually I can assign the IP, but can't pass that error, I am using ignore_errors: yes for now but I would like a better way). Here is part of my script:
---
- hosts: all
gather_facts: yes
become: yes
vars_files:
- client2
tasks:
- set_fact:
intip: "{{ intip | default([]) + [item] }}"
loop: "{{ ansible_interfaces | map('extract',ansible_facts, 'ipv4') | select('defined') | map(attribute='address') | list }}"
ignore_errors: yes
- name: Check IP address
shell:
"ip a a '{{ start_ip|ipmath(my_idx) }}' dev spanbr"
loop_control:
index_var: my_idx
when: (item == inventory_hostname )
loop: "{{ ansible_play_hosts }}"
ignore_errors: yes
I am using this var file, but maybe there is a better way? (I am trying to use something like the first example, but can't get my head around to use it on every hosts)
First vars file (didn't try yet):
client:
- interface:
- local_ip: 10.10.10.10
- name: eth1
- interface:
- local_ip: 10.10.10.11
- name: eth1
Second file:
interface:
- config:
- name: eth0
- config:
- name: eth1
start_ip: 10.10.10.10
I can get only one interface getting the IP and ignoring the address, but as the when conditional is not a loop it only check one interface:
Output:
TASK [Check IP address] ************************************************************************************************************************
skipping: [host2] => (item=host1)
failed: [host1] (item=host1) => {"ansible_index_var": "my_idx", "ansible_loop_var": "item", "changed": true, "cmd": "ip a a '10.10.10.10' dev spanbr", "delta": "0:00:00.005429", "end": "2022-10-20 08:49:43.800954", "item": "host1", "msg": "non-zero return code", "my_idx": 0, "rc": 2, "start": "2022-10-20 08:49:43.795525", "stderr": "RTNETLINK answers: File exists", "stderr_lines": ["RTNETLINK answers: File exists"], "stdout": "", "stdout_lines": []}
skipping: [host1] => (item=host2)
...ignoring
failed: [host2] (item=host2) => {"ansible_index_var": "my_idx", "ansible_loop_var": "item", "changed": true, "cmd": "ip a a '10.10.10.11' dev spanbr", "delta": "0:00:00.002691", "end": "2022-10-20 07:49:43.815422", "item": "host2", "msg": "non-zero return code", "my_idx": 1, "rc": 2, "start": "2022-10-20 07:49:43.812731", "stderr": "RTNETLINK answers: File exists", "stderr_lines": ["RTNETLINK answers: File exists"], "stdout": "", "stdout_lines": []}
...ignoring
I would like to use a notify but I need to use the loop on the task itself so that may be an issue...
Any ideas please??
Here is the output of intip (set_fact) if that helps:
TASK [set_fact] ********************************************************************************************************************************
ok: [host1] => (item=192.168.1.100)
ok: [host1] => (item=127.0.0.1)
ok: [host1] => (item=10.10.10.10)
ok: [host1] => (item=169.254.0.1)
ok: [host2] => (item=127.0.0.1)
ok: [host2] => (item=10.10.10.11)
ok: [host2] => (item=192.168.1.101)
Alright, I found a way to do it, by using host_vars and group_vars, and making my script even easier to write :
---
- hosts: all
gather_facts: yes
become: yes
tasks:
- set_fact:
intip: "{{ hostvars[inventory_hostname].ansible_all_ipv4_addresses }}"
- name: Check IP address
shell:
"ip a a '{{ local_ip }}' dev spanbr"
when: local_ip not in intip
Just by creating 2 files in host_vars.
host1.yaml:
ansible_host: "some_ip"
local_ip: 10.10.10.10
and the same for host2.yaml with different values. All is working great and it's making the script much easier to read too.

How to loop over inventory_hostname in Ansible

I'm trying to add all the hosts of the dynamic inventory to the group: reachable.
Here is my playbook.
$ cat collect_try.yaml
---
- hosts: "{{ env }}:&{{ zone }}"
become: true
tasks:
- add_host:
name: "{{ inventory_hostname }}"
group: reachable
- name: dynamicgroup
hosts: reachable
gather_facts: false
tasks:
- debug: msg="{{ inventory_hostname }} is reachable"
Here is my output:
TASK [Gathering Facts]
ok: [vm1.nodekite.com]
ok: [vm2.nodekite.com]
ok: [vm3.nodekite.com]
ok: [vm4.nodekite.com]
TASK [add_host]
changed: [vm1.nodekite.com] => {
"add_host": {
"groups": [
"reachable"
],
"host_name": "vm1.nodekite.com",
"host_vars": {
"group": "reachable"
}
},
"changed": true
}
PLAY [dynamicgroup]
META: ran handlers
TASK [debug]
ok: [vm1.nodekite.com] => {
"msg": "vm1.nodekite.com is reachable"
PLAY RECAP:
vm1.nodekite.com : ok=3 changed=1 unreachable=0 <=====
vm2.nodekite.com : ok=1 changed=0 unreachable=0
vm3.nodekite.com : ok=1 changed=0 unreachable=0
vm4.nodekite.com : ok=1 changed=0 unreachable=0
How to use loops to add all the hosts to the "group": "reachable". could someone please assist.
From add_host documentation notes:
This module bypasses the play host loop and only runs once for all the hosts in the play, if you need it to iterate use a with-loop construct.
In your specific case (i.e. dynamic host pattern in your play), you should be able to achieve your requirements using the inventory_hostnames lookup, e.g (not fully tested):
- name: Collect reachable hosts
hosts: localhost
gather_facts: false
tasks:
- name: Push hosts to "reachable" group
vars:
pattern: "{{ env }}:&{{ zone }}"
add_host:
name: "{{ item }}"
group: reachable
loop: "{{ query('inventory_hostnames', pattern) }}"

Is there any way to get output of top 5 servers only among all hosts in ansible-playbook

I'm having an issue ansible condition and loops:
I would like to get the output of top 5 servers only but the following playbook giving the output of all hosts instead of 2 hosts.
---
- name:
hosts: all
tasks:
- name:
command: hostname -i
register: out
- set_fact: my_ip = "{{out.stdout_lines}}"
with_items: [1,2]
when: item <= 2
output:
TASK [set_fact] *********************************************************************************************************************************************
task path: /home/ec2-user/ansible/mm.yml:9
ok: [s1] => (item=1) => {"ansible_facts": {"_raw_params": "my_ip = \"[u'172.31.45.164']\""}, "ansible_loop_var": "item", "changed": false, "item": 1}
ok: [s1] => (item=2) => {"ansible_facts": {"_raw_params": "my_ip = \"[u'172.31.45.164']\""}, "ansible_loop_var": "item", "changed": false, "item": 2}
ok: [ansible] => (item=1) => {"ansible_facts": {"_raw_params": "my_ip = \"[u'172.31.39.107']\""}, "ansible_loop_var": "item", "changed": false, "item": 1}
ok: [ansible] => (item=2) => {"ansible_facts": {"_raw_params": "my_ip = \"[u'172.31.39.107']\""}, "ansible_loop_var": "item", "changed": false, "item": 2}
ok: [s2] => (item=1) => {"ansible_facts": {"_raw_params": "my_ip = \"[u'172.31.37.172']\""}, "ansible_loop_var": "item", "changed": false, "item": 1}
ok: [s2] => (item=2) => {"ansible_facts": {"_raw_params": "my_ip = \"[u'172.31.37.172']\""}, "ansible_loop_var": "item", "changed": false, "item": 2}
I think you may not fully understand how Ansible works. When your play starts with this:
- hosts: all
This means that Ansible will run the tasks in your play on each of the hosts in your inventory. So if you have, say, five hosts in your inventory, then you're running these tasks...
- name:
command: hostname -i
register: out
- set_fact: my_ip = "{{out.stdout_lines}}"
with_items: [1,2]
when: item <= 2
...five times (so that second task will actually execute 10 times...five hosts, and for each a host a loop with two items).
Furthermore, it's not clear what you're trying to accomplish with that second task. Given your loop (with_items: [1,2]), your conditional (when: item <= 2) will always be true, so it's not serving any purpose. And you're not use the value of item in the task.
If you want the hostnames of the first two hosts in your inventory, you could write:
- hosts: all[0:1]
gather_facts: false
tasks:
- command: echo {{ inventory_hostname }}
register: out
- debug:
msg: "{{ out.stdout }}"
This would run those two tasks on the first two hosts in your inventory. Given an inventory that look like this:
host1 ansible_host=localhost
host2 ansible_host=localhost
host3 ansible_host=localhost
host4 ansible_host=localhost
The above playboook would produce:
PLAY [all[0:1]] ******************************************************************************
TASK [command] *******************************************************************************
changed: [host1]
changed: [host2]
TASK [debug] *********************************************************************************
ok: [host1] => {
"msg": "host1"
}
ok: [host2] => {
"msg": "host2"
}
PLAY RECAP ***********************************************************************************
host1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How to trigger Ansible Play for every iteration of a loop

Below registered variable command_result gets records from the database which could be multi-line.
The below helps me loop over each record of the database in the command_result variable.
- debug:
msg: "This is a database line: {{ item }}"
with_items: "{{ command_result.stdout_lines }}"
What I, now, need is to trigger a fresh play like below for every loop iteration along with the respective {{ item }}.
- hosts: "{{ item.stdout.split('\t')[0] }}"
tasks:
- name: Check if reboot is required
shell: /home/ansible/scripts/check.sh "{{ item.stdout.split('\t')[1] }}"
register: output
- debug: var=output.stdout_lines
- add_host: name={{ item }}
groups=dest_nodes
ansible_user={{ USER }}
with_items: "{{ dest_ip.split(',') }}"
The item value will change on each loop and that will be fed to the play above.
Thus in summary: if the database returns three records in {{ command_result.stdout_lines }} the shell module play should be invoked thrice with the details of each record in {{ item }} respectively.
For example:
The database can return any number of rows and lets consider it returns three rows of type: <listofhosts>\t<somearguments>:
host5,host8\targ1
host6,host2\targ3
host9,host3,host4\targ4
What I need is that the loop with_items: {{ command_result.stdout_lines }} would run three plays and each play to build dynamic host group of the host list for that run and its respective argument.
So:
for the first run dynamic hosts group will be host5,host8 and the shell should get arg1
for second loop iteration dynamic hosts group will be host6,host2 and shell would get arg3
and so forth.
Hope this makes my requirement understood.
I'm on the latest version of Ansible.
You should be able to run this kind of task with the creation of a dynamic host group, via the module add_host.
Also, to fulfil the requirement that there will be multiple hosts per line, we are first recreating a clean list, with the help of the module set_fact.
Then, with the newly created list, we can use the loop with_subelements, in order to create the right tuples (check_arg, host).
Here is an example, where I faked you database output in the dynamic_hosts variable of the play:
---
- hosts: 127.0.0.1
gather_facts: false
connection: local
vars:
dynamic_hosts:
- "host5,host8\targ1"
- "host6,host2\targ3"
- "host9,host3,host4\targ4"
tasks:
- debug:
msg: "{{ dynamic_hosts }}"
- name: Make a clean list out of the hosts and arguments
set_fact:
hosts: "{{ hosts | default([]) + [ {'hosts': item.split('\t')[0].split(','), 'check_arg': item.split('\t')[1]} ] }}"
with_items: "{{ dynamic_hosts }}"
- debug:
msg: "{{ hosts }}"
- name: Adding hosts to a dynamic group based on the faked database output stored in hosts
add_host:
name: "{{ item.1 }}"
check_arg: "{{ item.0.check_arg }}"
ansible_host: 127.0.0.1
ansible_connection: local
groups: nodes_to_run_on
with_subelements:
- "{{ hosts }}"
- hosts
- hosts: nodes_to_run_on
gather_facts: false
tasks:
- debug:
msg: "Run the shell with the argument `{{ check_arg }}` here"
As you can see, I am creating a host group named nodes_to_run_on on the first part of the play, with the help of add_host. Later on, I am using this host group to run a new set of tasks on all the hosts in that group.
This would be the output of this playbook:
PLAY [127.0.0.1] **********************************************************************************************************************************
TASK [debug] **************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": [
"host5,host8\targ1",
"host6,host2\targ3",
"host9,host3,host4\targ4"
]
}
TASK [Make a clean list out of the hosts and arguments] ******************************************************************************************
ok: [127.0.0.1] => (item=host5,host8 arg1)
ok: [127.0.0.1] => (item=host6,host2 arg3)
ok: [127.0.0.1] => (item=host9,host3,host4 arg4)
TASK [debug] **************************************************************************************************************************************
ok: [127.0.0.1] => {
"msg": [
{
"check_arg": "arg1",
"hosts": [
"host5",
"host8"
]
},
{
"check_arg": "arg3",
"hosts": [
"host6",
"host2"
]
},
{
"check_arg": "arg4",
"hosts": [
"host9",
"host3",
"host4"
]
}
]
}
TASK [Adding hosts to a dynamic group based on the faked database output stored in hosts] *********************************************************
changed: [127.0.0.1] => (item=[{'check_arg': 'arg1'}, 'host5'])
changed: [127.0.0.1] => (item=[{'check_arg': 'arg1'}, 'host8'])
changed: [127.0.0.1] => (item=[{'check_arg': 'arg3'}, 'host6'])
changed: [127.0.0.1] => (item=[{'check_arg': 'arg3'}, 'host2'])
changed: [127.0.0.1] => (item=[{'check_arg': 'arg4'}, 'host9'])
changed: [127.0.0.1] => (item=[{'check_arg': 'arg4'}, 'host3'])
changed: [127.0.0.1] => (item=[{'check_arg': 'arg4'}, 'host4'])
PLAY [nodes_to_run_on] ****************************************************************************************************************************
TASK [debug] **************************************************************************************************************************************
ok: [host5] => {
"msg": "Run the shell with the argument `arg1` here"
}
ok: [host8] => {
"msg": "Run the shell with the argument `arg1` here"
}
ok: [host6] => {
"msg": "Run the shell with the argument `arg3` here"
}
ok: [host2] => {
"msg": "Run the shell with the argument `arg3` here"
}
ok: [host9] => {
"msg": "Run the shell with the argument `arg4` here"
}
ok: [host3] => {
"msg": "Run the shell with the argument `arg4` here"
}
ok: [host4] => {
"msg": "Run the shell with the argument `arg4` here"
}
PLAY RECAP ****************************************************************************************************************************************
127.0.0.1 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host4 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host5 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host6 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host8 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host9 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Please note: this is not a really DRY list of host we are creating here, but it keeps the solution naive and is more achieving a KISS principle.
The inventory we end up generating here would look like this:
## kiss.yml ## lots of repetition
nodes_to_run_on:
hosts:
host5:
check_arg: arg1
ansible_host: 127.0.0.1
ansible_connection: local
host8:
check_arg: arg1
ansible_host: 127.0.0.1
ansible_connection: local
host6:
check_arg: arg3
ansible_host: 127.0.0.1
ansible_connection: local
host2:
check_arg: arg3
ansible_host: 127.0.0.1
ansible_connection: local
host9:
check_arg: arg4
ansible_host: 127.0.0.1
ansible_connection: local
host3:
check_arg: arg4
ansible_host: 127.0.0.1
ansible_connection: local
host4:
check_arg: arg4
ansible_host: 127.0.0.1
ansible_connection: local
When it could definitely be dried out to
## dry.yaml ## no repetition with the help of group variables
nodes_to_run_on:
vars:
ansible_host: 127.0.0.1
ansible_connection: local
children:
with_arg1:
hosts:
host5:
host8:
vars:
check_arg: arg1
with_arg3:
hosts:
host6:
host2:
vars:
check_arg: arg3
with_arg4:
hosts:
host9:
host3:
host4:
vars:
check_arg: arg4
Consider those two run in this play:
---
- hosts: nodes_to_run_on
gather_facts: false
tasks:
- debug:
msg: "Run the shell with the argument `{{ check_arg }}` here"
With kiss.yml
$ ansible-playbook example.yml --inventory=kiss.yml
PLAY [nodes_to_run_on] ****************************************************************************************************************************
TASK [debug] **************************************************************************************************************************************
ok: [host5] => {
"msg": "Run the shell with the argument `arg1` here"
}
ok: [host8] => {
"msg": "Run the shell with the argument `arg1` here"
}
ok: [host6] => {
"msg": "Run the shell with the argument `arg3` here"
}
ok: [host2] => {
"msg": "Run the shell with the argument `arg3` here"
}
ok: [host9] => {
"msg": "Run the shell with the argument `arg4` here"
}
ok: [host3] => {
"msg": "Run the shell with the argument `arg4` here"
}
ok: [host4] => {
"msg": "Run the shell with the argument `arg4` here"
}
PLAY RECAP ****************************************************************************************************************************************
host2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host4 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host5 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host6 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host8 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host9 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
With dry.yml
$ ansible-playbook example.yml --inventory=dry.yml
PLAY [nodes_to_run_on] ****************************************************************************************************************************
TASK [debug] **************************************************************************************************************************************
ok: [host5] => {
"msg": "Run the shell with the argument `arg1` here"
}
ok: [host8] => {
"msg": "Run the shell with the argument `arg1` here"
}
ok: [host6] => {
"msg": "Run the shell with the argument `arg3` here"
}
ok: [host2] => {
"msg": "Run the shell with the argument `arg3` here"
}
ok: [host9] => {
"msg": "Run the shell with the argument `arg4` here"
}
ok: [host3] => {
"msg": "Run the shell with the argument `arg4` here"
}
ok: [host4] => {
"msg": "Run the shell with the argument `arg4` here"
}
PLAY RECAP ****************************************************************************************************************************************
host2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host4 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host5 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host6 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host8 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host9 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
So, you can see that we achieve the same with an inventory, dry.yml, of 24 lines, when the kiss.yml inventory was 30 lines long.
But ultimately the effort to create an inventor that would be DRY would not really pay, if the source is, either way, coming from a database that would generate a host list.

Resources