Ansible : The loop variable 'item' is already in use - ansible

I want to run a task in ansible something similar to the following.
#Task in Playbook
- name : Include tasks
block:
- name: call example.yml
include_tasks: "example.yml"
vars:
my_var: item
with_items:
- [1, 2]
# example.yml
- name: Debug.
debug:
msg:
- "my_var: {{ my_var }}"
with_inventory_hostnames:
- 'all'
I expect the output to be printing the my_var as values 1 in the first iteration and 2 in second iteration of the loop in the playbook. But instead, it is printing the hostnames
# Output
TASK [proxysql : Debug.] ************************************************************************************************
[WARNING]: The loop variable 'item' is already in use. You should set the `loop_var` value in the `loop_control` option for the task to something else to avoid variable collisions and unexpected behavior.
ok: [10.1xx.xx.xx] => (item=None) => {
"msg": [
"my_var: 10.134.34.34"
]
}
ok: [10.1xx.xx.xx] => (item=None) => {
"msg": [
"my_var: 10.123.23.23"
]
}
ok: [10.1xx.xx.xx] => (item=None) => {
"msg": [
"my_var: 10.112.12.12"
]
}
TASK [proxysql : Debug.] ************************************************************************************************
[WARNING]: The loop variable 'item' is already in use. You should set the `loop_var` value in the `loop_control` option for the task to something else to avoid variable collisions and unexpected behavior.
ok: [10.1xx.xx.xx] => (item=None) => {
"msg": [
"my_var: 10.134.34.34"
]
}
ok: [10.1xx.xx.xx] => (item=None) => {
"msg": [
"my_var: 10.123.23.23"
]
}
ok: [10.1xx.xx.xx] => (item=None) => {
"msg": [
"my_var: 10.112.12.12"
]
}
Thanks in advance

There are two problems:
In the playbook, tasks are included in a loop that has the loop variable name item and the included task also has a loop and
the default variable name is again item. This is why the warning
messages and to solve that use loop_control.
my_var: item assignment needs to be in format my_var: "{{ item }}" for correct assignment.
After both the corrections, playbook would look like this.
- name : Include tasks
block:
- name: call example.yml
include_tasks: "example.yml"
vars:
my_var: "{{ outer_item }}"
with_items:
- [1, 2]
loop_control:
loop_var: outer_item

Related

Ansible variables

I'm a real Ansible beginner.
Is there any way to reconstruct a variable from another ansible variable?
For example, this playbook :
- hosts: servers
vars:
ex_server1: First
ex_server2: Second
ex_server3: Third
toto: ex_
tasks:
- debug:
msg: "{{ toto+ansible_hostname }}"
It print :
ok: [server2] => {
"msg": "ex_server2"
}
ok: [server3] => {
"msg": "ex_server3"
}
ok: [server1] => {
"msg": "ex_server1"
}
Instead of "First", "Second" and "Third".
Is there a way to print variable content instead of variable name in this situation or in a jinja template ?
Use lookup vars plugin
- debug:
msg: "{{ lookup('vars', toto + ansible_hostname) }}"
gives
TASK [debug] ***********************************************************
ok: [server1] =>
msg: First
ok: [server2] =>
msg: Second
ok: [server3] =>
msg: Third
The details about the plugin are available from the command-line
shell> ansible-doc -t lookup vars

Migration between with_items to loop

