There is a list:
mylist: ['a_value', 'something_else']
There is a dict:
my_dict:
a_value:
something: true
bar:
foo: false
something_else:
placeholder: true
There is an Ansible task to set a fact for a new dict.
- name: "Create a new dict, when name(s) in the list match the key in the dict"
set_fact:
new_dict: "{{ new_dict | default({}) | combine({item.key: item.value}) }}"
loop:
- "{{ my_dict | dict2items }}"
- "{{ my_list }}"
when: my_list??? item
Q: How would I configure Ansible to create a new dict, when the name(s) match the key in the dict?
In this example, desired output:
new_dict:
a_value:
something: true
something_else:
placeholder: true
You complicated the task by adding my_list in the loop, creating yourself a list of list and making it more complex that it really was.
So, as a first step, you could corrected your logic and do:
- set_fact:
new_dict: "{{ new_dict | default({}) | combine({item.key: item.value}) }}"
loop: "{{ my_dict | dict2items }}"
when: item.key in my_list
vars:
my_list: ['a_value', 'something_else']
my_dict:
a_value:
something: true
bar:
foo: false
something_else:
placeholder: true
- debug:
var: new_dict
Which gives:
TASK [set_fact] *************************************************************************
ok: [localhost] => (item={'key': 'a_value', 'value': {'something': True}})
skipping: [localhost] => (item={'key': 'bar', 'value': {'foo': False}})
ok: [localhost] => (item={'key': 'something_else', 'value': {'placeholder': True}})
TASK [debug] ****************************************************************************
ok: [localhost] =>
new_dict:
a_value:
something: true
something_else:
placeholder: true
This said, there is a way shorter approach, using selectattr.
So, with this one-liner:
- debug:
var: my_dict | dict2items | selectattr('key', 'in', my_list) | items2dict
vars:
my_list: ['a_value', 'something_else']
my_dict:
a_value:
something: true
bar:
foo: false
something_else:
placeholder: true
We get:
TASK [debug] ****************************************************************************
ok: [localhost] =>
my_dict | dict2items | selectattr('key', 'in', my_list) | items2dict:
a_value:
something: true
something_else:
placeholder: true
extract the values of the mylist items
from my_dict
_values: "{{ mylist|map('extract', my_dict) }}"
gives
_values:
- something: true
- placeholder: true
Then create new_dict
new_dict: "{{ dict(mylist|zip(_values)) }}"
gives
new_dict:
a_value:
something: true
something_else:
placeholder: true
Related
given this dictionary:
passes:
sql_x:
password:
sql_y:
password:
I want to create random passwords for any key in the passes dict.
How do I loop through the keys in the dict and fill the password value with a random password?
I was able to do it with a list but I need to use a dict.
Something like this:
- name: create passwords
set_fact: "{{ item.value.password}}": "{{ lookup('password', '/dev/null', seed=inventory_hostname) }}"
loop: "{{ lookup('dict', passes) }}"
This code above does not work of course, just for clearance what I am trying to achieve.
Thanks for any hint.
you loop over dict2items
- name: "make this working"
hosts: localhost
vars:
passes:
sql_x:
password:
sql_y:
password:
tasks:
- name: Debug
set_fact:
passes: "{{ passes | combine ({item.key: {'password': password}}) }}"
loop: "{{ passes | dict2items }}"
vars:
password: "{{ lookup('password', '/dev/null') }}"
- name: display
debug:
msg: "{{ passes }}"
result:
ok: [localhost] => {
"msg": {
"sql_x": {
"password": "kcOqz_mbIiiT0Wo_2Qox"
},
"sql_y": {
"password": "TMN_nKbnAEIzI5w-8Of."
}
}
}
I'm trying to get some values from a dictionary in ansible playbook.
First, I build the dictionary and it runs correctly.
Then, I would compute the output_dict evaluating the values of this dictionary but I have the issue above.
- set_fact:
state_service: "{{ state_service | default({}) | combine({ item.key : item.value }) }}"
with_items:
- { 'key': 'op_state', 'value': "{{Check_Operative_State.results[0].stdout}}"}
- { 'key': 'ad_state', 'value': "{{Check_Administrative_State.results[0].stdout}}"}
- name: Display the state of service
debug: msg="{{state_service}}"
- set_fact:
output_dict: '{{output_dict | default([]) + ["CHECK ERRORS IN SERVICE ************PASSED" if ((item.op_state == "enabled" and item.ad_state == "unlocked") or (item.op_state == "disabled" and item.ad_state == "locked")) else "CHECK ERRORS IN SERVICE **********FAILED"] }}'
with_items: "{{state_service}}"
The error:
**************************************************
2022-02-04 19:04:45,341 p=4478 u=abcd n=ansible | ok: [localhost] => {
"msg": {
"ad_state": "unlocked",
"op_state": "enabled"
}
}
2022-02-04 19:04:45,366 p=4478 u=abcd n=ansible | TASK [check : set_fact] ************************************************************************************************************************************************************************
2022-02-04 19:04:45,366 p=4478 u=abcd n=ansible | fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'op_state'\n\nThe error appears to be in '/home/abcd/health/service/script/roles/health/tasks/main.yaml': line 483, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- set_fact:\n ^ here\n"}
2022-02-04 19:04:45,368 p=4478 u=abcd n=ansible | PLAY RECAP *****************************************************************************************************************************************
Given the list below to simplify the example
l1:
- {key: op_state, value: enabled}
- {key: ad_state, value: unlocked}
Use the filter items2dict to create a dictionary from the list, e.g.
state_service: "{{ l1|items2dict }}"
gives
state_service:
ad_state: unlocked
op_state: enabled
You can't iterate a dictionary, but you can use dict2items to convert the dictionary to a list, e.g.
- debug:
var: item
loop: "{{ state_service|dict2items }}"
gives (abridged)
item:
key: op_state
value: enabled
item:
key: ad_state
value: unlocked
I have two dictionaries:
source_list:
myFlag1: true
MyFlag2: false
And second dict:
target_list:
MyFlag1: false
MyFlag2: false
Keys are identical always, but the order is not strict yet.
How can i highlight the difference between them?
I can use dict2items to convert lists to
source_list:
- key: MyFlag1
value: true
- key: MyFlag2
value: false
But again i'm confused, how can i compare them... Of course, i can loop ov er two dicts and compare it one by one, but it doesn't look really effective.
Any help will be highly appreciated!
Q: "How to compare two dictionaries with the same keys?"
A: Given the simplified data for testing
s:
flag1: true
Flag3: true
flag2: false
t:
flag3: true
flag1: false
flag2: false
Compare the variables to briefly find out whether there are any differences or not, e.g.
- debug:
msg: The dictionaries are identical
when: s == t
- debug:
msg: The dictionaries are different
when: s != t
gives
msg: The dictionaries are different
Next, compare the keys, e.g. declare the variables
s_keys: "{{ s.keys()|list }}"
t_keys: "{{ t.keys()|list }}"
diff_keys: "{{ s_keys|symmetric_difference(t_keys) }}"
and test the length of the symmetric_difference
- debug:
msg: "The difference in the keys: {{ diff_keys }}"
when: diff_keys|length != 0
gives
msg: 'The difference in the keys: [''Flag3'', ''flag3'']'
Let's assume dictionaries with identical keys to finding different values, e.g.
s:
flag1: true
flag3: true
flag2: false
t:
flag3: true
flag1: false
flag2: false
Iterate the list of keys and compare the dictionaries item by item, e.g.
- debug:
msg: "{{ item }} is {{ (s[item] == t[item])|ternary('OK', 'KO') }}"
loop: "{{ s.keys()|sort }}"
when: diff_keys|length == 0
gives
msg: flag1 is KO
msg: flag2 is OK
msg: flag3 is OK
Q: "How can I highlight the difference between them?"
A: Create a dictionary with the different values only, e.g.
- set_fact:
diff_vals: "{{ diff_vals|d({})|combine({item: s[item]}) }}"
loop: "{{ s.keys()|sort }}"
when:
- diff_keys|length == 0
- s[item] != t[item]
- debug:
var: diff_vals
gives
diff_vals:
flag1: true
Example of a complete playbook
- hosts: localhost
gather_facts: false
vars:
s:
flag1: true
flag3: true
flag2: false
t:
flag3: true
flag1: false
flag2: false
s_keys: "{{ s.keys()|list }}"
t_keys: "{{ t.keys()|list }}"
diff_keys: "{{ s_keys|symmetric_difference(t_keys) }}"
tasks:
- debug:
msg: The dictionaries are identical
when: s == t
- debug:
msg: The dictionaries are different
when: s != t
- debug:
msg: "The difference in the keys: {{ diff_keys }}"
when: diff_keys|length != 0
- debug:
msg: "{{ item }} is {{ (s[item] == t[item])|ternary('OK', 'KO') }}"
loop: "{{ s.keys()|sort }}"
when: diff_keys|length == 0
- set_fact:
diff_vals: "{{ diff_vals|d({})|combine({item: s[item]}) }}"
loop: "{{ s.keys()|sort }}"
when:
- diff_keys|length == 0
- s[item] != t[item]
- debug:
var: diff_vals
I'd like to remove a single key from a dictionary in Ansible.
For example, I'd like this:
- debug: var=dict2
vars:
dict:
a: 1
b: 2
c: 3
dict2: "{{ dict | filter_to_remove_key('a') }}"
To print this:
ok: [localhost] => {
"dict2": {
"b": 2,
"c": 3
}
}
Please note that the dictionary is loaded from a json file and I POST it to the Grafana REST API. I'd like to allow saving an 'id' key in the file and remove the key before POSTing it.
This is closer to the actual use I have for the removal:
- name: Install Dashboards
uri:
url: "{{ grafana_api_url }}/dashboards/db"
method: POST
headers:
Authorization: Bearer {{ grafana_api_token }}
body:
overwrite: true
dashboard:
"{{ lookup('file', item) | from_json | removekey('id') }}"
body_format: json with_fileglob:
- "dashboards/*.json"
- "../../../dashboards/*.json"
- set_fact:
dict:
a: 1
b: 2
c: 3
dict2: {}
- set_fact:
dict2: "{{dict2 |combine({item.key: item.value})}}"
when: "{{item.key not in ['a']}}"
with_dict: "{{dict}}"
- debug: var=dict2
or create a filter plugin and use it.
Here's an approach inspired by an article by John Mazzitelli that can be used inline without additional set_fact tasks, etc.:
Task:
tasks:
- debug: var=dict2
vars:
dict:
a: 1
b: 2
c: 3
# It is important that there be NO WHITESPACE outside of `{% ... %}` and `{{ ... }}`
# or else the var will be converted to a string. The copy() step optionally prevents
# modifying the original. If you don't care, then: "{% set removed=dict.pop('a') %}{{dict}}"
dict2: "{% set copy=dict.copy() %}{% set removed=copy.pop('a') %}{{ copy }}"
Outputs:
TASK [debug] ***********
ok: [localhost] => {
"dict2": {
"b": 2,
"c": 3
}
}
- debug: var=dict2
vars:
dict:
a: 1
b: 2
c: 3
dict2: '{{ dict | dict2items | rejectattr("key", "eq", "a") | list | items2dict }}'
#dict2: '{{ dict | dict2items | rejectattr("key", "match", "^(a|b)$") | list | items2dict }}'
Output:
ok: [localhost] => {
"dict2": {
"b": 2,
"c": 3
}
}
If you interested in filter (which is in my opinion the cleanest way to delete an item in dict) then create filter_plugins/dicts.py in directory, which your playbook resides in, and fill it with:
'''Custom ansible filters for dicts'''
import copy
class FilterModule(object):
def filters(self):
return {
'del_by_list': self.del_by_list
}
def del_by_list(self, dict_to_reduce, list_of_keys):
'''Deletes items of dict by list of keys provided'''
dict_to_return = copy.deepcopy(dict_to_reduce)
for item in list_of_keys:
if item in dict_to_return:
del dict_to_return[item]
return dict_to_return
And you good to go:
---
- hosts: hadoop
gather_facts: no
tasks:
- debug:
msg: "{{ {123: 456, 789: 123} | del_by_list([123]) }}"
This will yield {789: 123}
You can accomplish this by using
combine and omit
- set_fact:
dict:
a: 1
b: 2
c: 3
- debug:
var: dict
- debug:
msg: "{{ dict | combine({ 'a': omit }) }}"
TASK [test : set_fact]
ok: [server]
TASK [test: debug]
ok: [server] => {
"dict": {
"a": 1,
"b": 2,
"c": 3
}
}
TASK [test : debug]
ok: [server] => {
"msg": {
"b": 2,
"c": 3
}
}
I want to dynamically create a list of dictionaries that looks like this:
[ {'host': 'hostname1', 'id': 1}, {'host': 'hostname2', 'id': 2}, ]
And assign it to a variable in my playbook.
This variable is needed for a role I am using.
My attempt is the following:
- hosts:
- some-hosts
vars:
zk_hosts: []
tasks:
- name: create my var
set_fact:
zk_hosts: "{{ zk_hosts + [ {'host': item.1, 'id': item.0} ] }}"
with_indexed_items: "{{ groups.some-hosts }}"
However, when I run the playbook I have this warning:
[WARNING]: While constructing a mapping from stack.yml, line 16, column 3, found a duplicate dict key (vars). Using last defined value only.
And and error at this play:
fatal: [192.168.0.21]: FAILED! => {"failed": true, "msg": "ERROR! 'zk_hosts' is undefined"}
If I don't define zk_hosts before trying to set the fact, I get an error that the variable is undefined.
How can I solve?
EDIT
Easy fix, I just defined zk_hosts within the same task...
tasks:
- name: create my var
vars:
zk_hosts: []
set_fact:
zk_hosts: "{{ zk_hosts + [ {'host': item.1, 'id': item.0} ] }}"
with_indexed_items: "{{ groups.some-hosts }}"
Anyway, if there is a less cumbersome way of achieving the same, please advise!
You can use default filter:
set_fact:
zk_hosts: "{{ zk_hosts|default([]) + [ {'host': item.1, 'id': item.0} ] }}"
with_indexed_items: "{{ groups.some-hosts }}"