Ansible - how to compare two dictionaries with same keys - dictionary

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

Related

Error "Vars in a Task must be specified as a dictionary, or a list of dictionaries"

'data_list' consists of the values in the csv file. I want to use the values in 'data_list' to loop through the parameters in the 'Create user' section of the playbook, but I am getting this error after running my playbook:
TASK [Create Multiple Users : Create multiple users] ***************************
fatal: [10.16.220.30]: FAILED! => {"reason": "Vars in a Task must be specified as a dictionary, or a list of dictionaries\n\nThe error appears to be in '/runner/project/Windows AD/roles/Create Multiple Users/tasks/Create_multiple_users.yml': line 14, column 9, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n - \"{{ item.groups }}\"\n vars: data_list\n ^ here\n"}
This is my playbook:
---
- name: Read Users
hosts: localhost
vars:
data_list: []
tasks:
- read_csv:
path: user.csv
key: name
fieldnames: name,firstname,surname,displayName,groups
delimiter: ','
register: userdata
- name: Extract the list
set_fact:
data_list: "{{ data_list + [{ 'name': item.value.name, 'firstname': item.value.firstname, 'surname': item.value.surname, 'displayName': item.value.displayName, 'groups': item.value.groups }] }}"
loop: "{{ userdata.dict|dict2items }}"
- name: Create user accounts
hosts: "{{ hostname }}"
gather_facts: false
any_errors_fatal: false
become: yes
become_method: runas
become_user: admin
roles:
- { role: Create Multiple Users }
- name: Create users
community.windows.win_domain_user:
name: "{{ item.name }}"
firstname: "{{ item.firstname }}"
surname: "{{ item.surname }}"
attributes:
displayName: "{{ item.firstname + ' ' + item.surname }}"
groups:
- "{{ item.groups }}"
vars: data_list
with_items:
- "{{ data_list }}"
What is the correct vars that I should write?
This is the line causing the error in your task
vars: data_list
As mentioned in your error message, the vars section should look like:
vars:
var1: value1
var2: value2
But this is not the only problem in you above script. You are gathering your csv data in a separate play on localhost and setting that info as a fact in variable data_list. When your first play is over, that var will only be known from the localhost target. If you want to reuse it in a second play targeting other hosts, you'll have to get that var from the hostvars magic variable
{{ hostvars.localhost.data_list }}
This is not the best approach here as you can easily shorten your playbook to a single play. The trick here is to delegate your csv gathering task to localhost and set run_once: true so that the registered var is calculated only once and distributed to all hosts with the same value. You can also drop the set fact which basically copies the same key: value to a new var.
Here is an (untested) example playbook to show you the way:
---
- name: Create multiple Windows AD user accounts from CSV
hosts: "{{ hostname }}"
gather_facts: false
tasks:
- name: read csv from localhost (single run same registered var for all hosts)
read_csv:
path: user.csv
key: name
fieldnames: name,firstname,surname,displayName,groups
delimiter: ','
register: userdata
run_once: true
delegate_to: localhost
- name: Create users
community.windows.win_domain_user:
name: "{{ item.name }}"
firstname: "{{ item.firstname }}"
surname: "{{ item.surname }}"
attributes:
displayName: "{{ item.firstname + ' ' + item.surname }}"
groups:
- "{{ item.groups }}"
# This will work on any number of hosts as `userdata`
# now has the same value for each hosts inside this play.
# we just have to extract the values from each keys from
# `userdata` and loop on that list
loop: "{{ userdata.dict | dict2items | map(attribute='value') }}"

Ansible: Capture single value from each element in dict

