How to loop over inventory_hostname in Ansible - loops

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) }}"

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

Iterate over nested loops

I am trying to iterate over level3 variable in Ansible. I've prepared a playbook like this:
---
- hosts: localhost
gather_facts: no
connection: local
vars:
level1:
- abc
- def
level2:
- { name: "app1", level3: ["aaaa","bbbb"]}
- { name: "app2", level3: ["eeeee"]}
tasks:
- name: test
debug: msg="{{ item.0 }} {{ item.1.name }} {{ item.2 }}"
with_nested:
- "{{ level1 }}"
- "{{ level2 }}"
- "{{ level2.level3 }}"
But I get error:
PLAY [localhost] ***************************************************************************************************************************************************
TASK [test] ********************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "'list object' has no attribute 'level3'"}
PLAY RECAP *********************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
What is bad with my config? I want to have on output each element.
level2.level3 does not exist. level2 is a list so e.g. level2.0.level3 exists.
I'm not entirely sure from your question but I think what you are looking for is:
- debug:
msg: "{{ item.0 }} {{ item.1.0.name }} {{ item.1.1 }}"
loop: "{{ level1 | product(level2 | subelements('level3')) }}"
You can keep the older with_ syntax using the nested lookup if you wish. The produced result is a bit different so you have to adapt to the datastructure:
- debug:
msg: "{{ item.0 }} {{ item.1.name }} {{ item.2 }}"
with_nested:
- "{{ level1 }}"
- "{{ level2 | subelements('level3') }}"
which can eventually be written back to a loop: syntax as follow:
- debug:
msg: "{{ item.0 }} {{ item.1.name }} {{ item.2 }}"
loop: "{{ q('nested', level1, level2 | subelements('level3')) }}"
All examples give the same result (delta the spitted item for each iteration, example output with first proposition)
PLAY [localhost] *****************************************************************************************************************************************
TASK [debug] *********************************************************************************************************************************************
ok: [localhost] => (item=['abc', [{'name': 'app1', 'level3': ['aaaa', 'bbbb']}, 'aaaa']]) => {
"msg": "abc app1 aaaa"
}
ok: [localhost] => (item=['abc', [{'name': 'app1', 'level3': ['aaaa', 'bbbb']}, 'bbbb']]) => {
"msg": "abc app1 bbbb"
}
ok: [localhost] => (item=['abc', [{'name': 'app2', 'level3': ['eeeee']}, 'eeeee']]) => {
"msg": "abc app2 eeeee"
}
ok: [localhost] => (item=['def', [{'name': 'app1', 'level3': ['aaaa', 'bbbb']}, 'aaaa']]) => {
"msg": "def app1 aaaa"
}
ok: [localhost] => (item=['def', [{'name': 'app1', 'level3': ['aaaa', 'bbbb']}, 'bbbb']]) => {
"msg": "def app1 bbbb"
}
ok: [localhost] => (item=['def', [{'name': 'app2', 'level3': ['eeeee']}, 'eeeee']]) => {
"msg": "def app2 eeeee"
}
PLAY RECAP ***********************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Ansible playbook get values from dict

