In Ansible, I have a list of strings that I want to join with newline characters to create a string, that when written to a file, becomes a series of lines. However, when I use the join() filter, it works on the inner list, the characters in the strings, and not on the outer list, the strings themselves. Here's my sample code:
# Usage: ansible-playbook tst3.yaml --limit <GRP>
---
- hosts: all
remote_user: root
tasks:
- name: Create the list
set_fact:
my_item: "{{ item }}"
with_items:
- "One fish"
- "Two fish"
- "Red fish"
- "Blue fish"
register: my_item_result
- name: Extract items and turn into a list
set_fact:
my_list: "{{ my_item_result.results | map(attribute='ansible_facts.my_item') | list }}"
- name: Examine the list
debug:
msg: "{{ my_list }}"
- name: Concatenate the public keys
set_fact:
my_joined_list: "{{ item | join('\n') }}"
with_items:
- "{{ my_list }}"
- name: Examine the joined string
debug:
msg: "{{ my_joined_list }}"
I want to get output that looks like:
One fish
Two fish
Red fish
Blue Fish
What I get instead is:
TASK: [Examine the joined string] *********************************************
ok: [hana-np-11.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
ok: [hana-np-12.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
ok: [hana-np-13.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
ok: [hana-np-14.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
ok: [hana-np-15.cisco.com] => {
"msg": "B\nl\nu\ne\n \nf\ni\ns\nh"
}
How do I properly concatenate a list of strings with the newline character?
Solution
join filter works on lists, so apply it to your list:
- name: Concatenate the public keys
set_fact:
my_joined_list: "{{ my_list | join('\n') }}"
Explanation
While my_list in your example is a list, when you use with_items, in each iterationitem is a string. Strings are treated as lists of characters, thus join splits them.
It’s like in any language: when you have a loop for i in (one, two, three) and refer to i inside the loop, you get only one value for each iteration, not the whole set.
Remarks
Don’t use debug module, but copy with content to have\n rendered as newline.
The way you create a list is pretty cumbersome. All you need is (quotation marks are also not necessary):
- name: Create the list
set_fact:
my_list:
- "One fish"
- "Two fish"
- "Red fish"
- "Blue fish"
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
In the dictionary MyDict, the second dictionary in the nested list has the name key set to an integer value (101). Hence, the lookup in the debug statement is returning false. Is there a way to make the lookup convert the values in the list to strings, so that true will be returned in this example? Thanks!
---
- hosts: localhost
vars:
MyDict:
- name: Bob
- name: 101 <-- This is a value entered by a user, which happens to be an integer
findname: "101" <-- This is a string i wish to find in the dictionary
tasks:
- debug: msg="{{ findname in MyDict | map(attribute='name') | list }}" <-- I want the lookup to return true
Convert all items of the list to strings. For example
- debug:
msg: "{{ findname in MyDict|
map(attribute='name')|
map('string')|
list }}"
Convert also the variable findname to make sure you compare always stings
- debug:
msg: "{{ findname|string in MyDict|
map(attribute='name')|
map('string')|
list }}"
Test it in the loop. For example
- debug:
msg: "{{ item|string in MyList }}"
loop:
- Bob
- 101
- "101"
- Joe
vars:
MyList: "{{ MyDict|
map(attribute='name')|
map('string')|
list }}"
gives
ok: [localhost] => (item=Bob) => {
"msg": true
}
ok: [localhost] => (item=101) => {
"msg": true
}
ok: [localhost] => (item=101) => {
"msg": true
}
ok: [localhost] => (item=Joe) => {
"msg": false
}
How can I print a sorted list excluding its three first items?
Below is my Ansible playbook:
- set_fact:
filesDel: "{{ filesDel|default({})|
combine({item.NameOfFile: findFiles.files|
sort(attribute='mtime', reverse = true)|
map(attribute='path')|
select('search', item.NameOfFile)|
list}) }}"
with_items:
- "{{ fileList }}"
I tried this, but it is not working
- debug:
msg: "{{ item.value[0:-3] }}"
with_dict:
- "{{ filesDel }}"
loop_control:
label: "{{ item.key }}"
When I removed [0:-3], I do get the whole list of data grouped by file name, for instance:
ok: [142.20.10.15] => (item=fileName.png) => {
"msg": [
"/filePathA/fileName.png.25751.2020-08-31#19:30:59~",
"/filePathB/fileName.png.25752.2020-08-31#19:30:59~",
"/filePathB/fileName.png.25751.2020-08-30#22:30:59~",
"/filePathB/fileName.png.2222.2020-08-31#19:30:59~",
"/filePathB/fileName.png.2222.2020-08-31#19:30:59~",
"/filePathA/fileName.png.2222.2020-08-30#22:30:59~"
]
}
When I add [0:-3], I get:
ok: [142.20.10.15] => (item=fileName.png) => {
"msg": [
"/filePathA/fileName.png.25751.2020-08-31#19:30:59~",
"/filePathB/fileName.png.25752.2020-08-31#19:30:59~",
"/filePathB/fileName.png.25751.2020-08-30#22:30:59~"
]
}
This not correct as those files are the first 3 files.
What I want is to exclude those 3 files, so, I should get:
ok: [142.20.10.15] => (item=fileName.png) => {
"msg": [
"/filePathB/fileName.png.2222.2020-08-31#19:30:59~",
"/filePathB/fileName.png.2222.2020-08-31#19:30:59~",
"/filePathA/fileName.png.2222.2020-08-30#22:30:59~"
]
}
What am I doing wrong here?
Basically, the files are sorted based on the time they were created.
The slicing syntax of Python that you can use in Ansible works like this:
[start:end:step]
For reference, see https://docs.python.org/3/library/functions.html#slice
In this syntax, if you use a negative value, the reference is not the beginning of the list but the end.
Still, in this syntax, any element can be left empty, where [:] would actually means from the beginning to the end of the list.
So your actual trial [0:-3] means that you want
the items from the first element of the list (start: 0)
until the end - 3 of the list (end: -3)
If you want all the element except the three first then, you need
the items from the third element of the list (start: 3)
until the end of the list (end: – so it stays empty)
So your correct slice notation is [3:].
Given the task:
- debug:
msg: "{{ filesDel[3:] }}"
vars:
filesDel:
- "/filePathA/fileName.png.25751.2020-08-31#19:30:59~"
- "/filePathB/fileName.png.25752.2020-08-31#19:30:59~"
- "/filePathB/fileName.png.25751.2020-08-30#22:30:59~"
- "/filePathB/fileName.png.2222.2020-08-31#19:30:59~"
- "/filePathB/fileName.png.2222.2020-08-31#19:30:59~"
- "/filePathA/fileName.png.2222.2020-08-30#22:30:59~"
This yields the list:
- /filePathB/fileName.png.2222.2020-08-31#19:30:59~
- /filePathB/fileName.png.2222.2020-08-31#19:30:59~
- /filePathA/fileName.png.2222.2020-08-30#22:30:59~
I have 2 lists as set_fact and want to create a dict
I am running ansible 2.8
I have list1 as below
"inventory_devices": [
"device0",
"device1"
]
and list2 as below
"inventory_ips": [
"10.1.1.1",
"10.1.1.2"
]
I want to get an output shows like
"inventory_dict": [
"device0": "10.1.1.1",
"device1": "10.1.1.2"
]
Thanks.
You can do it entirely with jinja2 using the zip filter built into ansible.
To get a list combining the elements of other lists use zip
- name: give me list combo of two lists
debug:
msg: "{{ [1,2,3,4,5] | zip(['a','b','c','d','e','f']) | list }}"
...
Similarly to the output of the items2dict filter mentioned above, these filters can be
used to contruct a dict:
{{ dict(keys_list | zip(values_list)) }}
The zip filter sequentially combines items from pairs of lists and the dict construct creates a dictionary from a list of pairs.
inventory_dict: "{{ dict(inventory_devices | zip(inventory_ips)) }}"
here is the task to do it, populate combined var in the PB below:
---
- hosts: localhost
gather_facts: false
vars:
inventory_devices:
- device0
- device1
inventory_ips:
- 10.1.1.1
- 10.1.1.2
tasks:
- name: populate combined var
set_fact:
combined_var: "{{ combined_var|default({}) | combine({ item.0: item.1 }) }}"
loop: "{{ query('together', inventory_devices, inventory_ips) }}"
- name: print combined var
debug:
var: combined_var
result:
TASK [print combined var] **********************************************************************************************************************************************************************************************
ok: [localhost] => {
"combined_var": {
"device0": "10.1.1.1",
"device1": "10.1.1.2"
}
}
hope it helps
I want to create an array and insert value from the the array IP_TO_DNS to reversed IP address.
The idea is to restructure the IP address given in the argument to be matchable later in my code.
Code
- name: create array reversed
set_fact: reversed_ip=[]
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')}}'
with_items: '{{IP_TO_DNS}}'
- name: Match first block of results in path name
debug: var=item
with_items: '{{reversed_ip}}'
Output
TASK [dns : set convert ips from cli to matchable reversed ip] *****************
ok: [10.1.10.5] => (item=10.1.10.1)
ok: [10.1.10.5] => (item=10.1.10.2)
ok: [10.1.10.5] => (item=10.1.10.3)
TASK [dns : Match first block of results in path name] *************************
ok: [10.1.10.5] => (item=named.10.1.103) => {
"item": "named.10.1.103"
}
It look like my variable is not set as an array and only the first value is populate.
Any ideas ?
This is the one of the ways which I used
vars:
my_new_list: []
tasks:
- name: Get list of elements from list_vars
set_fact:
my_new_list: "{{ my_new_list + [item] }}"
with_items: "{{ list_vars }}"
You are setting the same fact three times and it gets overwritten.
You should register the output:
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')}}'
with_items: '{{IP_TO_DNS}}'
register: reversed_ip_results_list
- name: Match first block of results in path name
debug: var=item.ansible_facts.reversed_ip
with_items: '{{reversed_ip_results_list.results}}'
or if you want a list:
- debug: msg="{{ reversed_ip_results_list.results | map(attribute='ansible_facts.reversed_ip') | list }}"
You can assign the default of reversed_ip as a list and append the item to the list.
- name: set convert ips from cli to matchable reversed ip
set_fact: reversed_ip='{{ reversed_ip |default([]) + [item | regex_replace('^(?P<first_range>\d{1,3})\.(?P<second_range>\d{1,3})\.(?P<third_range>\d{1,3})\.', 'named.\\g<third_range>.\\g<second_range>.\\g<first_range>')] }}'
with_items: "{{ IP_TO_DNS }}"
- name: Match first block of results in path name
debug: var=item
with_items: '{{reversed_ip}}'