I'm trying to migrate some code from with_items to loop.
I have this playbook.yml. Seems they are the same but just changed the with_items to loop
- debug:
msg: "{{ item }}"
loop:
- "{{ lookup('pipe','echo -e \"pro.json\npre.json\ndev.json\"').split('\n') }}"
- debug:
msg: "{{ item }}"
with_items:
- "{{ lookup('pipe','echo -e \"pro.json\npre.json\ndev.json\"').split('\n') }}"
This is the output.
TASK [loop: debug] **********************************************************************************************************************************************************************************
ok: [server] => (item=[u'pro.json', u'pre.json', u'dev.json']) => {
"msg": [
"pro.json",
"pre.json",
"dev.json"
]
}
TASK [with_items : debug] **********************************************************************************************************************************************************************************
Ok: [server] => (item=pro.json) => {
"msg": "pro.json"
}
ok: [server] => (item=pre.json) => {
"msg": "pre.json"
}
ok: [server] => (item=dev.json) => {
"msg": "dev.json"
}
As you see loop keeps the output in a kind of chain and with_items split it in three outputs.
I need change the ouput of loop, to look like the with_items. What I want is create a loop that give me a list, depend on the list this loop will be looping.
The with_items result will loop three or as many as the pipe command does but loop only will one
EDIT:
I have tried the with sequence solution for loop and seems this is posible but I cannot change my code to work like.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html?highlight=loop#with-sequence
TASK [: with_sequence] **************************************************************************************************************************************************************************
ok: [server] => (item=testuser00) => {
"msg": "testuser00"
}
ok: [server] => (item=testuser02) => {
"msg": "testuser02"
}
ok: [server] => (item=testuser04) => {
"msg": "testuser04"
}
TASK [: with_sequence -> loop] ******************************************************************************************************************************************************************
ok: [server] => (item=0) => {
"msg": "testuser00"
}
ok: [server] => (item=2) => {
"msg": "testuser02"
}
ok: [server] => (item=4) => {
"msg": "testuser04"
}
Pass the list directly and not as a listitem containing the list to loop
- debug:
msg: "{{ item }}"
loop: "{{ lookup('pipe','echo -e \"pro.json\npre.json\ndev.json\"').split('\n') }}"
For more infos see https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html

How to conditionally replace text in a list with regex_replace?

