Using Ansible loop to create multiple users: Undefined error - ansible

I am using the following ansible code to create multiple unix user accounts
---
- hosts: test
become: true
tasks:
- name: more complex items to add several users
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
state: present
with_items: "{{ user_details }}"
I am storing the user information by using a separate a variable file as below
`cat /etc/ansible/vars.yml
---
user_details:
- { name: testuser1, uid: 1002, groups: "admin, logs" }
- { name: testuser2, uid: 1003, groups: logs: }`
To execute above playbook , I tried with both the commands below
sudo ansible-playbook /etc/ansible/userloop.yml -e /etc/ansible/vars.yml
sudo ansible-playbook /etc/ansible/userloop.yml
but both commands are failing with below error
fatal: [host-003]: FAILED! => {"msg": "'user_details' is undefined"}
fatal: [host-004]: FAILED! => {"msg": "'user_details' is undefined"}
How to resolve the issue ? I want to maintain a separate variable file to store the user information rather then putting them in the same playbook file .

You can also refer the multiple variable files in playbooks like below
- hosts: all
become: true
vars_files:
- /etc/ansible/vars.yml
tasks:
- name: more complex items to add several users
user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
state: present
with_items: "{{ user_details }}"

The type of variables is in the column "Parameter" of the module user. Try the structure of the data below
user_details:
- {name: 'testuser1', uid: 1002, groups: ['admin', 'logs']}
- {name: 'testuser2', uid: 1003, groups: ['logs']}

You are missing # while passing the vars.yml. Hence, the ansible is not reading the file. Try the below command. It works for me.
sudo ansible-playbook /etc/ansible/userloop.yml -e #/etc/ansible/vars.yml

Related

How to iterate over a list in a condition

I have two hosts: One in production, another one in test.
test and prod are defined in a fact file available on target hosts (nodes).
[node]
type= prod
or
[node]
type= test
I have the following variables defined:
users:
- username: A
password: password_A
update_password: always
home: /home/A
state: present
nodes: ['test', 'prod']
- username: B
password: passwd_B
update_password: always
home: /home/B
state: present
nodes: ['test']
My A user shall be installed on production and test hosts.
B user only on test host.
Hereafter a role that works fine if i use a single value for nodes definition.
- name: create users
ansible.builtin.user:
name: "{{ item.username }}"
password: "{{ item.password }}"
uid: "{{ item.uid }}"
home: "{{ item.home }}"
create_home: yes
group : "{{ item.group }}"
shell: /bin/bash
state: present
expires: -1
with_items:
- "{{ users }}"
when: item.nodes == ansible_local['myfact']['node"']['type']
I don't know how to loop on each value of the item.nodes list and compare them with the local fact value. item.nodes[0], item.nodes[1], ... I might have other type of host, not only prod and test.
I tried subelements without success.
You don't need to iterate anything in your condition, you can assert that an element is in a list with the in test.
So your condition needs to be
when: ansible_local.myfact.node.type in item.nodes
Q: "A user shall be installed on production and test hosts.
B user only on test host."
A: Condition is not needed. Use filter selectattr and test if a list contains a value. For example, given the inventory for testing
shell> cat hosts
prod type=prod
test type=test
The task
shell> cat pb.yml
- hosts: all
tasks:
- debug:
msg: "Create user {{ item.username }}"
loop: "{{ users|selectattr('nodes', 'contains', type) }}"
loop_control:
label: "{{ item.username }}"
iterates selected users only
TASK [debug] ******************************************************
ok: [prod] => (item=A) =>
msg: Create user A
ok: [test] => (item=A) =>
msg: Create user A
ok: [test] => (item=B) =>
msg: Create user B

Ansible can't loop through subelements in variables files

