Joining elements of nested lists in Ansible - ansible

I have a nested list str that looks like below:
[["22","ABC","XYZ"],["555","IJK","PQR"],...]
I have to combine the elements of the inside list with a / then join them with a , to form a string as:
22/ABC/XYZ,555/IJK/PQR,...
I tried with set_fact and jinja2 but no luck.
- set_fact:
str1: |-
{%- set fs = "" -%}
{%- set im = "" -%}
{%- for i in str -%}
{%- for elem in i -%}
{%- set im = im + "/" + elem -%}
{%- endfor -%}
{%- set fs = fs + "," + im -%}
{%- endfor -%}
{{ fs }}
- debug: var=str1
Output:
TASK [debug var=str1] **********************************
ok: [host1] => {
"str1": ""
Expected output:
TASK [debug var=str1] **********************************
ok: [host1] => {
"str1": "22/ABC/XYZ,555/IJK/PQR"
Thanks

First map the filter join(/) to the items of the list and then join(,) them
- set_fact:
str1: "{{ str|map('join', '/')|join(',') }}"
- debug: var=str1
gives
str1: 22/ABC/XYZ,555/IJK/PQR

Use wiht_list to create a var with a list with items joined with / and later join it with ,.
- set_fact:
str1: "{{ str1 | default([]) + [ item | join('/') ] }}"
with_list: "{{ str }}"
- debug:
msg: "{{ str1 | join(',') }}"

Related

Jina2 template, Nested Ansible Condition

- set_fact:
{%- for IP in Neighbor_Info.keys() -%}
{%- if item['items']['key'] | string is IP | string -%}
Neighbor_Info[IP]: {{ Neighbor_Info[IP] + item[fetch][0][0] | regex_search(' [0-9]+') }}
{%- endif -%}
{%- endfor -%}
when: fetch in item.keys()
loop: "{{Prefix_Limit1['results']}}"
Error:
fatal: ]: FAILED! =>
msg: 'template error while templating string: Encountered unknown tag ''endif''. Jinja was looking for the following tags: ''endfor'' or ''else''. The innermost block that needs to be closed is ''for''.. String: {% for IP in Neighbor_Info.keys() if item[''items''][''key''] == IP -%} Neighbor_Info[IP]: {{ Neighbor_Info[IP] + item[fetch][0][0] | regex_search('' [0-9]+'') }} {% endif %} {% endfor -%}'
I want to update the value in a dictionary after matching the key value.
Once the match against the key is found, the value is appended in the list.
Dictionary
(item={'key': '192.168.123.9', 'value': ['xxx: 1', 'YYY: 0', 'ABCD']})

can't access dictionary values in jinja

I'm creating a dict, it works fine. but after I create it i can't access the values of in the dict only the keys.
the dict:
- set_fact:
dictonary_obj: |
{% set record_info = dict() %}
{% for item in list%}
{% set _dummy = record_info.update({ item: 0 }) %}
{%endfor%}
"{{ record_info }}"
the access:
- debug:
var: dictonary_obj[item]
with_items: "{{list}}"
Remove the quotation
- set_fact:
dictonary_obj: |
{% set record_info = dict() %}
{% for item in list1 %}
{% set _dummy = record_info.update({ item: 0 }) %}
{%endfor%}
{{ record_info }}
For example, given the list
list1: [a, b, c]
the dictionary is
dictonary_obj:
a: 0
b: 0
c: 0
and the loop works as expected
- debug:
var: dictonary_obj[item]
loop: "{{ list1 }}"
ok: [localhost] => (item=a) =>
ansible_loop_var: item
dictonary_obj[item]: '0'
item: a
ok: [localhost] => (item=b) =>
ansible_loop_var: item
dictonary_obj[item]: '0'
item: b
ok: [localhost] => (item=c) =>
ansible_loop_var: item
dictonary_obj[item]: '0'
item: c
Try the simpler option below which gives the same result
- set_fact:
dictonary_obj: "{{ _dict|from_yaml }}"
vars:
_dict: |
{% for item in list1 %}
{{ item }}: 0
{% endfor %}

set interface name in a variable with jinja loop on ansible

i want to set inside a variable interface name if is equals to a variable pass on command line so i'm using jinja like this :
- name: Inizializzo la rete
hosts: all
gather_facts: true
become: yes
become_user: root
tasks:
- name: stampo interfacce
set_fact:
privata: "{% for interfacce in ansible_interfaces %} {% set int_tmp = 'ansible_facts.' ~ interfacce ~ '.macaddress' %} {% if {{int_tmp}}==mac_privata %} {{interfacce}} {% endif %} {% endfor %}"
- name: Stampo
debug:
msg: "{{privata}}"
but it doesn't works
i call this :
ansible-playbook test.yml --extra-vars "mac_privata=00:50:56:b7:bc:f1"
i do this because i've more than three interface
where i'm wrong?
EDIT:
#mdaniel
Thanks for your answer but it seems that it doesn't substitute hostvars['ansible_' ~ interfacce ~ '.macaddress'] with content of ansible_ens32.macaddress.
For example i do this :
- name: Stampo
debug:
msg: "{{ ansible_ens32.macaddress }}"
i've this output :
TASK [Stampo] **********************************************************************************************************************************************
ok: [10.150.20.130] => {
"msg": "00:50:56:b7:bb:f1"
}
but if i do this :
- name: stampo interfacce
set_fact:
privata: >-
{%- for interfacce in ansible_interfaces -%}
{%- if interfacce != 'lo' -%}
{%- set int_tmp = hostvars['ansible_' ~ interfacce ~ '.macaddress'] -%}
{%- if int_tmp == mac_privata -%}
{{ interfacce }}
{%- endif -%}
{%- endif -%}
{%- endfor -%}
- name: Stampo
debug:
msg: "{{privata}}"
I'm expecting name of interface with mac_privata macaddress in privata variable if i do this :
ansible-playbook test.yml --extra-vars "mac_privata=00:50:56:b7:bb:f1"
i've ens_32 but it's empty:
TASK [stampo interfacce] ***********************************************************************************************************************************
task path: /mnt/c/Users/francesco.ferraro/ansible_test/test.yml:7
ok: [10.150.20.130] => {
"ansible_facts": {
"privata": ""
},
"changed": false
}
Any time one sees {{ inside another jinja2 context, it's almost certainly not what you what to happen. To dynamically look up a variable, use either vars[""] or hostvars[""], or if you prefer the lookup("vars", "")
privata: >-
{%- for interfacce in ansible_interfaces -%}
{%- set int_tmp = vars['ansible_facts.' ~ interfacce ~ '.macaddress'] -%}
{%- if int_tmp == mac_privata -%}
{{ interfacce }}
{%- endif -%}
{%- endfor -%}
Most cases support the conditional for loop, which can make the snippet a little shorter
privata: >-
{%- for interfacce in ansible_interfaces
if mac_privata == vars['ansible_facts.' ~ interfacce ~ '.macaddress'] -%}
{{ interfacce }}
{%- endfor -%}

Ansible inline jinja templating out to a list

I can't figure out why this wouldn't work.
I'm using jinja to dynamically generate the list I would pass to the vmware_guest module, so I can decide in host or group vars if I would like to add additional disks.
I'm using the vmware_disk_info module to look up the size of the template disk and then add any additional disks that I would define in a group var.
It looks to me like the outputted list just isn't a list????
---
- name: "Get facts for named template"
vmware_guest_disk_info:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: False
datacenter: "{{ datacenter_name }}"
name: "{{ template_name }}"
register: template_disk
delegate_to: localhost
- name: "Define new disk structure"
set_fact:
vm_disks: >-
[{% for disk in (template_disk.guest_disk_info|dictsort) %}{
'size_kb': {{ disk[1].capacity_in_kb }},
'datastore': {{ datastore_name }}},
{% endfor %}
{% for disk in additional_disks|default([]) %}{
{% if disk.size_gb is defined %}'size_gb': {{ disk.size_gb }},{% endif %}
{% if disk.size_mb is defined %}'size_mb': {{ disk.size_mb }},{% endif %}
{% if disk.size_kb is defined %}'size_kb': {{ disk.size_kb }},{% endif %}
'datastore': {{ datastore_name }}},
{% endfor %}]
delegate_to: localhost
- name: Clone the template
vmware_guest:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: False
name: "{{ inventory_hostname }}"
template: "{{ template_name }}"
datacenter: "{{ datacenter_name }}"
folder: "/{{ datacenter_name }}/vm/{{ folder_name }}"
cluster: "{{ cluster_name }}"
datastore: "{{ datastore_name }}"
resource_pool: "{{ resource_pool_name }}"
disk: "{{ vm_disks }}"
hardware:
memory_gb: "{{ mem_size_gb }}"
num_cpu: "{{ cpu_size }}"
networks:
- name: "{{ network_name }}"
ip: "{{ ansible_host }}"
netmask: "{{ network_mask }}"
gateway: "{{ network_gw }}"
type: static
customization:
hostname: "{{ inventory_hostname }}"
domain: "{{ domain_name }}"
dns_suffix:
- "{{ domain_name }}"
dns_servers: "{{ network_dns }}"
state: poweredon
wait_for_ip_address: yes
delegate_to: localhost
and an example var would be:
additional_disks:
- size_gb: "120"
datastore: "VSAN_Datastore"
the error out:
fatal: [docker02 -> localhost]: FAILED! => changed=false
module_stderr: |-
Traceback (most recent call last):
File "/home/piwi/.ansible/tmp/ansible-tmp-1586075176.0007603-201082838827202/AnsiballZ_vmware_guest.py", line 102, in <module>
_ansiballz_main()
File "/home/piwi/.ansible/tmp/ansible-tmp-1586075176.0007603-201082838827202/AnsiballZ_vmware_guest.py", line 94, in _ansiballz_main
invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
File "/home/piwi/.ansible/tmp/ansible-tmp-1586075176.0007603-201082838827202/AnsiballZ_vmware_guest.py", line 40, in invoke_module
runpy.run_module(mod_name='ansible.modules.cloud.vmware.vmware_guest', init_globals=None, run_name='__main__', alter_sys=True)
File "/usr/lib/python3.6/runpy.py", line 205, in run_module
return _run_module_code(code, init_globals, run_name, mod_spec)
File "/usr/lib/python3.6/runpy.py", line 96, in _run_module_code
mod_name, mod_spec, pkg_name, script_name)
File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/tmp/ansible_vmware_guest_payload_fiddyn_z/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2834, in <module>
File "/tmp/ansible_vmware_guest_payload_fiddyn_z/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2823, in main
File "/tmp/ansible_vmware_guest_payload_fiddyn_z/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2342, in deploy_vm
File "/tmp/ansible_vmware_guest_payload_fiddyn_z/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2005, in configure_disks
AttributeError: 'str' object has no attribute 'get'
module_stdout: ''
msg: |-
MODULE FAILURE
See stdout/stderr for the exact error
rc: 1
The full traceback is:
Traceback (most recent call last):
File "/home/piwi/.ansible/tmp/ansible-tmp-1586075175.423514-45894902001082/AnsiballZ_vmware_guest.py", line 102, in <module>
_ansiballz_main()
File "/home/piwi/.ansible/tmp/ansible-tmp-1586075175.423514-45894902001082/AnsiballZ_vmware_guest.py", line 94, in _ansiballz_main
invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
File "/home/piwi/.ansible/tmp/ansible-tmp-1586075175.423514-45894902001082/AnsiballZ_vmware_guest.py", line 40, in invoke_module
runpy.run_module(mod_name='ansible.modules.cloud.vmware.vmware_guest', init_globals=None, run_name='__main__', alter_sys=True)
File "/usr/lib/python3.6/runpy.py", line 205, in run_module
return _run_module_code(code, init_globals, run_name, mod_spec)
File "/usr/lib/python3.6/runpy.py", line 96, in _run_module_code
mod_name, mod_spec, pkg_name, script_name)
File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/tmp/ansible_vmware_guest_payload_mspbq6yr/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2834, in <module>
File "/tmp/ansible_vmware_guest_payload_mspbq6yr/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2823, in main
File "/tmp/ansible_vmware_guest_payload_mspbq6yr/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2342, in deploy_vm
File "/tmp/ansible_vmware_guest_payload_mspbq6yr/ansible_vmware_guest_payload.zip/ansible/modules/cloud/vmware/vmware_guest.py", line 2005, in configure_disks
AttributeError: 'str' object has no attribute 'get'
example var output:
ok: [gluster01] =>
vm_disks: |-
[{
'size_kb': 125829120,
'datastore': VSAN_Datastore},
{
'size_gb': 120, 'datastore': VSAN_Datastore},
]
ok: [gluster02] =>
vm_disks: |-
[{
'size_kb': 125829120,
'datastore': VSAN_Datastore},
{
'size_gb': 120, 'datastore': VSAN_Datastore},
]
ok: [docker01] =>
vm_disks: |-
[{
'size_kb': 125829120,
'datastore': VSAN_Datastore},
]
New code based on below suggestion:
The same error, however:
- name: "Define new disk structure"
set_fact:
vm_disks: >-
{%- set results = [] -%}
{%- for osdisk in ( template_disk.guest_disk_info | dictsort ) -%}
{%- set od = { "size_kb": osdisk[1].capacity_in_kb } -%}
{%- set _ = od.update({ "datastore": osdisk[1].backing_datastore }) -%}
{%- set _ = results.append(od) -%}
{%- endfor -%}
{%- for disk in additional_disks|default([]) -%}
{%- set d = {"size_gb": disk.size_gb} if (disk.size_gb is defined) else {} -%}
{%- set _ = d.update({"datastore": disk.datastore_name}) -%}
{%- set _ = results.append(d) -%}
{%- endfor -%}
"{{ results }}"
var debugging:
TASK [vm_clone : Debugging var] *******************************************************************************************************************
ok: [gluster01] =>
vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}, {''size_gb'': ''120'', ''datastore'': ''VSAN_Datastore''}]"'
ok: [gluster02] =>
vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}, {''size_gb'': ''120'', ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker01] =>
vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker02] =>
vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker03] =>
vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker04] =>
vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
ok: [docker05] =>
vm_disks: '"[{''size_kb'': 125829120, ''datastore'': ''VSAN_Datastore''}]"'
This looks like a list to me, So to verify this, I piped the result to the to_json filter, and it errored out saying, it's not a str it's a list.
So it is definitely a list.
As Uttam correctly pointed out, but didn't provide an answer for, the problem is that your set_fact: produces a string but disks must be a list of dict, as one can see from the fine manual (and the code)
There are two reasons: the first is that ansible only auto-coerces JSON-looking strings into actual python list and dict structures, but you have used python syntax with the single quoted string literals and trailing commas, both of which are illegal is JSON
the second is that one should never build up a rich data structure in jinja using text: it has strong support for those data structures, as well as the wonderful | to_json and |from_json filters to ensure the output is legal JSON and correctly escaped characters
But I know that's a lot of words, so I believe the smallest change you can make to cause your situation to work is to stop using single quotes and guard the trailing comma:
- name: "Define new disk structure"
set_fact:
vm_disks: >-
[
{% for disk in (template_disk.guest_disk_info|dictsort) %}
{{ "" if loop.first else "," }}
{
"size_kb": {{ disk[1].capacity_in_kb }},
"datastore": "{{ datastore_name }}"
}
{% endfor %}
{% for disk in additional_disks|default([]) %}
{{ "," if template_disk.guest_disk_info else "" }}
{
{% if disk.size_gb is defined %}"size_gb": {{ disk.size_gb }},{% endif %}
{% if disk.size_mb is defined %}"size_mb": {{ disk.size_mb }},{% endif %}
{% if disk.size_kb is defined %}"size_kb": {{ disk.size_kb }},{% endif %}
"datastore": "{{ datastore_name }}"
}
{% endfor %}
]
The correct code would be something akin to:
set_fact:
vm_disks: >-
{%- set results = [] -%}
{%- for disk in additional_disks|default([]) -%}
{%- set d = {"datastore": datastore_name} -%}
{%- set _ = d.update({"size_gb": disk.size_gb} if (disk.size_gb is defined) else {}) -%}
{%- set _ = results.append(d) -%}
{%- endfor -%}
{{ results }}
final working code, Big Thanks to #mdaniel
- name: "Get facts for named template"
vmware_guest_disk_info:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: False
datacenter: "{{ datacenter_name }}"
name: "{{ template_name }}"
register: template_disk
delegate_to: localhost
- name: "Define new disk structure"
set_fact:
vm_disks: >-
{%- set results = [] -%}
{%- for osdisk in ( template_disk.guest_disk_info | dictsort ) -%}
{%- set od = { "size_kb": osdisk[1].capacity_in_kb } -%}
{%- set _ = od.update({ "datastore": osdisk[1].backing_datastore }) -%}
{%- set _ = results.append(od) -%}
{%- endfor -%}
{%- for disk in additional_disks|default([]) -%}
{%- if (disk.size_gb is defined) -%}
{%- set d = {"size_gb": disk.size_gb} -%}
{%- set _ = d.update({"datastore": disk.datastore_name}) -%}
{%- set _ = results.append(d) -%}
{%- endif -%}
{%- if (disk.size_mb is defined) -%}
{%- set d = {"size_mb": disk.size_mb} -%}
{%- set _ = d.update({"datastore": disk.datastore_name}) -%}
{%- set _ = results.append(d) -%}
{%- endif -%}
{%- if (disk.size_kb is defined) -%}
{%- set d = {"size_kb": disk.size_kb} -%}
{%- set _ = d.update({"datastore": disk.datastore_name}) -%}
{%- set _ = results.append(d) -%}
{%- endif -%}
{%- endfor -%}
{{ results }}
- name: Clone the template
vmware_guest:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: False
name: "{{ inventory_hostname }}"
template: "{{ template_name }}"
datacenter: "{{ datacenter_name }}"
folder: "/{{ datacenter_name }}/vm/{{ folder_name }}"
cluster: "{{ cluster_name }}"
datastore: "{{ datastore_name }}"
resource_pool: "{{ resource_pool_name }}"
disk: "{{ vm_disks }}"
hardware:
memory_gb: "{{ mem_size_gb }}"
num_cpu: "{{ cpu_size }}"
networks:
- name: "{{ network_name }}"
ip: "{{ ansible_host }}"
netmask: "{{ network_mask }}"
gateway: "{{ network_gw }}"
type: static
customization:
hostname: "{{ inventory_hostname }}"
domain: "{{ domain_name }}"
dns_suffix:
- "{{ domain_name }}"
dns_servers: "{{ network_dns }}"
state: poweredon
wait_for_ip_address: yes
delegate_to: localhost
and set your vars like this, in any combination you need:
additional_disks:
- size_gb: "120"
datastore_name: "VSAN_Datastore"
- size_mb: "10240"
datastore_name: "VSAN_Datastore"
- size_kb: "10240000"
datastore_name: "VSAN_Datastore"

