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
Related
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 Have 2 dictionary:
- Test1:
1: pass
2: fail
3: pass
- Test2:
1.1.1.1: val1
2.2.2.2: val2
3.3.3.3: val3
Condition is when Test1.value contians fail
- name: test
debug:
msg: "{{item.1.value}} {{item.1.key}} {{item.0.key}} {{item.0.value}}"
with_together:
- "{{Test1}}"
- "{{Test2}}"
when: item.0.value == "fail"
This is not working as expected unable to get both key and value of 2 dict in one loop
In when statement you must to use item.0 or item.1 to evaluate the condition. And I recommend you use a list in with_together loop and if you are using a variable you have to use braces {{ variable }} .
Try as below:
- name: test
debug:
msg: "{{item.1 }}"
with_together:
- "{{ Test1.values() | list }}"
- "{{ Test2.values() | list }}"
when: item.0 == "fail"
You'll get
TASK [test] *******************************************************************************************************************************************************************************************************
skipping: [127.0.0.1] => (item=['pass', 'val1'])
ok: [127.0.0.1] => (item=['fail', 'val2']) => {
"msg": "val2"
}
skipping: [127.0.0.1] => (item=['pass', 'val3'])
I achieved this by :
converting dict to list using filter -> |list
since
both dict of same size I was able to get data of both dict in single loop:
- name: test
debug:
msg: "{{item.0}} {{item.1}} {{item.2}} {{item.3}}"
with_together:
- "{{ Test1.values() | list }}"
- "{{ Test2.values() | list }}"
- "{{ Test1.keys() | list }}"
- "{{ Test2.keys() | list }}"
when: item.0 == "fail"
I do have a simple json file, where i need to pull a set of value from EACH array item, but during iteration it fails.
My playbook looks like:
code:
---
- name: direct - this works like charm
set_fact:
bb: "{{ pr_json.json.issues[0].fields.customfield_11756.value }}"
- debug:
var: bb
- name: via array - this is not working since iteration is not happening
set_fact:
dd_branch: "{{ pr_json.json.issues[{{ item }}].fields.customfield_11756.value }}"
register: mass
- debug:
var: mass
Getting output as:
TASK [jira_update : direct - this works like charm] ********************************************************************************************************************
task path: /home/test/ansible_jira/roles/jira_update/tasks/call.yml:3
ok: [localhost] => {
"ansible_facts": {
"bb": "R4.19"
},
"changed": false
}
TASK [jira_update : debug] *********************************************************************************************************************************************
task path: /home/test/ansible_jira/roles/jira_update/tasks/call.yml:7
ok: [localhost] => {
"bb": "R4.19"
}
TASK [jira_update : via array - this is not working since iteratoin is not happening] **********************************************************************************
task path: /home/test/ansible_jira/roles/jira_update/tasks/call.yml:10
fatal: [localhost]: FAILED! => {
"msg": "template error while templating string: expected token ':', got '}'. String: {{ pr_json.json.issues[{{ item }}].fields.customfield_11756.value }}"
}
Please do let us know how can I iterate through an array variable value on every sequence.
tried this too, but can somebody help to iterate the array values, please.
- name: Create PR request in TEMS JIRA
jira:
uri: "{{ tems_jira }}"
username: "{{ user }}"
password: "{{ pass }}"
operation: create
project: PR
issuetype: 'PR-Form'
summary: "{{ pr_json.json| json_query('issues[].fields.summary') }}"
description: "{{ pr_json.json | json_query('issues[].fields.description') }}"
args:
fields:
customfield_10303:
value: "{{ pr_json.json | json_query('issues[].fields.customfield_11756.value') }}"
Youy need to feed your list into a with_items iterator. Thats what sets the item variable for looping purposes.
- name: via array - this is not working since iteration is not happening
set_fact:
dd_branch: "{{ pr_json.json.issues[ item ].fields.customfield_11756.value }}"
register: mass
with_items:
- 0
- 1
That will iterate through all of the list items of pr_json.json.issues which will let you dive deeper into the variable structure like you are looking for. There are a lot of other factors that you can feed into the loop that might interest you that you can find detailed here.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
A shell command outputs one array per line on the standard output: [1,2] then [3,4]. I would like to use each element of the array as a parameter to a module and decided to loop over stdout_lines. However, item.0 and item.1 contains the first and second character of the string instead of the first and second element of the array.
Why is item a string instead of an array?
What would be the most elegant way to get what I need?
Here is a reproducer to help understand the question, using ansible 2.7.5. The following play:
---
- name: test
hosts: localhost
become: false
gather_facts: false
tasks:
- name: one array per line
shell: |
echo '[1,2]'
echo '[3,4]'
register: result
- name: each item is an array
debug:
msg: "{{ item }}"
loop: "{{ result.stdout_lines }}"
- name: accessing the first element of an item
debug:
msg: "{{ item.0 }} => {{ item.1 }}"
loop: "{{ result.stdout_lines }}"
The actual output is:
TASK [each item is an array] *****************************************************
ok: [localhost] => (item=[1, 2]) => {
"msg": [
1,
2
]
}
ok: [localhost] => (item=[3, 4]) => {
"msg": [
3,
4
]
}
TASK [accessing the first element of an item] ************************************
ok: [localhost] => (item=[1, 2]) => {
"msg": "[ => 1"
}
ok: [localhost] => (item=[3, 4]) => {
"msg": "[ => 3"
}
but I was expecting:
TASK [each item is an array] *****************************************************
ok: [localhost] => (item=[1, 2]) => {
"msg": [
1,
2
]
}
ok: [localhost] => (item=[3, 4]) => {
"msg": [
3,
4
]
}
TASK [accessing the first element of an item] ************************************
ok: [localhost] => (item=[1, 2]) => {
"msg": "1 => 2"
}
ok: [localhost] => (item=[3, 4]) => {
"msg": "3 => 4"
}
An option would be to convert the list of strings to the list of lists.
- name: convert strings to lists
set_fact:
lists: "{{ lists }} + [ {{ item }} ]"
loop: "{{ result.stdout_lines }}"
- name: accessing the 1st and 2nd element of a list
debug:
msg: "{{ item.0 }} => {{ item.1 }}"
loop: "{{ lists }}"
After discussions with Pilou on #ansible-fr#irc.freenode.net, a simple solution was found.
It is enough to trigger a jinja2 evaluation by assigning another variable with the content of item. It will be converted from string to a structure, if possible. See the test playbook updated with the solution below.
debug:
msg: "{{ array.0 }} => {{ array.1 }}"
vars:
array: "{{ item }}"
loop: "{{ result.stdout_lines }}"
There is no rationale explaining why stdout is converted into a structure when possible but not the elements in stdout_lines. It is how the shell module is implemented. It is possible that it will change in the future. If that's the case, the proposed solution will keep working. Only it would not be necessary.
---
- name: test
hosts: localhost
become: false
gather_facts: false
tasks:
- name: one array per line
shell: |
echo '[1,2]'
echo '[3,4]'
register: result
- name: each item is an array
debug:
msg: "{{ item }}"
loop: "{{ result.stdout_lines }}"
- name: accessing the first element of an item
debug:
msg: "{{ array.0 }} => {{ array.1 }}"
vars:
array: "{{ item }}"
loop: "{{ result.stdout_lines }}"
I am trying to create a simple playbook task traversing over multiple dict items(v1) and create a new list var (list_var) with specific item in it, but it is not working list_var is only showing me one element, can anyone please suggest what am i missing?
if i do this:
set_fact:
list_var: "{{ v1.stdout }}"
with_items: "{{ v1.items }}"
Values in v1 are returned by simple shell output and have values from different hosts like hostname
Probably something list this?
- command: echo "{{ item }}"
register: v1
with_sequence: start=0 end=3
- set_fact:
list_var: "{{ list_var|default([]) }} + [ {{ item }} ]"
loop: "{{ v1.results | json_query('[].stdout') }}"
- debug: var=list_var
Which produces this list
TASK [debug] **********
ok: [localhost] => {
"list_var": [
0,
1,
2,
3
]
}