Need to process complex data? Like rearranging keys in a list of nested dicts. No problem! You can do it with set_fact
, with_items
and register
.
Let's take this list of users as example data:
users:
- name: John Smith
login: jsmith
birth: 1960-01-01
- name: Jane Smith
login: jane
birth: 1970-12-12
And somewhere in our playbook we got their passwords:
passwords:
jsmith: pwd1234
jane: qwerty
Now we will create a list of users with their age
and password
:
---
- hosts: localhost
vars:
users:
- name: John Smith
login: jsmith
birth: '1960-01-01'
- name: Jane Smith
login: jane
birth: '1970-12-12'
passwords:
jsmith: pwd1234
jane: qwerty
tasks:
# Make temporary list (will get it as tmp_users.results)
- set_fact:
tmp_user:
password: "{{ passwords[item.login] }}"
age: "{{ ((ansible_date_time.date | to_datetime('%Y-%m-%d')
- (item.birth | to_datetime('%Y-%m-%d'))).days/365) | int }}"
with_items: "{{ users }}"
register: tmp_users
# Join original items with temporary ones
- set_fact:
tmp_user: "{{ item.item | combine(item.ansible_facts.tmp_user) }}"
with_items: "{{ tmp_users.results }}"
register: tmp_users
# Get clean results my mapping only one key from tmp_user.results
- debug:
msg: "{{ tmp_users.results | map(attribute='ansible_facts.tmp_user') | list }}"
Here we use the trick with a combination of with_
loop and register
variable:
set_fact
task hasansible_facts
dictionary in its result (this way Ansible knows that it need to add new facts).register
andwith_
loop gives a complex dictionary, which always hasresults
list inside where each task iteration result is stored.
So we actually don't care about tmp_user
fact (which gets overwritten on each iteration) as a result of set_fact
task call, but use the tasks result object in registered list.
If we inspect tmp_users.results
after first task, we will find a list of such items:
{
"_ansible_item_result": true,
"_ansible_no_log": false,
"ansible_facts": {
"tmp_user": {
"age": "57",
"password": "pwd1234"
}
},
"changed": false,
"invocation": {
"module_args": {
"tmp_user": {
"age": "57",
"password": "pwd1234"
}
},
"module_name": "set_fact"
},
"item": {
"birth": "1960-01-01",
"login": "jsmith",
"name": "John Smith"
}
}
Where item
is original list element and ansible_facts
has tmp_user
.
As we also need our original data fields, and not just age and password, we combine item
and ansible_facts.tmp_user
in a similar way.
And on the last step, we can get a "clean" list of users with map
filter, extracting only ansible_facts.tmp_user
attribute:
tmp_users.results | map(attribute='ansible_facts.tmp_user') | list