Creating a comma separated string from a dictionary in Ansible

I want to write an Ansible role to be able to alter a given Kafka topic. I am using a dictionary of key/value pairs.
The command module is then used to execute a Kafka script that take a string of comma separated values. For instance, use app_kafka_topic list:
---
app_kafka_topic:
cleanup.policy :
- "delete"
retention.ms :
- "146800000"
partitions :
- "6"
replication-factor :
- "2"
and create the string:
"cleanup.policy=delete,retention.ms=146800000,partitions=6,replication-factor=2"
This is what I have so far.
- name: Reading the Default Topic Properties
set_fact:
app_kafka_topic_properties_dicts: |
{% set res = [] -%}
{% for key in app_kafka_topic.keys() -%}
{% for value in app_kafka_topic[key] -%}
{% set ignored = res.extend([{'topic_property': key, 'value':value}]) -%}
{%- endfor %}
{%- endfor %}
{{ res }}
- name: Create Topic with Default Properties
command: "{{ kafka_bin_dir }}/{{ kafka_config_script }}
--zookeeper {{ prefix }}-kafka-{{ Kafka_node }}.{{ DNSDomain}}:{{ zookeeper_port }}
--entity-type topics
--alter
--entity-name {{ kafka_topic }}
--add-config
{{ properties }}"
with_items: "{{ app_kafka_topic_properties_dicts }}"
register: createdTopic
vars:
properties: |-
{% for key in app_kafka_topic.keys() %}
{% for value in app_kafka_topic[key] %}
"{{ key }}={{ value }}"
{%- endfor %}
{%- endfor %}
However, the properties variable is not concatenating the values to the end of a string. Is there a way to append the values to a string and separate them by a comma?
Is this the code that you're looking for?
play.yml
- hosts: localhost
gather_facts: no
vars:
string: ""
app_kafka_topic:
cleanup.policy :
- "delete"
retention_ms :
- "146800000"
partitions :
- "6"
replication_factor :
- "2"
tasks:
- set_fact:
string: "{{ string }}{{ (index > 0)|ternary(',','') }}{{ item.key }}={{ item.value[0] }}"
loop: "{{ app_kafka_topic|dict2items }}"
loop_control:
index_var: index
- debug:
var: string
$ ansible-playbook play.yml | grep string
"string": "retention_ms=146800000,cleanup.policy=delete,replication_factor=2,partitions=6"

Resources