Below ansible tasks display pip packages in dictionary format.
- name: Ganther pip packages
pip_package_info:
clients: pip3
register: pip_pkgs
And the output is:
{
"packages": {
pip3: {
"xxx": [
{
name: "xxx"
source: "pip3"
version: "1.0.0"
}
],
"yyy": [
{
name: "yyy"
source: "pip3"
version: "2.0.0"
}
]
}
}
}
I tried to loop through the registered variable, but, I am getting errors about undefined variable. How to get the name and version from the above dictionary?
You can use the values method of the Python dictionary in order to have something that you could loop on:
- debug:
msg: "{{ item.name }} is in version {{ item.version }}"
loop: "{{ pip_pkgs.packages.pip3.values() | flatten }}"
Alternatively, it can also be achieved with dict2items, but makes the loop syntax a little bit longer, since you then have to map the attribute you are interested in — here, the value:
- debug:
msg: "{{ item.name }} is in version {{ item.version }}"
loop: >-
{{
pip_pkgs.packages.pip3
| dict2items
| map(attribute='value')
| flatten
}}
If you want to create a dictionary, this would be the easiest:
- set_fact:
pip_packages: >-
{{
dict(
_pip_pkg | map(attribute='name')
| zip(_pip_pkg | map(attribute='version'))
)
}}
vars:
_pip_pkg: "{{ pip_pkgs.packages.pip3.values() | flatten }}"
The dictionary would look like
pip_packages:
ansible: 5.6.0
ansible-compat: 2.0.2
ansible-core: 2.12.4
ansible-lint: 6.0.2
Given the two tasks:
- pip_package_info:
clients: pip3
register: pip_pkgs
- debug:
msg: "{{ item.name }} is in version {{ item.version }}"
loop: "{{ pip_pkgs.packages.pip3.values() | flatten }}"
loop_control:
label: "{{ item.name }}"
This gives:
ok: [localhost] => (item=ansible) =>
msg: ansible is in version 5.6.0
ok: [localhost] => (item=ansible-compat) =>
msg: ansible-compat is in version 2.0.2
ok: [localhost] => (item=ansible-core) =>
msg: ansible-core is in version 2.12.5
ok: [localhost] => (item=ansible-lint) =>
msg: ansible-lint is in version 6.0.2
... list goes on
Related
Let me introduce my problem. I have some list of dictionary in my Ansible code:
my_example_list = [
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
I need execute command which will iterate over this list and it should look something like:
- name: 'Example'
shell: 'Here is my {{ item.key }} and here is {{ item.value }}'
What I've do or try to do:
I was trying to do that with with_items but i'm not able to point into value of particular key.
I've also try to filter values using | first and | last but it's not worked in my case.
What I want to achieve:
Creating loop which will iterate via that list and inject separated key and value into command.
I was asked to show how I was trying to resolve my issue:
Here is some code:
# Showing last component failing
- name: "Try to show last component of my list"
debug:
msg: "{{ my_example_list[1] | last }}"
# When i'm trying to show first component of my list i get "key1"
- name: "Try to show first component of my list"
debug:
msg: "{{ my_example_list[1] | first }}"
# This shows me my list of dict
- name: "Trying use with_items"
debug:
msg: "{{ item }}"
with_items: "{{ my_example_list }}"
# But when i'm trying point to key and value for example
- name: "Trying use with_items point to key and value"
debug:
msg: "Here is my {{ item.key }} which keep {{ item.value }}"
with_items: "{{ my_example_list }}"
# It's failing.
Sorry it's not maybe solution with using loop. I'm just stack with that issue over few days... And as first step I want to know how correctly point to pair keys and values.
It also works well:
- name: Correct solution
debug:
msg: "This is my {{ item.key }} and my value {{ item.value }}"
with_dict: "{{ my_example_list }}"
Thanks #U880D for help! I'm not able to add some plus for your solution because I'm new joiner. Appreciate your answer! :)
Your data structure and naming seems to be unfavorable. There is no need to number the key name and therefore it should be avoided. Furthermore counting list elements in Python starts at 0 not 1.
The following minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
example_list: |
[
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
tasks:
- name: Example loop
debug:
msg: "{{ item }} is of type {{ item | type_debug }}"
loop: "{{ example_list }}"
- name: Example loop
debug:
msg: "{{ item.values() }}"
loop: "{{ example_list }}"
will result into an output of
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg: '{u''key1'': u''value_of_first_key''} is of type dict'
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg: '{u''key2'': u''value_of_second_key''} is of type dict'
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg: '{u''key3'': u''value_of_third_key''} is of type dict'
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg:
- value_of_first_key
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg:
- value_of_second_key
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg:
- value_of_third_key
Further Readings
How to work with lists and dictionaries in Ansible
Extended loop variables
This has got to be a simple question but I can't seem to find the answer anywhere.
I have the following list:
yum_repo_ip_addrs: ['172.16.130.4', '172.16.130.1']
I want to dynamically create a list called yum_baseurls where I copy in these values into the list along with the rest of the url. The list should ultimately look like this when run successfully:
yum_baseurls:
- "http://172.16.130.4/repos/elrepo-8-x86_64"
- "http://172.16.130.1/repos/elrepo-8-x86_64"
Instead, I'm finding that after the first iteration of my loop it's pasting in the variables literally.
Here's my playbook:
---
- name: Print the list of baseurl IP addresses.
debug:
msg: "{{ yum_repo_ip_addrs }}"
- name: Create the list of baseurls.
set_fact:
yum_baseurls: "{{ yum_baseurls + ['http://{{ item }}/repos/elrepo-{{ ansible_distribution_major_version }}-{{ ansible_userspace_architecture }}'] }}"
with_items:
- "{{ yum_repo_ip_addrs }}"
- name: print the list of baseurls.
debug:
msg: "{{ yum_baseurls }}"
And here's the output I get when I run it:
TASK [yum : Print the list of baseurl IP addresses.] ***************************************************************************************
ok: [ansibletarget3.jnk.sys] => {
"msg": [
"172.16.130.4",
"172.16.130.1"
]
}
TASK [yum : Create the list of baseurls.] **************************************************************************************************
ok: [ansibletarget3.jnk.sys] => (item=172.16.130.4)
ok: [ansibletarget3.jnk.sys] => (item=172.16.130.1)
TASK [yum : print the list of baseurls.] ***************************************************************************************************
ok: [ansibletarget3.jnk.sys] => {
"msg": [
"http://172.16.130.1/repos/elrepo-8-x86_64",
"http://{{ item }}/repos/elrepo-{{ ansible_distribution_major_version }}-{{ ansible_userspace_architecture }}"
]
}
Is there a better way to generate my list?
I'd remove it from the code and put it somewhere into the vars, e.g.
yum_repo_ip_addrs: [172.16.130.4, 172.16.130.1]
version: 8
architecture: x86_64
yum_baseurls_str: |
{% for ip in yum_repo_ip_addrs %}
- http://{{ ip }}/repos/elrepo-{{ version }}-{{ architecture }}
{% endfor %}
yum_baseurls: "{{ yum_baseurls_str|from_yaml }}"
I've got dict with array like this
tests:
test01:
state: 'enabled'
objects:
- 'A111'
- 'B111'
test02:
state: 'enabled'
objects:
- 'C222'
- 'D222'
test03:
state: 'enabled'
objects:
- 'E333'
- 'F333'
How to combine array "objects" together in one output? The result should be
"msg": "A111,B111,E333,F333,C222,D222"
Is this what you need
- debug:
msg: "{{(tests.test01.objects,tests.test02.objects,tests.test03.objects)|flatten|join('\n')|replace('\n', ',')}}"
ok: [localhost] => {
"msg": "A111,B111,C222,D222,E333,F333"
}
Here is multiliner but without hardcoding of items
- set_fact:
tests_dict: "{{ item }}"
with_dict: "{{ tests }}"
register: tests_items
- set_fact:
tests_objects: "{{ tests_objects }} + {{ item.item.value.objects }}"
with_items: "{{ tests_items.results }}"
vars:
tests_objects: []
- debug:
msg: "{{ tests_objects | join(',') }}"
ok: [127.0.0.1] => {
"msg": "C222,D222,E333,F333,A111,B111" }
Sorry if there are many posts about variables inside variable my use case is different.
Trying to access an element from a variable list "efs_list" based on the index-number of the current host. There are three hosts in the inventory
vars:
efs_list:
- efs1
- efs2
- efs3
sdb_index: "{{ groups['all'].index(inventory_hostname) }}"
The values should be as follows
host1- efs1
host2- efs2
host3- efs3
Tried accessing it through efs_list.{{ sdb_index }}
for - debug: var=efs_list.{{ sdb_index }} the output is as intended
ok: [10.251.0.174] => {
"efs_list.0": "efs1"
}
ok: [10.251.0.207] => {
"efs_list.1": "efs2"
}
ok: [10.251.0.151] => {
"efs_list.2": "efs3"
}
But for
- debug:
msg: "{{ efs_list.{{ sdb_index }} }}"
fatal: [10.251.0.174]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ efs_list.{{ sdb_index }} }}"}
---
- name: SDB Snapshots Creation
hosts: all
remote_user: "centos"
become: yes
vars:
efs_list:
- efs1
- efs2
- efs3
sdb_index: "{{ groups['all'].index(inventory_hostname) }}"
tasks:
- debug: var=efs_list.{{ sdb_index }}
- debug:
msg: "{{ efs_list.{{ sdb_index }} }}"
- name: Get Filesystem ID
become: false
local_action: command aws efs describe-file-systems --creation-token "{{ efs_list.{{ sdb_index }} }}"
--region us-east-1 --query FileSystems[*].FileSystemId --output text
register: fs_id
It should attribute the element of list to current indexenter code here
extract filter will do the job. The input of the filter must be a list of indices and a container (array in this case). The tasks below
- set_fact:
sdb_index: "{{ [] + [ groups['all'].index(inventory_hostname) ] }}"
- debug:
msg: "{{ sdb_index|map('extract', efs_list)|list }}"
give
ok: [host1] => {
"msg": [
"efs1"
]
}
ok: [host2] => {
"msg": [
"efs2"
]
}
ok: [host3] => {
"msg": [
"efs3"
]
}
If the hosts are not sorted in the inventory it's necessary to sort them in the play
- set_fact:
my_hosts: "{{ groups['all']|sort }}"
- set_fact:
sdb_index: "{{ [] + [ my_hosts.index(inventory_hostname) ] }}"
- debug:
msg: "{{ sdb_index|map('extract', efs_list)|list }}"
I have this list:
"mylist": [
{ "files": [{"path": "path11"}, {"path": "path12"}] },
{ "files": [{"path": "path21"}, {"path": "path22"}] }
]
and I need to run role with with_items, where items should be path elements from my list.
For example:
- debug: msg="{{ item }}"
with_items: "{{ mylist | some_magic }}"
Needed output:
TASK [test : debug] **********************************
ok: [host] => (item=path11 ) => {
"msg": "path11"
}
ok: [host] => (item=path12 ) => {
"msg": "path12"
}
ok: [host] => (item=path21 ) => {
"msg": "path21"
}
...
Is it possible?
This is what I have already tried:
Constructions look like this:
- debug: msg="{{ item }}"
with_items: "{% for files in mylist | map(attribute='files') %}{% for file in files %}{{file.path}}{% endfor %}{% endfor %}"
Returned value as expected is not a list.
Wrong constructions look like this:
- debug: msg="{{ item }}"
with_items: "{{ mylist | map(attribute='files') | map(attribute='path') | list }}"
It is a legacy with_subelements loop pattern.
Using the loop keyword (Ansible 2.5 and later) you can iterate with:
- debug:
msg: "{{ item.1.path }}"
loop: "{{ mylist | subelements('files') }}"
Or the same using JMESPath (this shows how to create a list out of the whole data structure):
- debug:
msg: "{{ item }}"
loop: "{{ mylist | json_query('[].files[].path') }}"