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
Related
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
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
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
I have the following task.
However, it doesn't output the output for each ping that is produced, and I only get a 1 x output. When there should be x 5.
Task
tasks:
- name: "Check Connectivity (ping)"
nxos_ping:
provider: "{{ nxos_ssh }}"
source: "{{ hostvars[inventory_hostname]['lo0_ipaddr'] }}"
vrf: default
dest: "192.168.1.{{item}}"
with_sequence: start=1 end=5
register: out
- debug:
msg:
- "command: {{ out['results'][0]['commands'][0] }}"
Example
TASK [Check Connectivity (ping)] ***************************************************************************************************
ok: [spine-nxos-1] => (item=1)
ok: [spine-nxos-2] => (item=1)
ok: [spine-nxos-2] => (item=2)
ok: [spine-nxos-1] => (item=2)
ok: [spine-nxos-1] => (item=3)
ok: [spine-nxos-2] => (item=3)
ok: [spine-nxos-1] => (item=4)
ok: [spine-nxos-2] => (item=4)
ok: [spine-nxos-1] => (item=5)
ok: [spine-nxos-2] => (item=5)
TASK [debug] ***********************************************************************************************************************
ok: [spine-nxos-1] => {
"msg": [
"command: ping 192.168.1.1 count 5 source 192.168.1.1 vrf default"
]
}
ok: [spine-nxos-2] => {
"msg": [
"command: ping 192.168.1.1 count 5 source 192.168.1.2 vrf default"
]
}
Your code is working correct, as you only print out the first element of the registered output. If you want to see all your commands you should replace the last line of your playbook with:
- "command {{ out | json_query('results[*].commands[*]') }}"
or loop through your output corresponding to your sequence:
debug:
msg:
- "command {{ out.results[item|int].commands[0]}}"
with_sequence: start=0 end=2
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"
]
]
}