I have the following user lists in separated files.
The idea behind this is to create multiple users and assign them to different user groups.
To make it easier, I shortened the list. I reality they include passwords and etc.
First variables file
userlist-os:
group: os
users:
- comment: Test User
username: ostest1
user_id: 9404
user_state: present
- comment: Test User
username: ostest2
user_id: 9405
user_state: present
Second variables file
userlist-zos:
group: zos
users:
- comment: Test User1
username: zostest1
user_id: 9204
user_state: present
- comment: Test User2
username: zostest2
user_id: 9205
user_state: present
This is how my playbook looks like:
- name: test
hosts: all
user: root
vars_files:
- [userlist-zos.yml]
- [userlist-os.yml]
tasks:
- name: Create user accounts
user:
name: "{{ item.users.username }}"
update_password: on_create
uid: "{{ item.users.user_id }}"
shell: /bin/bash
create_home: yes
group: "{{ item.group }}"
state: present
comment: "{{ item.users.comment }}"
when: item.users.user_state == 'present'
with_items:
- "{{ userlist-os }}"
- "{{ userlist-zos }}"
The problem is that I'm not getting into the sub elements of users(variable username is undefined), but when I set an index like this name: "{{ item.users.0.username }}" I do get the first username from each file.
Any help is appreciated.
In your scenario, item.users are lists of users, they are not dictionaries. Therefore they don't have username field, they have list elements which have that field instead. You were able to access to first element of the list with "item.users.0.username". What I suggest you to do is to access these nested variables with an include_task variable as follows:
main.yaml
- name: Trial
hosts: localhost
vars:
# YOUR VARS
tasks:
- name: Create user accounts
include_tasks: helper.yml
with_items:
- "{{ userlistos }}"
- "{{ userlistzos }}"
loop_control:
loop_var: list
helper.yml
- name: Create user accounts
user:
name: "{{ item.username }}"
update_password: on_create
uid: "{{ item.user_id }}"
shell: /bin/bash
create_home: yes
group: "{{ list.group }}"
state: present
comment: "{{ item.comment }}"
when: item.user_state == 'present'
with_items:
- "{{list.users}}"

Creating a loop in ansible Play book - Error