I want to retrieve the id from a json dict based on a certain name. In this case I would like to get the ID from the "WebLogic" and store it into a variable to use the ID in a next task.
The playbook:
- name: Get Tags
uri:
url: "https://xx.xx-xx.xx{{ uat_env }}api/config/v1/autoTags"
method: GET
headers:
Content-Type: application/json; charset=utf-8
Authorization: xxx
return_content: yes
register: data
- name: Print returned json dictionary
debug:
var: data.json
- debug:
msg: "{{ data.json['values'] | json_query(query) }}"
vars:
name: 'WebLogic'
query: "[?name=='{{ name }}'].id"
- name: TEST
set_fact:
test: "{{ data.json['values'] | json_query([?name=='WebLogic'].id) }}"
Test run:
PLAY [TEST] ********************************************************************
TASK [Get all UAT autoTags] ****************************************************
ok: [localhost]
TASK [Print returned json dictionary] ******************************************
ok: [localhost] => {
"data.json": {
"values": [
{
"id": "5c3849a4-a044-4a98-a67a-c1ea42d652ca",
"name": "Apache"
},
{
"id": "b37511f4-d4e8-4c77-a628-841dba5c65d8",
"name": "WebLogic"
}
]
}
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": [
"b37511f4-d4e8-4c77-a628-841dba5c65d8"
]
}
TASK [TEST] ********************************************************************
fatal: [localhost]: FAILED! => {"msg": "template error while templating string: unexpected char '?' at 37. String: {{ data.json['values'] | json_query([?name=='WebLogic'].id) }}"}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The message returns empty.
the problem is at the data.json.values syntax, please replace with data.json["values"]
the two tasks to show the difference:
- debug:
msg: "{{ data.json.values | json_query(query) }}"
vars:
name: 'WebLogic'
query: "[?name=='{{ name }}'].id"
- debug:
msg: "{{ data.json['values'] | json_query(query) }}"
vars:
name: 'WebLogic'
query: "[?name=='{{ name }}'].id"
update:
To assign the value to a variable, below task should do it:
- set_fact:
my_var: "{{ data.json['values'] | json_query(query) }}"
vars:
name: 'WebLogic'
query: "[?name=='{{ name }}'].id"
- debug:
var: my_var

Ansible nested loop over hostvars

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

How to use proper loop in Ansible uri module

I'm using the Ansible uri module to trigger the pfSense API.
Now I want to create firewall rules in a task (code is truncated).
---
# tasks file for creating firewall rules
- name: "Create firewall rules"
uri:
url: "https://{{ pf_hostname }}/api/v1/firewall/rule"
method: "POST"
body: "{ \
\"client-id\": \"{{ pf_user }}\",
\"client-token\": \"{{ pf_password }}\",
\"type\": \"{{ pf_fw_type_01 }}\",
\"interface\": \"{{ pf_fw_interface_01 }}\",
}"
The vars file looks like this.
---
# vars file for creating firewall rules
# Authentication
pf_hostname: "pfsense.local"
pf_user: "admin"
pf_password: "pfsense"
# Rule 01
pf_fw_type_01: "pass"
pf_fw_interface_01: "wan"
How can I now repeat the task without unnecessary redundancy (e.g. with loop) for other rules?
I only come up with the following idea, but it doesn't seem ideal to me.
loop:
- "{{ item.client-id: {{ pf_user }}, item.type: {{ pf_fw_type_01 }} }}"
- "{{ item.client-id: {{ pf_user }}, item.type: {{ pf_fw_type_02 }} }}"
How about putting the rules as a dynamic parameter in the list?
For example, here's like.
vars.yml
---
# vars file for creating firewall rules
# Authentication
pf_hostname: "pfsense.local"
pf_user: "admin"
pf_password: "pfsense"
rules:
- num: 01
type: "pass"
pf_fw_interface: "wan"
- num: 02
type: "pass"
pf_fw_interface: "wan"
playbook
---
- hosts: localhost
gather_facts: false
vars_files:
- vars.yml
tasks:
- debug:
msg: |
{
"client-id": "{{ pf_user }}",
"client-token": "{{ pf_password }}",
"type": "{{ item.type }}",
"interface": "{{ item.pf_fw_interface }}"
}
loop: "{{ rules }}"
result
$ ansible-playbook main.yml
(snip)
PLAY [localhost] *********************************************************************************************************************************************************************
TASK [debug] *************************************************************************************************************************************************************************
ok: [localhost] => (item={'type': 'pass', 'pf_fw_interface': 'wan'}) => {
"msg": {
"client-id": "admin",
"client-token": "pfsense",
"interface": "wan",
"type": "pass"
}
}
ok: [localhost] => (item={'type': 'pass', 'pf_fw_interface': 'wan'}) => {
"msg": {
"client-id": "admin",
"client-token": "pfsense",
"interface": "wan",
"type": "pass"
}
}
(snip)

Resources