Ansible loop not execute if same value - ansible

can i know in an Ansible loop is there a way not to execute repeated statement . Below is my code
vars.yml
type_of_fruit:
- "lemon"
- "orange"
- "orange"
- "pineapple"
main.yml
- name: Type of frutis
debug:
msg: "{{ item }}"
loop: "{{ type_of_fruit }}"
Expected
"lemon"
"orange"
"pineapple"

Filter the items, e.q.
loop: "{{ type_of_fruit|unique }}"

Related

Ansible merge dictionaries using with_items and vars stores only last item

Trying to create a dictionary per item and merge them
---
- name: TestingLab
hosts: localhost
gather_facts: False
tasks:
- name: Hello Vars
set_fact:
two_nums:
- 1
- 2
- name: create empty dict
set_fact:
ids: {}
- name: Merge all
vars:
single_entry: "{ '{{ item }}': {{ item }} }"
set_fact:
ids: "{{ ids | combine(single_entry) }}"
with_items: "{{ two_nums }}"
- name: Print Result
debug:
msg: "{{ ids }}"
I thought I followed the right guidelines but I seem to be getting only the last item afterwards:
ok: [localhost] => {
"msg": {
"2": 2
} }
I tried replacing the single_entry with the expression in vars but it does not run.
Is there a different syntax to get this done?
EDIT: version info
ansible-playbook 2.5.1
python version = 2.7.17 [GCC 7.5.0]
Try the filters dict and zip. The zip is available since 2.3, e.g.
- set_fact:
d2: "{{ dict(two_nums|zip(two_nums)) }}"
- debug:
var: d2
- debug:
var: d2|type_debug
gives
d2:
1: 1
2: 2
d2|type_debug: dict
If this does not work try Jinja and the filter from_yaml, e.g.
- hosts: localhost
vars:
two_nums:
- 1
- 2
l1: |-
{% for i in two_nums %}
{{ i }}: {{ i }}
{% endfor %}
tasks:
- set_fact:
d1: "{{ l1|from_yaml }}"
- debug:
var: d1
- debug:
var: d1|type_debug
gives the same result
d1:
1: 1
2: 2
d1|type_debug: dict
If you need the keys to be strings quote it, e.g.
l1: |-
{% for i in two_nums %}
"{{ i }}": {{ i }}
{% endfor %}
gives
d1:
'1': 1
'2': 2
In the first case, map the list's items to string, e.g.
- set_fact:
d2: "{{ dict(two_nums|map('string')|zip(two_nums)) }}"
gives the same result
d2:
'1': 1
'2': 2
I can't reproduce the behavior you're describing. Running your
playbook verbatim, I get as output:
TASK [Print Result] **************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"1": 1,
"2": 2
}
}
I'm using Ansible core 2.11.2, but I've also tested your playbook with Ansible 2.9.20 and I get the same output.
I would probably drop the set_fact task, and also change how you're
setting single_entry:
- name: TestingLab
hosts: localhost
gather_facts: False
tasks:
- name: Hello Vars
set_fact:
two_nums:
- 1
- 2
- name: Merge all
vars:
ids: {}
single_entry: "{{ {item: item} }}"
set_fact:
ids: "{{ ids | combine(single_entry) }}"
with_items: "{{ two_nums }}"
- name: Print Result
debug:
msg: "{{ ids }}"
In this version, the template expression is returning a dictionary,
and only requires a single set of Jinja template markers. I'm curious
if this version behaves any differently for you.

Ansible How do i sort an array in descending order upon the element substring

Below is my array:
- set_fact:
diskout:
- 85_20.198.65.132
- 86_52.140.118.141
- 84_20.198.75.31
- 82_20.204.75.114
- 83_20.204.24.160
I wish to sort this in descending order upon just the first substring separated by _ while ignoring whatever is after the underscore.
Thus, my expected output is:
- 86_52.140.118.141
- 85_20.198.65.132
- 84_20.198.75.31
- 83_20.204.24.160
- 82_20.204.75.114
I tried the below but it did not give me the desired output:
- debug:
msg: "The automation will run on {{ item }}"
with_items: "{{ diskout | reverse | list }}"
Can you please suggest?
Create index, e.g.
- debug:
msg: "{{ _dict|dict2items|
sort(attribute='key', reverse=true)|
map(attribute='value')|
list }}"
vars:
_index: "{{ diskout|map('regex_replace', '^(.*)_(.*)$', '\\1')|list }}"
_dict: "{{ dict(_index|zip(diskout)) }}"
gives
msg:
- 86_52.140.118.141
- 85_20.198.65.132
- 84_20.198.75.31
- 83_20.204.24.160
- 82_20.204.75.114
The next option might be faster
- debug:
msg: "{{ _dict|sort(reverse=true)|map('extract', _dict)|list }}"
vars:
_index: "{{ diskout|map('regex_replace', '^(.*)_(.*)$', '\\1')|list }}"
_dict: "{{ dict(_index|zip(diskout)) }}"

