How to create a dynamic dictionary in Ansible - dictionary

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

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

How to retrieve dictionary value when creating key from set_fact

I am trying to retrieve a value from a dictionary by 'dotting' through the keys with a variable (using set_fact).
How do I retrieve the value for applications.office.nuspec.id if i built it through set_fact?
Here is my dictionary
vars:
applications:
office:
nuspec:
id: data_wanted
Here is the code put together with "current_chocolatey_parameter_value" storing the location of the dictionary value I want
- name: Set variable to id
set_fact: selected_current_chocolatey_parameter=id
- name: Create string to represent variable to select value from dictionary
set_fact: current_chocolatey_parameter_value="applications.office.nuspec.{{ selected_current_chocolatey_parameter }}"
- name: The combined new string is
debug: msg="{{ current_chocolatey_parameter_value }}"
TASK [The combined new string is]
******************************************************************************************
***********************************************************
ok: [localhost] => {
"msg": "applications.office.nuspec.id"
}
I have tried lookup('vars', current_chocolatey_parameter_value) with no luck.
How do I get the value of applications.office.nuspec.id from my defined dictionary when I have the string stored in a variable that I want to reference it with?
---
- name: Test lookup play
hosts: localhost
connection: local
gather_facts: false
vars:
applications:
office:
nuspec:
id: data_wanted
tasks:
- name: Set variable to id
set_fact: selected_current_chocolatey_parameter=id
- name: Create string to represent variable to select value from dictionary
set_fact: current_chocolatey_parameter_value="applications.office.nuspec.{{ selected_current_chocolatey_parameter }}"
- name: The combined new string is
debug: msg="{{ current_chocolatey_parameter_value }}"
- name: Looked up value 1
debug: msg="{{ applications.office.nuspec[selected_current_chocolatey_parameter] }}"
- name: Looked up value 2
debug: msg="{{ applications.office.nuspec.get(selected_current_chocolatey_parameter) }}"
Produces
$ ansible-playbook -i localhost, stackoverflow1.yml
PLAY [Test lookup play] *******************************************************************************
TASK [Set variable to id] *****************************************************************************
ok: [localhost]
TASK [Create string to represent variable to select value from dictionary] ****************************
ok: [localhost]
TASK [The combined new string is] *********************************************************************
ok: [localhost] => {
"msg": "applications.office.nuspec.id"
}
TASK [Looked up value 1] ******************************************************************************
ok: [localhost] => {
"msg": "data_wanted"
}
TASK [Looked up value 2] ******************************************************************************
ok: [localhost] => {
"msg": "data_wanted"
}
Remember that you can call any core Python functions between "{{ ...}}"

Using Ansible set_fact to create a dictionary from register results systemctl

In Ansible I've used register to save the results of a task in the variable services.
It has this structure:
"stdout_lines": [
"arp-ethers.service \u001b[1;31mdisabled\u001b[0m",
"auditd.service \u001b[1;32menabled \u001b[0m",
"autovt#.service \u001b[1;31mdisabled\u001b[0m",
"blk-availability.service \u001b[1;31mdisabled\u001b[0m"]
and I would like to receive this:
{
"arp-ethers.service": "disabled",
"auditd.service": "enabled",
"autovt#.service": "disabled",
"blk-availability.service":"disabled"
}
I'd like to use a subsequent set_fact task to generate a new variable with a dictionary, but I'm going round in circles with no luck so far.
- name: Collect all services for SYSTEMD
raw: systemctl list-unit-files --type=service --no-pager -l --no-legend`
register: services
changed_when: false
- debug:
var: services
- debug:
msg: "{{ item.split()[0]|to_json }} : {{ item.split()[1]|to_json }}"
with_items:
- "{{ services.stdout_lines }}"
- name: Populate fact list_services for SYSTEMD
set_fact:
cacheable: yes
list_services: "{{ list_services|default({}) | combine ( {item.split()[0]|to_json: item.split()[1]|to_json} ) }}"
with_items: "{{ services.stdout_lines }}"
This return :
FAILED! => {"msg": "|combine expects dictionaries, got u'arp-ethers.service \\x1b[1;31mdisabled\\x1b[0m\\r\\nauditd.service \\x1b[1;32menabled \\x1b[0m\\r\\nautovt#.service \\x1b[1;31mdisabled\\x1b[0m\\r\\nblk-availability.service \\x1b[1;31mdisabled\\x1b[0m\\r\\n'"}
What you want is to switch list-unit-files into json output using --output=json (yes, that's a link to the journalctl man page, because the systemctl one links there)
roughly like this, although I didn't test it:
- name: Collect all services for SYSTEMD
raw: systemctl --output=json list-unit-files --type=service
register: services_json
changed_when: false
- set_fact:
services: '{{ services_json.stdout | from_json }}'
Use service_facts. For example
- service_facts:
- set_fact:
dict_services: "{{ dict(ansible_facts.services|
dict2items|
json_query('[].[key, value.status]')) }}"

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