Ansible: Convert two lists into key, value dict - ansible

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

Related

Ansible: how to filter using varible

i have ansible-playbook that gives list of lines in debug output.
I am able to filter debug OUTPUT using a string (exp: CUST) but I am struggling to filter the list using a variable.
- debug:
msg: "{{ List.msg | select('match', '^(CUST)[0-9]+') | list }}"
List msg output:
CUST1
CUST2
NEW1
NEW2
from the above debug command, i get CUST1, CUST2 in filtered output.
- set_fact:
filter: "{{ fileout.results[0].content }}"
above filter generates "CUST" and i want to use this filter variable in above debug command.
using below syntax i get nothing, may be ansible is NOT taking it as appropriate variable.
- debug:
msg: "{{ List.msg | select('match', '^("{{ filter }}")[0-9]+') | list }}"
Please help.
thanks in advance.
It's possible to isolate the declaration of the regex filter and simplify the quotation. For example
vars:
List:
msg: ['CUST1','CUST2','NEW1','NEW2']
Patterns: ['CUST','NEW']
tasks:
- debug:
msg: "{{ List.msg | select('match', filter) | list }}"
vars:
filter: '^{{ item }}[0-9]+'
loop: "{{ Patterns }}"
gives
"msg": [
"CUST1",
"CUST2"
]
"msg": [
"NEW1",
"NEW2"
]

Ansible 'how to traverse over a list` to create a new list

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
]
}

Ansible - Convert list of strings to a list of dictionaires with predefined key

I'm using Ansible to install packages on a new deployment. I have a pre-defined list of dicts in a variable.
I want to open an interface to update this list using Jenkins.
My list looks like this:
package_list: [
{'name': 'python-devel', 'apt': 'python-dev'},
{'name': 'python-pip'},
{'name': 'postgresql-devel'},
...
]
The way I communicate Jenkins input to Ansible is using environment variables. I can pass a list of additional packages to be installed and read it as part of my Ansible configuration.
Question is: How I convert a list of strings, to a list of dictionaries matches the structure of my package_list?
For example:
ENV:
PACKAGES=gcc,vim,ntp
ANSIBLE:
additional_packages = [
{'name': 'gcc'},
{'name': 'vim'},
{'name': 'ntp'}
]
Is it even possible?
i believe this playbook will get you where you want. it assumes you have the env variable: PACKAGES=gcc,vim,ntp
it converts the string variable to a list (split by ,), and then in another loop it converts to a list of dictionaries:
playbook:
- hosts: localhost
gather_facts: false
vars:
tasks:
- name: pick up env variable, convert to list
set_fact:
PACKAGES: "{{ lookup('env', 'PACKAGES').split(',') }}"
- name: create dict list variable
set_fact:
PACKAGES_DICT: "{{ PACKAGES_DICT|default([]) + [{'name': item}] }}"
with_items:
- "{{ PACKAGES }}"
- name: print results
debug:
var: PACKAGES_DICT
results:
TASK [print results] **************************************************************************************************************************************************************************************************
ok: [localhost] => {
"PACKAGES_DICT": [
{
"name": "gcc"
},
{
"name": "vim"
},
{
"name": "ntp"
}
]
}
hope this helps
EDIT
refining the code, removing the set_fact task, declaring the PACKAGES variable in vars section:
- hosts: localhost
gather_facts: false
vars:
PACKAGES: "{{ lookup('env', 'PACKAGES').split(',') }}"
tasks:
- name: create dict list variable
set_fact:
PACKAGES_DICT: "{{ PACKAGES_DICT|default([]) + [{'name': item}] }}"
with_items:
- "{{ PACKAGES }}"
- name: print results
debug:
var: PACKAGES_DICT
Using Ansible, list should be written that way:
package_list:
- name: "gcc"
- name: "vim"
- name: "ntp
so to get that list from the string you can do that way:
vars:
package_list: "{{ packages.split(',').values() | list }}"

Ansible set_fact array and populate it from in loop

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}}'

Ansible - How to keep appending new keys to a dictionary when using set_fact module with with_items?