The Play book
---
- name: task 11
hosts: prod
vars_files:
- users_pass.yml
tasks:
- name: create group profs
group:
name: profs
state: present
- name: create users who have depart set as profs
user:
name: "{{ item.uname }}"
groups: "{{ item.department }}"
shell: /bin/bash
password: "{{ item.password | password_hash ('sha256') }}"
when: "'{{ item.department }}'=='profs'"
loop: " {{ users }} "
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#the users_pass.yml
---
users:
- uname: linda
password: password
department: profs
- uname: lisa
password: secret
department: profs
- uname: anna
password: geheim
department: students
when running the above mentioned playbook and adding this var file i get the following error:
fatal: [ansible5]: FAILED! => {
"msg": "Invalid data passed to 'loop', it requires a list, got this instead: [{'uname': 'linda', 'password': 'password', 'department': 'profs'}, {'uname': 'lisa', 'password': 'secret', 'department': 'profs'}, {'uname': 'anna', 'password': 'geheim', 'department': 'students'}] . Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup."
}
I really don't understand, I am sure that my declaration of variable is correct. Any help, please?
I think the problem is due to the space within the quotes in variable to loop, i.e. " {{ users }} ". There might be another "hidden" issue in the when condition due to the use of Jinja delimiters {{.
Overall, it should work with changes as below:
- name: create users who have depart set as profs
user:
name: "{{ item.uname }}"
groups: "{{ item.department }}"
shell: /bin/bash
password: "{{ item.password|password_hash('sha256') }}"
when: item.department == "profs"
loop: "{{ users }}"

How to use the lookup plugin to get the directory path and file in Ansible

I have a two playbooks where one creates SSH Keys and the other one creates a new user and deploys the public ssh key for the new user created.
My issue is I created a task that create a new directory with a timestamp to store the relevant data, I was able to get the path to a variable where I added it as a dummy host so that I can be able to call that path with all my plays but it seems like I am unable to use the same variable in lookup so that I can be able to deploy the ssh key. Kindly assist, below are the relevant tasks.
# Create the directory with timestamp
- name: Create Directory with timestamp to store data that was run multiple times that day
when: inventory_hostname in groups['local']
file:
path: "{{store_files_path}}/{{ansible_date_time.date}}/{{ansible_date_time.time}}"
state: directory
mode: "0755"
register: dir_path
# Add the directory path to dummy host called save so that I can call it from other plays
- name: Add dir path:"{{dir_path.path}}" as a 'save' host
when: inventory_hostname in groups['local']
add_host:
name: "save"
dir: "{{dir_path.path}}"
# Deploying SSH Key I tried this -->
- name: Deploy Public Key to the server
when: inventory_hostname in groups['Servers']
authorized_key:
user: "{{hostvars['new-user']['user']}}"
state: present
key: "{{dir_path.path}}/SSH-Key.pub"
# ...this -->
- name: Deploy Public Key to the server
when: inventory_hostname in groups['Servers']
authorized_key:
user: "{{hostvars['new-user']['user']}}"
state: present
key: "{{ lookup('file','{{dir_path.path}}/SSH-Key.pub') }}"
# .... and this -->
- name: Deploy Public Key to the server
when: inventory_hostname in groups['Servers']
authorized_key:
user: "{{hostvars['new-user']['user']}}"
state: present
key: "{{ lookup('file','{{hostvars['save']['dir']}}/SSH-Key.pub') }}"
None of them worked, what am I doing wrong?
If you put a Jinja expression into a string in a Jinja expression, then you indeed end up with a your variable not being interpreted.
A basic example of this is:
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ '{{ foo }}' }}"
vars:
foo: bar
Which gives:
ok: [localhost] => {
"msg": "{{ foo }}"
}
When
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ foo }}"
vars:
foo: bar
Gives thes expected:
ok: [localhost] => {
"msg": "bar"
}
So in order to achieve what you want here, you should use the concatenation operator of Jinja: ~, in order to let Jinja interpret your variable and concatenate it with the rest of your "hardcoded" string.
Effectively ending with the instruction:
key: "{{ lookup('file', hostvars['save']['dir'] ~ '/SSH-Key.pub') }}"

Iterate with Ansible with_dict over list of a dictionaries

I am stuck in iterating over the list of a dictionary. Sample vars.yml and the minimal playbook is bellow.
---
- hosts: localhost
connection: local
gather_facts: false
become: false
vars:
csvfile: "{{ lookup('file', 'vars/users.csv') }}"
tasks:
- name: Convert CSV to YAML
template:
src: "./users_csv.j2"
dest: "vars/users.yml"
run_once: true
- name: Include users from users.yml to users variable
include_vars:
file: vars/users.yml
name: users
- debug:
msg: "{{ users.value }}"
with_dict:
- "{{ users }}"
My Jinja2 template produces a list of dictionaries in YAML format as below:
--
users:
- username: Auser1
group: Admin
- username: Auser2
group: Admin
- username: Auser3
group: User
Anyhow, when I am iterating the dictionary, I am not able to get for example a username or group.
Most far I got is getting a fatal error message saying:
fatal: [localhost]: FAILED! => {"msg": "with_dict expects a dict"}
I know how to iterate over the list, but I don't have an idea why it fails here.
The users is not a dictionary, its a list variable of dictionaries.
if you want to parse this variable in a loop, you can use:
- debug:
msg: "username: {{ item.username }}, group: {{ item.group }}"
with_items:
- "{{ users.users }}"
hope it helps
UPDATE
i noticed now that when including the var file, you pass the name: users instruction as well. this cause all the variables of the file to be placed under the users variable. So to refer to the users list which is defined in the variable file, you need to use users.users.
updated the with_items to:
with_items:
- "{{ users.users }}"

Resources