Ansible: How to fill dict value with random passwords? - dictionary

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

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

How to create a dynamic dictionary in Ansible

I have a list of users (this is not actual Ansible code, just an example):
users:
-tom
-jerry
-pluto
Now I am trying to create a dynamic structure ( list of dictionaries ?) like this, for setting random (at runtime) passwords to them:
users_and_passwords:
-tom, "random_password_here1"
-jerry, "random_password_here2"
-pluto, "random_password_here3"
How can I write a set_fact task to generate random password for each user and save it for later use?
How can I then read that and use it in other tasks later?
So far I tried declaring a list of users (later to be looped from a file), then an empty list of passwords. Now I am trying to populate the Password list with random strings, then to combine them into a single {user:password} dict.I can't figure out how to do this easier, and I am stuck in the "populate password list" phase.
users:
-top
-jerry
-pluto
passwords: []
tasks:
- name: generate passwords in an empty Dict
set_fact:
passwords: "{{ passwords | default({}) + {{ lookup('password', '/dev/null chars=ascii_lowercase,ascii_uppercase,digits,punctuation length=20') }} }}"
with_items: "{{users}}"
Rather than having a complex dictionary that would read:
- tom: password123456
- jerry: password123456789
- muttley: password12345
Aim for a structure that would be normalized, like:
- name: tom
password: password123456
- name: jerry
password: password123456789
- name: muttley
password: password12345
This can be achieved with the task:
- set_fact:
users_with_passwords: >-
{{
users_with_passwords | default([]) +
[{
'name': item,
'password': lookup(
'password',
'/dev/null chars=ascii_lowercase,ascii_uppercase,digits,punctuation length=20'
)
}]
}}
loop: "{{ users }}"
And, can be easily accessed via something like:
- debug:
msg: "{{ item.name }}'s password is `{{ item.password }}`"
loop: "{{ users_with_passwords }}"
loop_control:
label: "{{ item.name }}"
Given the playbook
- hosts: localhost
gather_facts: no
vars:
users:
- tom
- jerry
- muttley
tasks:
- set_fact:
users_with_passwords: >-
{{
users_with_passwords | default([]) +
[{
'name': item,
'password': lookup(
'password',
'/dev/null chars=ascii_lowercase,ascii_uppercase,digits,punctuation length=20'
)
}]
}}
loop: "{{ users }}"
- debug:
msg: "{{ item.name }}'s password is `{{ item.password }}`"
loop: "{{ users_with_passwords }}"
loop_control:
label: "{{ item.name }}"
This yields:
TASK [set_fact] *************************************************************
ok: [localhost] => (item=tom)
ok: [localhost] => (item=jerry)
ok: [localhost] => (item=muttley)
TASK [debug] ****************************************************************
ok: [localhost] => (item=tom) =>
msg: tom's password is `tLY>#jg6k/_|sqke{-mm`
ok: [localhost] => (item=jerry) =>
msg: jerry's password is `Liu1wF#gPM$q^z~g|<E1`
ok: [localhost] => (item=muttley) =>
msg: muttley's password is `BGHL_QUTHmbn\(NGW`pJ`
How can I write a set_fact task to generate random password for each user and save it for later use?
This sound like a task for the password_lookup which "Retrieves or generate a random password, stored in a file", in Example
- name: Create random but idempotent password
set_fact:
toms_password: "{{ lookup('password', '/dev/null', seed=username) }}"
How can I then read that and use it in other tasks later?
Just by Using variable name which was defined during set_fact.
Further readings
Ansible documentation Lookup plugins
How to generate single reusable random password with Ansible
Ansible: Generate random passwords automatically for users
Creating user passwords from an Ansible Playbook
How do I create a user and set a password using Ansible

Accessing dictionary item returned by function with Ansible

Consider the below lookup being assigned to a variable:
my_var: "{{ lookup('community.general.consul_kv', 'my/var') }}"
The above lookup returns a dictionary with the following data:
{
"key1": "value1",
"key2": "value2"
}
How would I access the value of key1 and assign it to my_var? I tried these variations:
my_var: "{{ lookup('community.general.consul_kv', 'my/var').key1 }}"
my_var: "{{ lookup('community.general.consul_kv', 'my/var')['key1'] }}"
my_var: "{{ (lookup('community.general.consul_kv', 'my/var')).key1 }}"
my_var: "{{ (lookup('community.general.consul_kv', 'my/var'))['key1'] }}"
All of them return null values.
Always keep in mind that ansible/jinja2 magically reinterprets a previously stored yaml/json string representations into lists/dicts. This is what happened when you debugged your lookup content.
You stored your value my/var in consul as a json string representation of a dictionary containing two keys.
This will be even clearer if you directly check the type of value you get from the lookup:
- name: Check the var type returned by lookup
debug:
msg: "{{ lookup('community.general.consul_kv', 'my/var') | type_debug }}"
which gives:
TASK [Check the var type returned by lookup] **********************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "AnsibleUnsafeText"
}
If you want to address one of the keys of the represented dictionary directly, you have to "force" that "magic" reinterpretation of your json structure, i.e. use the from_json filter.
- name: store my var
set_fact:
my_var: "{{ (lookup('community.general.consul_kv', 'my/var') | from_json).key1 }}"
Alternatively, you can go through an intermediate var which will automagically do the job for you
- name: store my var
vars:
my_kv: "{{ lookup('community.general.consul_kv', 'my/var') }}"
set_fact:
my_var: "{{ my_kv.key1 }}"

Create a list of dictionaries from host groups in ansible playbook

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

Resources