I want to add keys to a dictionary when using set_fact with with_items. This is a small POC which will help me complete some other work. I have tried to generalize the POC so as to remove all the irrelevant details from it.
When I execute following code it is shows a dictionary with only one key that corresponds to the last item of the with_items. It seems that it is re-creating a new dictionary or may be overriding an existing dictionary for every item in the with_items. I want a single dictionary with all the keys.
Code:
---
- hosts: localhost
connection: local
vars:
some_value: 12345
dict: {}
tasks:
- set_fact: {
dict: "{
{{ item }}: {{ some_value }}
}"
}
with_items:
- 1
- 2
- 3
- debug: msg="{{ dict }}"
This can also be done without resorting to plugins, tested in Ansible 2.2.
---
- hosts: localhost
connection: local
vars:
some_value: 12345
dict: {}
tasks:
- set_fact:
dict: "{{ dict | combine( { item: some_value } ) }}"
with_items:
- 1
- 2
- 3
- debug: msg="{{ dict }}"
Alternatively, this can be written without the complex one-liner with an include file.
tasks:
- include: append_dict.yml
with_items: [1, 2, 3]
append_dict.yml:
- name: "Append dict: define helper variable"
set_fact:
_append_dict: "{ '{{ item }}': {{ some_value }} }"
- name: "Append dict: execute append"
set_fact:
dict: "{{ dict | combine( _append_dict ) }}"
Output:
TASK [debug]
*******************************************************************
ok: [localhost] => {
"msg": {
"1": "12345",
"2": "12345",
"3": "12345"
}
}
Single quotes ' around {{ some_value }} are needed to store string values explicitly.
This syntax can also be used to append from a dict elementwise using with_dict by referring to item.key and item.value.
Manipulations like adding pre- and postfixes or hashes can be performed in the same step, for example
set_fact:
dict: "{{ dict | combine( { item.key + key_postfix: item.value + '_' + item.value | hash('md5') } ) }}"
Use a filter plugin.
First, make a new file in your ansible base dir called filter_plugins/makedict.py.
Now create a new function called "makedict" (or whatever you want) that takes a value and a list and returns a new dictionary where the keys are the elements of the list and the value is always the same.
class FilterModule(object):
def filters(self):
return { 'makedict': lambda _val, _list: { k: _val for k in _list } }
Now you can use the new filter in the playbook to achieve your desired result:
- hosts: 127.0.0.1
connection: local
vars:
my_value: 12345
my_keys: [1, 2, 3]
tasks:
- set_fact: my_dict="{{ my_value | makedict(my_keys) }}"
- debug: msg="{{ item.key }}={{ item.value }}"
with_dict: "{{my_dict}}"
You can customize the location of the filter plugin using the filter_plugins option in ansible.cfg.
this does not seems to work any more on ansible 2.5
---
- hosts: localhost
connection: local
vars:
some_value: 12345
dict: {}
tasks:
- set_fact:
dict: "{{ dict | combine( { item: some_value } ) }}"
with_items:
- 1
- 2
- 3
- debug: msg="{{ dict }}"
returns only last value {"dict":{"3": "some value"}}
I suggest you could do this :
- set_fact:
__dict: |
{% for item in [1,2,3] %}
{{item}}: "value"
{% endfor %}
- set_fact:
final_dict: "{{__dict|from_yaml}}"
- debug:
var: final_dict
Another solution could be this one, tested in Ansible 2.9.6.
This solutions adds the extra benefit that you do not have to declare _dict beforehand in vars section. This is achieved by the | default({}) pipe which ensures that the loop will not fail in the first iteration when _dict is empty.
In addition, the renaming of dict to _dict is necessary since the dict is a special keyword reserved for <class 'dict'>. Referenced (unfortunately only at devel branch yet) here.
---
- hosts: localhost
connection: local
vars:
some_value: 12345
tasks:
- set_fact:
_dict: "{{ _dict | default({}) | combine( { item: some_value } ) }}"
with_items:
- 1
- 2
- 3
- debug: msg="{{ _dict }}"

Resources