I want to extract a list of users created with the following method:
group_vars/users.yml
add_users: "{{ users_all }}"
users_all:
- name: bob
create_home: yes
shell: /bin/bash
password: "{{ bob_user_pass | password_hash('sha512', bob_user_pass_salt) }}"
state: present
- name: jane
create_home: yes
shell: "/bin/bash"
password: "{{ jane_user_pass | password_hash('sha512', jane_user_pass_salt) }}"
state: present
play_create_users.yml
- name: Create Users
ansible.builtin.user:
name: "{{ item.name }}"
create_home: "{{ item.create_home }}"
shell: "{{ item.shell }}"
password: "{{ item.password }}"
state: "{{ item.state }}"
loop: "{{ add_users }}"
register: user_config
user_config
{
- user_config: {
- msg: All items completed
- results: [
- {
- name: bob
- state: present
- move_home: False
- password: NOT_LOGGING_PASSWORD
- changed: False
- home: /home/bob
- shell: /bin/bash
- failed: False
- item: {
- name: bob
- create_home: True
- shell: /bin/bash
- password: $6$xxxxxxxxxxxxxxx
- state: present
}
- ansible_loop_var: item
}
- {
- name: jane
- state: present
- move_home: False
- password: NOT_LOGGING_PASSWORD
- changed: False
- home: /home/jane
- shell: /bin/bash
- failed: False
- item: {
- name: jane
- create_home: True
- shell: /bin/bash
- password: $6$xxxxxxxxxx
- state: present
}
- ansible_loop_var: item
}
]
- skipped: False
- changed: False
}
}
I can get a single result with:
- debug:
var: user_config['results'][0]['name']
{
- user_config['results'][0]['name']: bob
}
Could anyone assist with a method of getting the list of created usernames? I have tried a few methods of iteration over the "user_config" variable without success.
The idea is to get a list that can be used as a loop value in subsequent tasks.
Cheers.
This answer was provided in a comment by #β.εηοιτ.βε.
Create a new fact containing the list of users.
- name: Create User List
set_fact:
user_list: "{{ user_config.results | map(attribute='name') }}"
Use the new fact in a play.
- name: Print User List
debug:
msg: "{{ user_list }}"
Gives the following result from the example above.
{
- msg: [
- bob
- jane
]
}

Ansible: How to fill dict value with random passwords?

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."
}
}
}

Ansible - Create new dict based on matching items in list

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

Ansible: can't access dictionary value - got error: 'dict object' has no attribute

---
- hosts: test
tasks:
- name: print phone details
debug: msg="user {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
with_dict: "{{ users }}"
vars:
users:
alice: "Alice"
telephone: 123
When I run this playbook, I am getting this error:
One or more undefined variables: 'dict object' has no attribute 'name'
This one actually works just fine:
debug: msg="user {{ item.key }} is {{ item.value }}"
What am I missing?
This is not the exact same code. If you look carefully at the example, you'll see that under users, you have several dicts.
In your case, you have two dicts but with just one key (alice, or telephone) with respective values of "Alice", 123.
You'd rather do :
- hosts: localhost
gather_facts: no
tasks:
- name: print phone details
debug: msg="user {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
with_dict: "{{ users }}"
vars:
users:
alice:
name: "Alice"
telephone: 123
(note that I changed host to localhost so I can run it easily, and added gather_facts: no since it's not necessary here. YMMV.)
You want to print {{ item.value.name }} but the name is not defined.
users:
alice: "Alice"
telephone: 123
should be replaced by
users:
name: "Alice"
telephone: 123
Then both the name and the telephone attribute are defined within the dict (users).
I found out that with dict only works when giving the dict inline. Not when taking it from vars.
- name: ssh config
lineinfile:
dest: /etc/ssh/sshd_config
regexp: '^#?\s*{{item.key}}\s'
line: '{{item.key}} {{item.value}}'
state: present
with_dict:
LoginGraceTime: "1m"
PermitRootLogin: "yes"
PubkeyAuthentication: "yes"
PasswordAuthentication: "no"
PermitEmptyPasswords: "no"
IgnoreRhosts: "yes"
Protocol: 2
If you want to take it from vars which can also be defined globally or on some other place, you can use lookup.
- name: ssh config
lineinfile:
dest: /etc/ssh/sshd_config
regexp: '^#?\s*{{item.key}}\s'
line: '{{item.key}} {{item.value}}'
state: present
loop: "{{ lookup('dict', sshd_config) }}"
vars:
sshd_config:
LoginGraceTime: "1m"
PermitRootLogin: "yes"
PubkeyAuthentication: "yes"
PasswordAuthentication: "no"
PermitEmptyPasswords: "no"
IgnoreRhosts: "yes"
Protocol: 2
small correction:
- name: print phone details
debug: msg="user {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
with_dict: "{{ users }}" <<<<<<<<<<<<<<<<

Resources