I have a variable in my playbook that's derived from a list. In some instances this variable contains a "-" to separate two values. For example,
Numbers:
- 2211
- 2211-2212
When this is the case I would like to replace the "-" with a "_" based on a conditional: If the number is 4 characters long, do this. Else, replace the "-" with a " _ " and do that.
I've already tried to fiddle around with jinja2 ans regex in my playbooks but so far no luck. Here's what I tried,
number: {% if length(item) == 4 %} {{ item | regex_replace("^(.*)$", "Number_\1") | string }} {% else %} {{ item | regex_replace("^(.*)$", "Number_\1") |replace("-", "_") | string }}
The result that I would like to have,
Number is four characters long:
number: Number_2211
Number is more then 4 characters long:
number: Number_2211_2212
Some of the Error messages I have received are,
ERROR! Syntax Error while loading YAML.
did not find expected key
ERROR! Syntax Error while loading YAML.
found character that cannot start any token
Is there a way to achieve this within the Ansible playbook?
Thanks in advance!
It's not really clear how you're trying to use this data. Ansible isn't great at modifying complex data structures in place, but it has lots of way of transforming data when you access it. So for example, this playbook:
---
- hosts: localhost
gather_facts: false
vars:
numbers:
- "2211"
- "2211-2212"
tasks:
- debug:
msg: "number: {{ item.replace('-', '_') }}"
loop: "{{ numbers }}"
Will output:
TASK [debug] **********************************************************************************
ok: [localhost] => (item=2211) => {
"msg": "number: 2211"
}
ok: [localhost] => (item=2211-2212) => {
"msg": "number: 2211_2212"
}
If you really need to make the transformation conditional on the length (and it's not clear that you do), you could do something like:
- debug:
msg: "{{ item.replace('-', '_') if item|length > 4 else item }}"
loop: "{{ numbers }}"
Update
I see you've selected the other answer. The solution presented here seems substantially simpler (there is no "incomprehensible sequence of filters, regex expressions, and equality checks"), and produces almost identical output:
TASK [debug] **********************************************************************************
ok: [localhost] => (item=445533) => {
"msg": "445533"
}
ok: [localhost] => (item=112234-538) => {
"msg": "112234_538"
}
ok: [localhost] => (item=11) => {
"msg": "11"
}
ok: [localhost] => (item=1111) => {
"msg": "1111"
}
ok: [localhost] => (item=1111-1111) => {
"msg": "1111_1111"
}
ok: [localhost] => (item=11-11) => {
"msg": "11_11"
}
It's not clear, given 11-11, whether you expect 11_11 or 11-11 as output. If you expect the former, this answer is more correct.
You can use an incomprehensible sequence of filters, regex expressions, and equality checks to do this.
#!/usr/bin/env ansible-playbook
- name: Lets munge some data
hosts: localhost
gather_facts: false
become: false
vars:
array:
- 445533
- 112234-538
- 11
- 1111
- 1111-1111
- 11-11
tasks:
- name: Replace hypens when starting with 4 numbers
debug:
msg: "{{ ((item | string)[0:4] | regex_search('[0-9]{4}') | string != 'None')
| ternary((item | regex_replace('-', '_')), item) }}"
loop: "{{ array }}"
PLAY [Lets munge some data] *****************************************************************************************************************************************************************************************************
TASK [Replace hypens when starting with 4 numbers] ******************************************************************************************************************************************************************************
ok: [localhost] => (item=445533) => {
"msg": "445533"
}
ok: [localhost] => (item=112234-538) => {
"msg": "112234_538"
}
ok: [localhost] => (item=11) => {
"msg": "11"
}
ok: [localhost] => (item=1111) => {
"msg": "1111"
}
ok: [localhost] => (item=1111-1111) => {
"msg": "1111_1111"
}
ok: [localhost] => (item=11-11) => {
"msg": "11-11"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0

What data type does dictsort produce in Ansible/Jinja2?

Explanation
Assuming I have a dictionary mydict set to { "key1": "value1" }:
The result of dictsort filter (mydict|dictsort) in Ansible seems to be a list containing another list:
[
[
"key1",
"value1"
]
]
However, when accessing the first element of this list directly in Jinja2 template (mydict|dictsort)[0], it renders to a strangely looking:
(u'key1', u'value1')
Then, if I set a fact with the value of (mydict|dictsort), it behaves like a regular list - accessing the first element with [0] results in:
[
"key1",
"value1"
]
Accessing its [0] element returns key1.
But if I set a fact with the value of (mydict|dictsort)[0], it behaves like a string - accessing [0] element returns the first character, i.e. (.
On the other hand, if I access subelements directly, for example (mydict|dictsort)[0][0], it behaves like a list, i.e. returns key1.
Questions
What is (u'key1', u'value1')? What kind of object does dictsort produce?
How to access the dictsort results in a consistent, reliable way?
Full playbook:
---
- hosts: localhost
gather_facts: no
connection: local
vars:
mydict:
key1: value1
tasks:
- name: show dict
debug:
msg: "{{ mydict }}"
- name: show mydict|dictsort
debug:
msg: "{{ mydict|dictsort }}"
- set_fact:
mydict_dictsorted: "{{ mydict|dictsort }}"
- name: show (mydict|dictsort)[0]
debug:
msg: "{{ (mydict|dictsort)[0] }}"
- name: show mydict_dictsorted[0]
debug:
msg: "{{ mydict_dictsorted[0] }}"
- name: show (mydict|dictsort|list)[0]
debug:
msg: "{{ (mydict|dictsort|list)[0] }}"
- name: show (mydict_dictsorted|list)[0]
debug:
msg: "{{ (mydict_dictsorted|list)[0] }}"
- set_fact:
mydict_dictsorted_element: "{{ (mydict|dictsort)[0] }}"
- name: mydict_dictsorted_element
debug:
msg: "{{ mydict_dictsorted_element }}"
- name: mydict_dictsorted_element[0]
debug:
msg: "{{ mydict_dictsorted_element[0] }}"
- name: (mydict|dictsort)[0][0]
debug:
msg: "{{ (mydict|dictsort)[0][0] }}"
Full transcript:
PLAY [localhost] ********************************************************************************************
TASK [show dict] ********************************************************************************************
ok: [localhost] => {
"msg": {
"key1": "value1"
}
}
TASK [show mydict|dictsort] *********************************************************************************
ok: [localhost] => {
"msg": [
[
"key1",
"value1"
]
]
}
TASK [set_fact] *********************************************************************************************
ok: [localhost]
TASK [show (mydict|dictsort)[0]] ****************************************************************************
ok: [localhost] => {
"msg": "(u'key1', u'value1')"
}
TASK [show mydict_dictsorted[0]] ****************************************************************************
ok: [localhost] => {
"msg": [
"key1",
"value1"
]
}
TASK [show (mydict|dictsort|list)[0]] ***********************************************************************
ok: [localhost] => {
"msg": "(u'key1', u'value1')"
}
TASK [show (mydict_dictsorted|list)[0]] *********************************************************************
ok: [localhost] => {
"msg": [
"key1",
"value1"
]
}
TASK [set_fact] *********************************************************************************************
ok: [localhost]
TASK [mydict_dictsorted_element] ****************************************************************************
ok: [localhost] => {
"msg": "(u'key1', u'value1')"
}
TASK [mydict_dictsorted_element[0]] *************************************************************************
ok: [localhost] => {
"msg": "("
}
TASK [(mydict|dictsort)[0][0]] ******************************************************************************
ok: [localhost] => {
"msg": "key1"
I checked the values with copy/content and they are the same as debug's (except indentation), so posting debug results for clarity.
dictsort produces a list of tuples. It uses dict.items() under the hood.
So when you access it as (mydict|dictsort)[0], you access Python's tuple.
Whereas if you access it after it is templated, you get generic list, because JSON doesn't make difference between tuples and lists, it has only lists.
Update: how to test – insert print into _dump_results here, like this:
print("Unaltered: {}".format(abridged_result))
return json.dumps(abridged_result, indent=indent, ensure_ascii=False, sort_keys=sort_keys)
And see this as the output:
TASK [show mydict|dictsort] ***************************
Unaltered: {'msg': [(u'key1', u'value1')]}
ok: [localhost] => {
"msg": [
[
"key1",
"value1"
]
]
}
Update2: why list of tuples becomes list of lists, but single tuple becomes string repr?
This is because of the fact that Jinja2 expression inside {{...}} can produce only string as its output, and there's some Ansible template magic done to try to type-cast it back to some complex type. But this magic only works with strings that looks like dicts or lists and not tuples. So if you have dict with tuples inside or list of tuples, you'll get it evaluated, but if you have a single tuple, it will remain a string. Here's demo of this:
- name: results in a string
debug:
msg: "{{ test_str }}"
vars:
test_str: "(u'a', u'b')"
- name: results in a list of tuples/lists
debug:
msg: "{{ test_str }}"
vars:
test_str: "[(u'a', u'b')]"
Output:
TASK [results in a string] ******************************************
ok: [localhost] => {
"msg": "(u'a', u'b')"
}
TASK [results in a list of tuples/lists] ****************************
ok: [localhost] => {
"msg": [
[
"a",
"b"
]
]
}

Checking Existence and Value of Variable

I am building a play that requires the existence of a set of variables and also requires the variables to not equal bar. Below is an example of my definition of extra variables:
ansible.extra_vars = {
A: "foo",
B: "bar",
C: "foo",
...
}
When my play runs, I see the following when I print a debug message for each item:
(item=A) => {
"item": "A",
"var": {
"A": "foo"
}
}
When I try the below evaluation, I would expect a failure on B, however all tests pass:
- fail: msg="bar is not a valid variable value for this play"
with_items: required_vars
when: var.{{ item }} is not defined or (var.{{ item }} is defined and var.{{ item }} == "bar")
Does anyone have any suggestions on what I need to do to evaluate the value and cause a failure when bar is encountered?
extra_vars seems to be a dict, so you should use with_dict instead of with_items.
I'm not sure how exactly you did define extra_vars. When I define it in the playbook like below I get different output from debug. When I define it like that (ansible.extra_vars) in a group_vars file I do not get any data in the playbook at all.
Also, when I use my extra_vars dict together with with_items I get the following error:
fatal: [localhost] => with_items expects a list or a set
So there seems to be something strange about your extra_vars.
Here's my working example:
---
- hosts:
- localhost
gather_facts: no
vars:
extra_vars: {
A: "foo",
B: "bar",
C: "foo"
}
tasks:
- debug: var=extra_vars
- debug: msg="{{ item.key }}"
with_dict: extra_vars
when: item.value is not defined or (item.value is defined and item.value == "bar")
...
Output:
PLAY [localhost] **************************************************************
TASK: [debug var=extra_vars] **************************************************
ok: [localhost] => {
"var": {
"extra_vars": {
"A": "foo",
"B": "bar",
"C": "foo"
}
}
}
TASK: [debug msg="{{ item.key }}"] ********************************************
skipping: [localhost] => (item={'key': 'A', 'value': 'foo'})
skipping: [localhost] => (item={'key': 'C', 'value': 'foo'})
ok: [localhost] => (item={'key': 'B', 'value': 'bar'}) => {
"item": {
"key": "B",
"value": "bar"
},
"msg": "B"
}
PLAY RECAP ********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0

Resources