how to get vlan id from vcenter using ansible - ansible

I am trying to use below ansible module to get vlan_id information.
- name: Get all portgroups in dvswitch vDS
vmware_dvs_portgroup_find:
hostname: "{{ vsphere_host }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_password }}"
dvswitch: 'test-123' --- dvswitch
validate_certs: no
with_items: "{{ dvswitch_list }}"
register: test
- debug: msg="{{ test }}"
I am getting out in below format.
"results": [
{
"ansible_loop_var": "item",
"changed": false,
"dvs_portgroups": [
{
"dvswitch": "test-123",
"name": "DPortGroup2",
"pvlan": false,
"trunk": false,
"vlan_id": "220"
},
{
"dvswitch": "test-123",
"name": "PP1",
"pvlan": false,
"trunk": false,
"vlan_id": "465"
},
{
"dvswitch": "test-123",
"name": "PP2",
"pvlan": false,
"trunk": false,
"vlan_id": "685"
},
I am using below debug msg to get vlan_id but some how it does not work.
- debug: msg="{{ item.vlan_id }}"
with_items: "{{ test.results.dvs_portgroups }}"
ASK [role_vmware_datastore_size : debug] ********************************************************************************
fatal: [192.168.90.00]: FAILED! => {"msg": "'list object' has no attribute 'dvs_portgroups'"}

test.results is the list of results for each item of the with_items of the vmware_dvs_portgroup_find task. Therefore you have to iterate over this list before accessing a field of each element.
If you want to iterate over all the dvs_portgroups of all the results, you can do that:
- debug: msg="{{ item.vlan_id }}"
with_items: "{{ test.results | map(attribute='dvs_portgroups') | flatten | list }}"
map(attribute='dvs_portgroups') is transforming the list of results into a list of the dvs_portgroups of each result
| flatten is transforming the list of lists (the dvs_portgroups) into a list of all elements of each lists
| list is transforming an iterator back to a list that can be interpreted by with_items

above debug code is working fine. I am getting list of vlan_id. Just to add more details. I have 4 dvswich and getting vlan_id from all dvswtich. I want to make it format like each dvswitch name with there vlan_id.
test-123 -- dvswith name
220
221
222
test-456 -- dvswtich name
300
301
302
I am trying below way but its giving only one vlan_id from each dvswitch.
- name: Final VLAN Details
set_fact:
vlan_details: "{{ vlan_details | default([]) + [dict(name=item[0], size=item[1])] }}"
loop: "{{ dvswitch | zip(vlan_id) | list }}"
loop_control:
label: " "

Related

Ansible conditionals with with_items/nested

Given the following tasks:
- name: Gather security group info
amazon.aws.ec2_group_info:
filters:
"tag:vpn_ports": "*"
register: sec_group_info_output
- name: Extract security groups and ports
set_fact:
vpn_groups: "{{ vpn_groups + [{ 'group_id': item.group_id, 'ports': item.tags.vpn_ports.split(',') }] }}"
with_items:
- "{{ sec_group_info_output | json_query('security_groups') }}"
vars:
vpn_groups: []
when: sec_group_info_output != []
- name: Generate list with CIDRs
set_fact:
vpn_rules: "{{ vpn_rules + [{ 'group_id': item.0.group_id , 'port': item.1, 'cidr': item.2 }] }}"
with_nested:
- "{{ vpn_groups|subelements('ports') }}"
- "{{ cidr_ranges }}"
vars:
vpn_rules: []
when: sec_group_info_output != []
I am trying to skip the last two tasks if the first task returns an empty set.
My understanding is that the when conditional is evaluated for every loop, and not just for the task as a whole.
The below therefor gives me:
TASK [security_groups : Gather security group info] ****************************
ok: [localhost]
TASK [security_groups : Extract security groups and ports] *********************
TASK [security_groups : Generate list with CIDRs] ******************************
fatal: [localhost]: FAILED! => {"msg": "obj must be a list of dicts or a nested dict"}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=1 skipped=1 rescued=0 ignored=0
🚨 Error: The command exited with status 2
How would I go about fixing this error? I've tried putting |default([]) into my nested_items like below:
- name: Generate list with CIDRs
set_fact:
vpn_rules: "{{ vpn_rules + [{ 'group_id': item.0.group_id , 'port': item.1, 'cidr': item.2 }] |default([])}}"
with_nested:
- "{{ vpn_groups|subelements('ports') |default([])}}"
- "{{ cidr_ranges |default([])}}"
vars:
vpn_rules: []
when: sec_group_info_output != []
The error remains the same.
I've also tried putting both tasks in a block, but this had no effect and the error remains the same.
How would I be able to skip these tasks based on my condition?
Firstly, your condition is completely wrong. sec_group_info_output is a registered variable, so it can never be equal to an empty list. It will always be a dictionary containing information about the task execution. In order to have a chance of working as intended, it would need to be:
when: sec_group_info_output.security_groups != []
# more idiomatically, an empty list is false so you can just treat the value as a boolean
when: sec_group_info_output.security_groups
# or you can check the length
when: sec_group_info_output['security_groups'] | length > 0
However, in this case you don't need conditions at all. You're looping over the same list you're checking, and an empty loop will not execute any tasks. You just need a | default([]) in the loop definition on the third task in case the second didn't execute, and everything's fine.
- name: Gather security group info
amazon.aws.ec2_group_info:
filters:
"tag:vpn_ports": "*"
register: sec_group_info_output
- name: Extract security groups and ports
set_fact:
vpn_groups: "{{ vpn_groups | default([]) + [{ 'group_id': item.group_id, 'ports': item.tags.vpn_ports.split(',') }] }}"
loop: "{{ sec_group_info_output.security_groups }}"
- name: Generate list with CIDRs
set_fact:
vpn_rules: "{{ vpn_rules | default([]) + [{ 'group_id': item.0.0.group_id , 'port': item.0.1, 'cidr': item.1 }] }}"
loop: "{{ vpn_groups | default([]) | subelements('ports') | product(cidr_ranges) }}"
{{ vpn_groups | subelements('ports') | default([]) }} was headed in the right direction, but you put the default() in the wrong place. It needs to be before the subelements() filter so that that filter receives an empty list, not an undefined variable.

"The task includes an option with an undefined variable. The error was: list object has no element 0

Getting following error with Ansible playbook. Please help in finding the root cause.
Error:
TASK [CrowdStrike Falcon | Download Falcon Sensor Installation Package] ************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: list object has no element 0\n\nThe error appears to be in '/var/log/download3.yml': line 131, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: CrowdStrike Falcon | Download Falcon Sensor Installation Package\n ^ here\n"}
Ansible playbook (if anyone want to test then please add falcon_client_id and falcon_client_secret before testing).
---
- hosts: localhost
vars:
falcon_api_url1: "https://api.us-2.crowdstrike.com"
##falcon_cloud: "api.crowdstrike.com"
falcon_cloud: "api.us-2.crowdstrike.com"
falcon_oauth2_token_url: "https://api.us-2.crowdstrike.com/oauth2/token"
falcon_os_family: "{{ ansible_system | lower }}"
falcon_target_os: "{{ ansible_distribution }}"
falcon_os_version: "{{ ansible_distribution_major_version }}"
falcon_install_tmp_dir: "/tmp"
falcon_install_temp_directory: "/var/deploy/roles/crowdstrike/tasks"
falcon_client_id: ""
falcon_client_secret: ""
falcon_sensor_update_policy_name: ""
falcon_sensor_version_decrement: 0
falcon_sensor_version: ""
tasks:
- name: Set fact for api url
set_fact:
falcon_api_url: "{{ falcon_api_url1 }}"
falcon_oauth2_token_url: "{{ falcon_oauth2_token_url }}"
falcon_os_family: "{{ falcon_os_family }}"
falcon_target_os: "{{ falcon_target_os }}"
falcon_install_tmp_dir: "{{ falcon_install_tmp_dir }}"
falcon_client_id: "{{ falcon_client_id }}"
- name: Show Fact
debug:
var: hostvars[inventory_hostname]
- name: CROWDSTRIKE - AUTHENTICATE API
ansible.builtin.uri:
url: "{{ falcon_oauth2_token_url | d(falcon_api_url + '/oauth2/token') }}"
return_content: yes
method: POST
body_format: form-urlencoded
status_code: 201
body:
client_id: "{{ falcon_client_id }}"
client_secret: "{{ falcon_client_secret }}"
register: oauth_request
##- name: CrowdStrike Falcon | Get list of installers
## uri:
## url: "https://{{ falcon_api_url }}/sensors/combined/installers/v1?filter=platform%3A%22{{ falcon_os_family }}*%22%2Bos%3A%22{{ falcon_target_os }}%22&=&sort=version.desc"
##method: GET
##return_content: true
##headers:
##authorization: "Bearer {{ falcon_oauth2_token_url.json.access_token }}"
##register: falcon_api_installer_list
- name: CrowdStrike Falcon | Filter to installers for OS major version
set_fact:
falcon_api_sha_hash: "{{ falcon_api_installer_list.json.resources | selectattr('os_version', 'equalto', ansible_distribution_major_version | list ) }}"
- name: "CrowdStrike Falcon | Default Operating System configuration"
ansible.builtin.set_fact:
falcon_target_os: "{{ ansible_distribution }}"
falcon_os_family: "{{ ansible_system | lower }}"
falcon_os_version: "{{ ansible_distribution_major_version }}"
falcon_sensor_update_policy_platform: "{{ ansible_system }}"
falcon_os_vendor: "{{ ansible_os_family | lower if (ansible_os_family == 'RedHat' and ansible_distribution != 'Amazon') else ansible_distribution | lower }}"
# Block when falcon_sensor_update_policy_name is supplied
- name: Sensor Update Policy Block
block:
- name: "CrowdStrike Falcon | Build Sensor Update Policy API Query"
ansible.builtin.set_fact:
falcon_sensor_update_policy_query: "{{ 'platform_name:\"' + falcon_sensor_update_policy_platform + '\"+name.raw:\"' + falcon_sensor_update_policy_name + '\"' }}"
- name: CrowdStrike Falcon | Authenticate to CrowdStrike API
ansible.builtin.uri:
url: "https://{{ falcon_cloud }}/oauth2/token"
method: POST
body_format: json
body:
"client_id={{ falcon_client_id }}&client_secret={{ falcon_client_secret }}"
return_content: true
follow_redirects: all
status_code: 201
headers:
content-type: application/x-www-form-urlencoded
register: falcon_api_oauth2_token
##no_log: "{{ falcon_api_enable_no_log }}"
- name: Show fact falcon_api_oauth2_token
debug:
var: falcon_api_oauth2_token
- name: "CrowdStrike Falcon | Build Sensor Update Policy API Query"
ansible.builtin.set_fact:
falcon_sensor_update_policy_query: "{{ 'platform_name:\"' + falcon_sensor_update_policy_platform + '\"+name.raw:\"' + falcon_sensor_update_policy_name + '\"' }}"
##- name: "CrowdStrike Falcon | Build API Sensor Query based on Sensor Update Policy"
##ansible.builtin.set_fact:
##falcon_os_query: "{{ 'os:\"' + falcon_target_os + '\"+os_version:\"' + falcon_os_version + '\"+version:\"' }}"
- name: "CrowdStrike Falcon | Build API Query"
set_fact:
falcon_os_query: "{{ 'os:\"' + falcon_target_os + '\"+os_version:\"' + falcon_os_version + '\"' }}"
- name: CrowdStrike Falcon | Get list of filtered Falcon sensors
uri:
url: "https://{{ falcon_cloud }}/sensors/combined/installers/v1?filter={{ falcon_os_query | urlencode }}"
method: GET
return_content: true
headers:
authorization: "Bearer {{ falcon_api_oauth2_token.json.access_token }}"
Content-Type: application/json
register: falcon_api_installer_list
- name: Show Fact
debug:
var: falcon_api_installer_list
- name: CrowdStrike Falcon | Filter to installers for OS major version
set_fact:
falcon_api_sha_hash: "{{ falcon_api_installer_list.json.resources | selectattr('os_version', 'equalto', ansible_distribution_major_version ) }}"
- name: Show Fact
debug:
var: falcon_api_installer_list.json.resources
- name: CrowdStrike Falcon | Download Falcon Sensor Installation Package
ansible.builtin.get_url:
url: "https://{{ falcon_cloud }}/sensors/entities/download-installer/v1?id={{ falcon_api_installer_list.json.resources[falcon_sensor_version_decrement | int].sha256 }}"
dest: "{{ falcon_install_temp_directory.path }}"
checksum: "sha256:{{ falcon_api_installer_list.json.resources[falcon_sensor_version_decrement | int].sha256 }}"
mode: 0640
headers:
authorization: "Bearer {{ falcon_api_oauth2_token.json.access_token }}"
changed_when: false
register: falcon_sensor_download
##no_log: "{{ falcon_api_enable_no_log }}"
Content of falcon_api_installer_list as per request from #U880D
TASK [Show Fact falcon_api_installer_list] *****************************************************************************************************************ok: [localhost] => {
"falcon_api_installer_list": {
"changed": false,
"connection": "close",
"content": "{\n \"meta\": {\n \"query_time\": 0.096845979,\n \"powered_by\": \"binserv\",\n \"trace_id\": \".......\"\n },\n \"errors\": [],\n \"resources\": []\n}",
"content_length": "159",
"content_type": "application/json",
"cookies": {},
"cookies_string": "",
"date": "Fri, 23 Sep 2022 02:56:28 GMT",
"elapsed": 0,
"failed": false,
"json": {
"errors": [],
"meta": {
"powered_by": "binserv",
"query_time": 0.096845979,
"trace_id": "........"
},
"resources": []
},
"msg": "OK (159 bytes)",
"redirected": false,
"server": "nginx",
"status": 200,
"strict_transport_security": "max-age=15724800; includeSubDomains, max-age=31536000; includeSubDomains",
"url": "https://api.us-2.crowdstrike.com/sensors/combined/installers/v1?filter=os%3A%22CentOS%22%2Bos_version%3A%227%22",
"x_cs_region": "us-2",
"x_cs_traceid": ".........",
"x_ratelimit_limit": "6000",
"x_ratelimit_remaining": "5999"
}
}

Ansible Strange conditional behavior

I'm facing a strange issue while trying to search inside a dictionary. In the code above, i'm trying to define a new var "OLD_VLANID_PP", with the value of the "name" key of the dictionary "VM_NETWORK_INFOS_PP".
If the dictionary is set and not empty, I want the var to be defined, otherwise just skip the task.
In order to test if the condition was true or false, I've added a debug task which should display the content of the dictionary.
When running my playbook, the debug task does indicates that the condition is not matched, and so doesn't display my dictionary, but Ansible keep trying to define my new var as shown in the output, and so fires an error "'item' is undefined"
---
- block:
#Search VCENTER for PPROD VM (If Windows to create => Search PG / EIther search Postgre)
- name: Search for PPROD VM already created for the CLIENT {{ CLIENTNAME }} to retrieve VLANID
set_fact:
OLD_VMNAME_PP: "{{ item.guest_name }}"
with_items: "{{ VM_EXIST_PP.virtual_machines }}"
when: item.guest_name == VMNAME_PP
delegate_to: localhost
when: ENV != "PROD" and DEPLOY != "ALL" and (VM_EXIST_PP is defined) and (VM_EXIST_PP|length > 0)
#If we successfully found the previous VM for the client, then get its network info
- name: Retrieve PPROD VM {{ VMNAME_PP }} VLAN ID
community.vmware.vmware_guest_network:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
datacenter: "{{ datacenter_name }}"
validate_certs: False
name: "{{ OLD_VMNAME_PP }}"
gather_network_info: true
register: VM_NETWORK_INFOS_PP
when: (OLD_VMNAME_PP is defined) and OLD_VMNAME_PP != ""
- block:
- debug: msg={{VM_NETWORK_INFOS_PP}}
#If we successfully found the network info, set the OLD_VLANID with previous VM VLANID
- set_fact:
OLD_VLANID_PP: "{{ (item.name) | replace('(HDS : Client)','') | replace('VLAN0','') | replace('VLAN','') | replace(' ','') }}"
with_items:
- "{{ VM_NETWORK_INFOS_PP.network_info }}"
when: item.name | regex_search('VLAN') and not item.name | regex_search('PVLAN')
when: (VM_NETWORK_INFOS_PP is defined) and VM_NETWORK_INFOS_PP != ""
Debug Output (Should display the dictionary content) :
ok: [127.0.0.1] => {
"msg": {
"changed": false,
"skip_reason": "Conditional result was False",
"skipped": true
}
}
Error Output :
The error was: error while evaluating conditional (item.name | regex_search('VLAN') and not item.name | regex_search('PVLAN')): 'item' is undefined
Ansible Version : 2.10.7
Python Version : 3.7.3
Any help would be appreciated.
Thank you
Not sure if it's the right thing to do, but finally get ride of this issue using :
when: item is defined and item.name | regex_search('VLAN') and not item.name | regex_search('PVLAN')
note the item is defined before the regex search

how to use json_query filter to extract all items equals to a value

Here is my json output:
{
"kind": [
{
"inventory": "",
"inventory_sources": "",
"job_templates": "",
"workflow_job_templates": "104"
},
{
"inventory": "",
"inventory_sources": "",
"job_templates": "114",
"workflow_job_templates": ""
},
{
"inventory": "24",
"inventory_sources": "",
"job_templates": "",
"workflow_job_templates": ""
},
{
"inventory": "",
"inventory_sources": "108",
"job_templates": "",
"workflow_job_templates": ""
}
]
}
I'd like to display all items name that contain a specific value. For example, for a search value of 104 I want to get the key name workflow_job_templates
I tested some syntaxes without any success:
- debug:
msg: "104 is {{kind|json_query(query)}}"
vars:
query: "[?*==`104`].workflow_job_templates"
I know it is wrong but can someone tell me how he'd do for himself?
json_query could be part of the equation for your solution but is really not needed here.
Explanation of the below piece of code:
Apply the dict2items filter to each element of your list. This transforms each mapping to a list of {key: "key", value: "value"} pairs
Flatten the given list so we get all those elements to a single top level
Select elements having a value of '104' only
Extract the key attribute of each element in a list
Make that list unique and sort it.
- name: Display all element having a value of 104
debug:
msg: "{{ kind | map('dict2items') | flatten
| selectattr('value', '==', '104')
| map(attribute='key') | unique | sort }}"
Please note that this solution will give you a result if the same key name has different values but one of them is `104. With your above data the result is:
TASK [Display all element having a value of 104] ***************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"workflow_job_templates"
]
}
(Update)
The task below
- debug:
msg: "{{ item }} {{ kind|
map('dict2items')|
map('json_query', query)|
flatten }}"
loop: [104, 114, 108, 24]
vars:
query: "[?to_string(value) == to_string('{{ item }}')].key"
gives
msg: 104 ['workflow_job_templates']
msg: 114 ['job_templates']
msg: 108 ['inventory_sources']
msg: 24 ['inventory']
(For the record. Brute-force approach)
Create a unique list of the keys
- set_fact:
my_keys: "{{ my_keys|default([]) + item.keys()|list }}"
loop: "{{ kind }}"
- set_fact:
my_keys: "{{ my_keys|unique }}"
gives
my_keys:
- inventory
- inventory_sources
- job_templates
- workflow_job_templates
Create a dictionary with all values
- set_fact:
my_dict: "{{ my_dict|default({})|combine({item: values}) }}"
loop: "{{ my_keys }}"
vars:
query: "[].{{ item }}"
values: "{{ kind|json_query(query) }}"
gives
my_dict:
inventory:
- ''
- ''
- '24'
- ''
inventory_sources:
- ''
- ''
- ''
- '108'
job_templates:
- ''
- '114'
- ''
- ''
workflow_job_templates:
- '104'
- ''
- ''
- ''
Then search the dictionary. For example
- debug:
msg: "{{ item }} {{ my_dict|dict2items|json_query(query) }}"
loop: [104, 114, 108, 24]
vars:
query: "[?value[?contains(#, '{{ item }}')]].key"
gives
msg: 104 ['workflow_job_templates']
msg: 114 ['job_templates']
msg: 108 ['inventory_sources']
msg: 24 ['inventory']
The correct alternative of selectattr with json_query is:
- debug:
msg: "{{ kind | map('dict2items') | flatten | json_query(query)}}"
vars:
- query: "[?value == `\"104\"`].key"
If you really just want to use json_query()
---
- name: PLAYBOOK Filtering
hosts: localhost # run locally
tasks:
- name: Create json
set_fact:
kind: '{{ lookup("file", "kind.json") }}'
- name: check the var was created properly
debug:
var: kind
- name: output the element that matches 104
debug:
msg: "{{ kind | json_query(\"kind[?workflow_job_templates=='104'].workflow_job_templates\") }}"
- name:
set_fact:
output: "{{ kind | json_query(\"kind[?workflow_job_templates=='104'].workflow_job_templates\") }}"
Output
TASK [output the element that matches 104] *************************************************************************************************************
ok: [localhost] => {
"msg": [
"104"
]
}

How to fix "Undefind error" while parsing a dictionary in ansible registered variable?

I'm creating some ec2 instances from a specific image, then trying to get a list of disks attached to these instances.
The problem is when I try to loop over the registered variable from the create instance task, I got an error
I have tried the solution from this post but with no luck
ansible get aws ebs volume id which already exist
- name: create instance
ec2:
region: us-east-1
key_name: xxxxxxx
group: xxxxxx
instance_type: "{{ instance_type }}"
image: "{{ instance_ami }}"
wait: yes
wait_timeout: 500
instance_tags:
Name: "{{ item.name }}"
vpc_subnet_id: "{{ item.subnet }}"
register: ec2
loop: "{{ nodes }}"
- name: show attached volumes Ids
debug:
msg: "{{ item.block_device_mapping | map(attribute='volume_id') }}"
loop: "{{ ec2.results[0].instances }}"
while printing only msg: "{{ item.block_device_mapping }}" I get:
"msg": {
"/dev/sda1": {
"delete_on_termination": true,
"status": "attached",
"volume_id": "vol-xxxxxxx"
},
"/dev/xvdb": {
"delete_on_termination": false,
"status": "attached",
"volume_id": "vol-xxxxxx"
},
"/dev/xvdc": {
"delete_on_termination": false,
"status": "attached",
"volume_id": "vol-xxxxxx"
}
}
but when I use
msg: "{{ item.block_device_mapping | map(attribute='volume_id') }}"
I get this error:
"msg": "[AnsibleUndefined, AnsibleUndefined, AnsibleUndefined]"
The task below
- debug:
msg: "{{ item }}: {{ block_device_mapping[item].volume_id }}"
loop: "{{ block_device_mapping.keys() }}"
gives the {device: volume_id} tuples (grep msg):
"msg": "/dev/xvdb: vol-xxxxxx"
"msg": "/dev/xvdc: vol-xxxxxx"
"msg": "/dev/sda1: vol-xxxxxxx"
To iterate instances use json_query. The task below
- debug:
msg: "{{ item.block_device_mapping|json_query('*.volume_id') }}"
loop: "{{ ec2.results[0].instances }}"
gives:
"msg": [
"vol-xxxxxx",
"vol-xxxxxx",
"vol-xxxxxxx"
]
and the task below with zip
- debug:
msg: "{{ item.block_device_mapping.keys()|zip(
item.block_device_mapping|json_query('*.volume_id'))|list }}"
loop: "{{ ec2.results[0].instances }}"
gives the list of lists:
"msg": [
[
"/dev/xvdb",
"vol-xxxxxx"
],
[
"/dev/xvdc",
"vol-xxxxxx"
],
[
"/dev/sda1",
"vol-xxxxxxx"
]
]
and the task below with dict
- debug:
msg: "{{ dict (item.block_device_mapping.keys()|zip(
item.block_device_mapping|json_query('*.volume_id'))) }}"
loop: "{{ ec2.results[0].instances }}"
gives the tuples
"msg": {
"/dev/sda1": "vol-xxxxxxx",
"/dev/xvdb": "vol-xxxxxx",
"/dev/xvdc": "vol-xxxxxx"
}
The mistake:
So the main mistake you made was thinking of item.block_device_mapping as if it was the map you wanted to work with instead of a map within a map. That is, the keys that you have to first find would, according to the msg that you printed /dev/sda, /dev/xvdb and /dev/xvdc.
So first you'd have to make an array with the keys of the parent map. In the question you can see the necessary code to make Jinja get you the necessary strings:
# The necessary filter to get that array should be something along these lines
item['block_device_mapping'] | list() | join(', ')
You should register that to then loop over,giving you the keys you need to access those elements' attributes.

Resources