I have a variable that looks like this:
device:
- sdb: 2
- sdc: 3
- sdd: 4
How can I produce the result with ansible loops:
sdb 1
sdb 2
sdc 1
sdc 2
sdc 3
sdd 1
sdd 2
sdd 3
sdd 4
I tried with_sequence and loop_control but it didn't work.
Wrote a debug task with ansible loop and jinja which should give you the desired result. Refactor as required.
- name: Debug device var
debug:
msg: "{% for key, value in item.iteritems() %}{% for i in range(value) %} {{ key }} {{ loop.index }} {% endfor %}{% endfor %}"
loop: "{{ device }}"
Finally, I got the solution but I changed the format of the variable, however. The variable is changed to:
device:
sdb: 2
sdc: 3
sdd: 4
The result is:
ok: [ceph-host-2] => (item=1) => {
"msg": "sdd 1"
}
ok: [ceph-host-2] => (item=2) => {
"msg": "sdd 2"
}
ok: [ceph-host-2] => (item=3) => {
"msg": "sdd 3"
}
ok: [ceph-host-2] => (item=4) => {
"msg": "sdd 4"
}
TASK [osd : debug]
ok: [ceph-host-2] => (item=1) => {
"msg": "sdb 1"
}
ok: [ceph-host-2] => (item=2) => {
"msg": "sdb 2"
}
TASK [osd : debug]
ok: [ceph-host-2] => (item=1) => {
"msg": "sdc 1"
}
ok: [ceph-host-2] => (item=2) => {
"msg": "sdc 2"
}
ok: [ceph-host-2] => (item=3) => {
"msg": "sdc 3"
}
main.yml
- include_tasks: inner.yml
loop: "{{ device.keys() }}"
loop_control:
loop_var: outer_item
inner.yml
- debug:
msg: "{{ outer_item }} {{ item }}"
with_sequence: count={{ device[outer_item] }}
The variable is:
device:
- sdb: 2
- sdc: 3
- sdd: 4
main.yml
- include_tasks: inner.yml
loop: "{{ device }}"
loop_control:
loop_var: outer_item
inner.yml
- debug:
msg: "{{ outer_item.keys()[0] }} {{ item }}"
with_sequence: count={{ outer_item.values()[0] }}
Related
I'm unsure if the title matches to what I'm trying to ask but I didn't know what other title to use.
So my question:
What I'm trying to do is gather disk info from VMWare and post the output of capacity_in_kb for EACH disk in a file (but currently only debug).
- name: Gather disk info from virtual machine
community.vmware.vmware_guest_disk_info:
hostname: "{{ lookup('ansible.builtin.env', 'VMWARE_HOST') }}"
username: "{{ lookup('ansible.builtin.env', 'VMWARE_USER') }}"
password: "{{ lookup('ansible.builtin.env', 'VMWARE_PASSWORD') }}"
validate_certs: no
name: "{{ ansible_hostname }}"
datacenter: "{{ lookup('ansible.builtin.env', 'VMWARE_HOST') }}"
delegate_to: 127.0.0.1
register: disk_info
- debug:
var: disk_info
This outputs the following:
{
"disk_info": {
"guest_disk_info": {
"0": {
"key": 2000,
"label": "Hard disk 1",
"summary": "16,777,216 KB",
"backing_filename": "[xxx] xxx.vmdk",
"backing_datastore": "xxx_log",
"controller_key": 1000,
"unit_number": 0,
"capacity_in_kb": 16777216,
"capacity_in_bytes": 17179869184,
"backing_type": "FlatVer2",
"backing_writethrough": false,
"backing_thinprovisioned": false,
"backing_eagerlyscrub": false,
"backing_diskmode": "persistent",
"backing_disk_mode": "persistent",
"backing_uuid": "xxxxxxxxxxxxxxxx",
"controller_bus_number": 0,
"controller_type": "paravirtual"
},
"1": {
"key": 2001,
"label": "Hard disk 2",
"summary": "262,144,000 KB",
"backing_filename": "[xxx] xxx.vmdk",
"backing_datastore": "xxx_log",
"controller_key": 1000,
"unit_number": 1,
"capacity_in_kb": 262144000,
"capacity_in_bytes": 268435456000,
"backing_type": "FlatVer2",
"backing_writethrough": false,
"backing_thinprovisioned": false,
"backing_eagerlyscrub": false,
"backing_diskmode": "persistent",
"backing_disk_mode": "persistent",
"backing_uuid": "xxx",
"controller_bus_number": 0,
"controller_type": "paravirtual"
}
},
"failed": false,
"changed": false
},
"_ansible_verbose_always": true,
"_ansible_no_log": false,
"changed": false
}
We now want to get capacity_in_kb for every disk. Some VMs only have one disk attached, some have two, some three and so on...
We tried
- name: with_dict
ansible.builtin.debug:
msg: "{{ item.value.label }} - {{ item.value.capacity_in_kb }}"
loop: "{{ disk_info.guest_disk_info|dict2items }}"
but that also doesn't work for us and only gives us the output of the first disk [0] in the list.
Does anyone have an idea? Thanks in advance :)
Edit: as #β.εηοιτ.βε pointet out it's
- loop: "{{ disk_info|dict2items }}"
+ loop: "{{ disk_info.guest_disk_info|dict2items }}"
but still it only iterates over the first disk and not the second, third, etc..
EDIT2: it worked, but I just didn't see it...dumb me
If you want to iterate the data, the task below
- debug:
msg: "{{ item.value.label }}: {{ item.value.capacity_in_kb }}"
loop: "{{ disk_info.guest_disk_info|dict2items }}"
loop_control:
label: "{{ item.key }}"
gives
ok: [localhost] => (item=0) =>
msg: 'Hard disk 1: 16777216'
ok: [localhost] => (item=1) =>
msg: 'Hard disk 2: 262144000'
The next option is to avoid the iteration and create a dictionary
label_capacity_q: '*.[label, capacity_in_kb]'
label_capacity: "{{ dict(disk_info.guest_disk_info|
json_query(label_capacity_q)) }}"
gives
label_capacity:
Hard disk 1: 16777216
Hard disk 2: 262144000
Example of a complete playbook for testing
- hosts: localhost
vars:
label_capacity_q: '*.[label, capacity_in_kb]'
label_capacity: "{{ dict(disk_info.guest_disk_info|
json_query(label_capacity_q)) }}"
tasks:
- debug:
var: label_capacity
- debug:
msg: "{{ item.key }}: {{ item.value }}"
loop: "{{ label_capacity|dict2items }}"
- debug:
msg: |-
{% for k,v in label_capacity.items() %}
{{ k }}: {{ v }}
{% endfor %}
gives (abridged)
TASK [debug] *********************************************************************************
ok: [localhost] =>
label_capacity:
Hard disk 1: 16777216
Hard disk 2: 262144000
TASK [debug] *********************************************************************************
ok: [localhost] => (item={'key': 'Hard disk 1', 'value': 16777216}) =>
msg: 'Hard disk 1: 16777216'
ok: [localhost] => (item={'key': 'Hard disk 2', 'value': 262144000}) =>
msg: 'Hard disk 2: 262144000'
TASK [debug] *********************************************************************************
ok: [localhost] =>
msg: |-
Hard disk 1: 16777216
Hard disk 2: 262144000
Given the data, for example in group_vars
shell> cat group_vars/all/disk_info.yml
---
disk_info:
changed: false
failed: false
guest_disk_info:
'0':
backing_datastore: xxx_log
backing_disk_mode: persistent
backing_diskmode: persistent
backing_eagerlyscrub: false
backing_filename: '[xxx] xxx.vmdk'
backing_thinprovisioned: false
backing_type: FlatVer2
backing_uuid: xxxxxxxxxxxxxxxx
backing_writethrough: false
capacity_in_bytes: 17179869184
capacity_in_kb: 16777216
controller_bus_number: 0
controller_key: 1000
controller_type: paravirtual
key: 2000
label: Hard disk 1
summary: 16,777,216 KB
unit_number: 0
'1':
backing_datastore: xxx_log
backing_disk_mode: persistent
backing_diskmode: persistent
backing_eagerlyscrub: false
backing_filename: '[xxx] xxx.vmdk'
backing_thinprovisioned: false
backing_type: FlatVer2
backing_uuid: xxx
backing_writethrough: false
capacity_in_bytes: 268435456000
capacity_in_kb: 262144000
controller_bus_number: 0
controller_key: 1000
controller_type: paravirtual
key: 2001
label: Hard disk 2
summary: 262,144,000 KB
unit_number: 1
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
I have an inventory in which many host_vars are set. Each host will contain a different number of datasets.
e.g.
host: host1
ip-addr: 192.0.2.12/24
datasets:
set1:
var1: 'east'
var2: 'west'
set2:
var1: 'north'
var2: 'south'
I can create a loop to count the datasets, but I don't seem able to use it to reference [varX]:
- name: "test loop"
debug:
msg:
- "{{ item }}"
- "{{ 'datasets.set' + item + '.var1' }}"
- "{{ datasets.set1.var1 }}"
loop: "{{ query('sequence', 'start=1 end='+((datasets|length)|string)) }}"
This appears to assemble the variable name I'm attempting to reference, however does not return the value associated with it. Manually calling that variable does return the interesting value.
ok: [host1] => (item=1) => {
"msg": [
"1",
"datasets.set1.var1",
"east"
]
}
ok: [host1] => (item=2) => {
"msg": [
"2",
"datasets.set2.var1",
"east"
]
}
Is what I'm doing possible, or should I be approaching it from another angle?
thanks in advance.
The task
- debug:
msg: "set{{ item }}.var1 = {{ datasets['set' ~ item].var1 }}"
loop: "{{ range(1, datasets|length+1)|list }}"
gives
"msg": "set1.var1 = east"
"msg": "set2.var1 = north"
I'm getting different results when using loop vs with_items when trying to iterate over a list of dictionaries.
I've tried using loop|dict2items (the structure isn't a dictionary, & it tells me as much. heh) and loop with the flatten filter.
Here is the list of dictionaries:
"msg": [
{
"id": "id1",
"ip": "ip1",
"name": "name1"
},
{
"id": "id2",
"ip": "ip2",
"name": "name2"
},
{
"id": "id3",
"ip": "ip3",
"name": "name3"
},
{
"id": "id4",
"ip": "ip4",
"name": "name4"
}
]
}
Here is the task in the playbook:
- name: Add privateIp windows_instances to inventory
add_host:
name: "{{ item.ip }}"
aws_name: "{{ item.name }}"
groups: windows_instances
aws_instanceid: "{{ item.id }}"
ansible_user: "{{ windows_user }}"
ansible_password: "{{ windows_password }}"
ansible_port: 5985
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore
loop:
- "{{ list1 | flatten(levels=1) }}"
When attempting to run the above code, I get the "list object has no attribute" error. I've tried different flatten levels to no avail.
HOWEVER...
If I simply replace the loop above with:
with_items:
- "{{ list1 }}"
Everything works perfectly. I'm missing something in the with_items > loop translation here...
Don't put a - before your list.
And here, you have a list of dicts, so you don't need to flatten neither.
This playbook:
- hosts: localhost
gather_facts: no
vars:
demo_list:
- ip: 1.2.3.4
id: 1
name: demo1
- ip: 2.2.3.4
id: 2
name: demo2
- ip: 3.2.3.4
id: 3
name: demo3
tasks:
- name: the list
debug:
msg: "{{ demo_list }}"
- name: unflattened list
debug:
msg: "{{ item.id }} {{ item.ip }} {{ item.name }}"
loop:
"{{ demo_list }}"
- name: flattened list == unflattened list in this case
debug:
msg: "{{ item.id }} {{ item.ip }} {{ item.name }}"
loop:
"{{ demo_list | flatten(levels=1) }}"
gives this result:
PLAY [localhost] ***************************************************************************************
TASK [the list] ****************************************************************************************
ok: [localhost] => {
"msg": [
{
"id": 1,
"ip": "1.2.3.4",
"name": "demo1"
},
{
"id": 2,
"ip": "2.2.3.4",
"name": "demo2"
},
{
"id": 3,
"ip": "3.2.3.4",
"name": "demo3"
}
]
}
TASK [unflattened list] ********************************************************************************
ok: [localhost] => (item=None) => {
"msg": "1 1.2.3.4 demo1"
}
ok: [localhost] => (item=None) => {
"msg": "2 2.2.3.4 demo2"
}
ok: [localhost] => (item=None) => {
"msg": "3 3.2.3.4 demo3"
}
TASK [flattened list == unflattened list in this case] *************************************************
ok: [localhost] => (item=None) => {
"msg": "1 1.2.3.4 demo1"
}
ok: [localhost] => (item=None) => {
"msg": "2 2.2.3.4 demo2"
}
ok: [localhost] => (item=None) => {
"msg": "3 3.2.3.4 demo3"
}
PLAY RECAP *********************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
I'm trying to use vars to perform loops in ansible but I'm struggling to get them to work. The documentation talks about the loop attribute but as far as I can tell, this only works if you have one loop in a task.
Is it possible to use different loops in a task or must it be split into different tasks?
- name: "Configure ufw"
ufw:
policy: "{{ defaults.policy }}"
direction: "{{ defaults.direction }}"
rule: allow
src: "{{ rules }}"
logging: on
state: enabled
vars:
defaults:
- { direction: incoming, policy: deny }
- { direction: outgoing, policy: allow }
rules:
- 80/tcp
- 443/tcp
When I run the code I get the error
"msg": "The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'direction'
What you need is with_nested. This:
---
- hosts: localhost
become: no
connection: local
vars:
defaults:
- { direction: incoming, policy: deny }
- { direction: outgoing, policy: allow }
rules:
- 80/tcp
- 443/tcp
tasks:
- name: Change connect file to bulk mode
debug:
msg: "{{ item.0.direction }}: {{ item.1 }}"
with_nested:
- "{{ defaults }}"
- "{{ rules }}"
Gives this output:
TASK [Change connect file to bulk mode] *********************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "incoming: 80/tcp"
}
ok: [localhost] => (item=None) => {
"msg": "incoming: 443/tcp"
}
ok: [localhost] => (item=None) => {
"msg": "outgoing: 80/tcp"
}
ok: [localhost] => (item=None) => {
"msg": "outgoing: 443/tcp"
}
with_nested may be an option, as per Jack's answer.
Alternately, and possibly the currently preferred mechanism, is to use loop: "{{ your_first_list | product (your_second_list) | list }}" to combine your multiple lists into a single list.
For example, this:
- name: test
ansible.builtin.debug:
msg: "{{ item.0.0 }} - {{ item.0.1}} - {{ item.1 }}"
loop: "{{ [ ['incoming','deny'], ['outgoing','allow']] | product( [ '80/tcp', '443/tcp' ] ) | list }}"
produces this output:
ok: [examplehost.my.example.com] => (item=[[u'incoming', u'deny'], u'80/tcp']) => {
"msg": "incoming - deny - 80/tcp"
}
ok: [examplehost.my.example.com] => (item=[[u'incoming', u'deny'], u'443/tcp']) => {
"msg": "incoming - deny - 443/tcp"
}
ok: [examplehost.my.example.com] => (item=[[u'outgoing', u'allow'], u'80/tcp']) => {
"msg": "outgoing - allow - 80/tcp"
}
ok: [examplehost.my.example.com] => (item=[[u'outgoing', u'allow'], u'443/tcp']) => {
"msg": "outgoing - allow - 443/tcp"
}