jinja2 turning lists into strings even when from_json is being used

I am trying to run a nested for loop in order to retrieve a nested value.
I would like to retrieve some_value_4 when some_value_3 matches a criteria that's predefined.
{
"some_dict": {
"some_key_0": "value_0",
"some_key_1": "value_1"
},
"testval_dict": {
"test_key_0": "some_value_0",
"test_key_1": "some_value_1",
"test_key_2": "some_value_2",
"test_key_3": "some_value_3",
"test_key_4": "some_value_4"
}
}
The playbook:
- hosts: localhost
tasks:
- set_fact:
mydict: "{{ lookup('file', '/tmp/file.json' ) | from_json }}"
- debug:
msg: |
"{% for item in mydict %}
{{ item }}
{% endfor %}"
when run, it alreay looks like dict names are treated as string and nothing more:
ansible-playbook /tmp/test_playbook.yml -c local -i ', localhost'
TASK [debug] ******************************************************************
ok: [localhost] => {}
MSG:
" somee_dict
testval_dict
"
Then when I add an itme.key to the debug task, the playbook fails:
MSG:
The task includes an option with an undefined variable. The error was: 'str object' has no attribute 'value'
Thank you.
edit for clarification
In the real example, I will not know the names of the dicts, so I cannot use some_dict or testval_dict, that is why I'm trying to go over this data source in an item.key or item.value form.
Q: "{% for item in mydict %} ... dict names are treated as string and nothing more."
A: This is correct. A dictionary, when evaluated as a list, returns the list of its keys. See some examples below
- debug:
msg: "{{ mydict.keys()|list }}"
- debug:
msg: "{{ mydict[item] }}"
loop: "{{ mydict.keys()|list }}"
- debug:
msg: "{{ mydict|difference(['testval_dict']) }}"
give
msg:
- some_dict
- testval_dict
msg:
some_key_0: value_0
some_key_1: value_1
msg:
test_key_0: some_value_0
test_key_1: some_value_1
test_key_2: some_value_2
test_key_3: some_value_3
test_key_4: some_value_4
msg:
- some_dict
See How to iterate through a list of dictionaries in Jinja template?
If you need to loop over the dictionary, you can use with_dict loop functionality. This way, if you loop over mydict and get item.key you will get somee_dict and testval_dict.
tasks:
- set_fact:
mydict: "{{ lookup('file', '/tmp/file.json')|from_json }}"
# This will get the top level dictionaries somee_dict and testval_dict
- debug:
var: item.key
with_dict: "{{ mydict }}"
And if you get item.value of mydict you will get the sub-dictionary:
- debug:
var: item.value
with_dict: "{{ mydict }}"
Will produce (showing output only for testval_dict):
"item.value": {
"test_key_0": "some_value_0",
"test_key_1": "some_value_1",
"test_key_2": "some_value_2",
"test_key_3": "some_value_3",
"test_key_4": "some_value_4"
}

How to loop over a nested dictionary in ansible?

How can I loop over this dictionary?
# nested dictionary to loop over:
vars:
commands:
group1:
cmd1:
run: foo
cmd2:
run: bar
group2:
cmd3:
run: zoo
# expected loop:
- group1, cmd1={...}
- group1, cmd2={...}
- group2, cmd4={...}
I tried using various combinations of dict2items and with_nested but so far I was not able to achieve that goal, and I would really want to avoid using loops with include tasks or writing a python module that does the flattening.
I still hope there is a pure-ansible way of doing it.
It's possible to loop tasks included by included_tasks. For example the file
shell> cat inner-loop.yml
- debug:
msg: "{{ outer_item.key }}, {{ item.key }}={{ item.value }}"
loop: "{{ outer_item.value|dict2items }}"
included from the task
- include_tasks: inner-loop.yml
loop: "{{ commands|dict2items }}"
loop_control:
loop_var: outer_item
gives
"msg": "group1, cmd1={u'run': u'foo'}"
"msg": "group1, cmd2={u'run': u'bar'}"
"msg": "group2, cmd3={u'run': u'zoo'}"

how can i loop over a variable that might have single value?

I'm writing a playbook and want to loop a role over a variable that gets its value from the user. however that value might not always be a list of items, it might be a single value and whenever that happens it throws an error.
My Task:
- name: task name
include role:
name: role name
vars:
cluster_name: '{{ item }}'
loop: "{{ list_or_not }}"
loop_control:
loop_var: item
error:
...Invalid data passed to 'loop', it requires a list...
Have you tried the: "| list" filter?
Sorry cannot test at the moment.
You could test if the variable is a string, and if so, transform it into a single-item list. Something like this:
---
- hosts: localhost
gather_facts: false
tasks:
- set_fact:
list_or_not: ["{{ list_or_not }}"]
when: list_or_not is string
- debug:
msg: "{{ item }}"
loop: "{{ list_or_not }}"

Resources