The requirement is for Ansible user module just check if a user exists and do not take any action.
Does check_mode help here? How should such a playbook task be written?
This link, Ansible playbook to check user exist or display error message provides an alternative. Is it possible to get this done with builtin user module?
In a nutshell and for the most basic check (i.e. username is there regardless of any other user configuration):
---
- name: Check if users exists
hosts: localhost
gather_facts: false
become: true
vars:
users_to_test:
- daemon # This one should exist, at least on ubuntu
- a_non_existing_user
tasks:
- name: Check if users exist
ansible.builtin.user:
name: "{{ item }}"
loop: "{{ users_to_test }}"
check_mode: true
register: test_users
- name: Report
ansible.builtin.debug:
msg: "User {{ item.item }} {{ 'exists' if item.state | d('') == 'present' else 'does not exist' }}"
loop: "{{ test_users.results }}"
loop_control:
label: "{{ item.item }}"
Which give on my ubuntu 20 local machine:
$ ansible-playbook testuser.yml
PLAY [Check if users exists] ********************************************************************************************************************************************************
TASK [Check if users exist] *********************************************************************************************************************************************************
ok: [localhost] => (item=daemon)
changed: [localhost] => (item=a_non_existing_user)
TASK [Report] ***********************************************************************************************************************************************************************
ok: [localhost] => (item=daemon) => {
"msg": "User daemon exists"
}
ok: [localhost] => (item=a_non_existing_user) => {
"msg": "User a_non_existing_user does not exist"
}
Since Ansible is mainly a Configuration Management Tool in which one can declare a Desired State
The requirement is for Ansible user module just check if a user exists and do not take any action.
this is already and mostly the default behavior of the user module. If the user already exists and there are no changes necessary the module will just return a changed: false and reports OK. So it will not take any action then.
For a simple check only if a user exists you've found already the Ansible playbook to check user exist via getent module. Please take note that it is not an "alternative" compared to the user module.
Does check_mode help here?
Since check_mode is mainly for Validating tasks and
Check mode is just a simulation ... it is great for validating configuration management playbooks that run on one node at a time.
it will mainly depend on what you try to achieve and how a run should behave.
Further Readings and Q&A
An Ansible playbook for solving a new problem from scratch
Ansible: Only disable existing users?
How to lock a user using Ansible?
I have a playbook which contains more than one plays. One of the plays generates a variable and stores it using the set_stats module as an artifact. The subsequent plays need to access the variable, but an error occurs that the given variable is undefined. How can I access a variable in the artifacts? (Btw using a workflow which would result in saving the variable in the extra_variables instead of the artifacts container is no option in this scenario)
The Problem in detail:
I have the following playbook which includes 2 plays which get executed on different hosts:
---
- hosts: ansible
roles:
- role_parse_strings
- hosts: all, !ansible
roles:
- role_setup_basics
- role_create_accounts
The role "role_parse_strings" in the first play generates the variable "users" which gets stored because of the set_stats module as an artifact. The following content lands in the artifact section of ansible awx:
users:
- username: user1
admin: true
- username: user2
admin: false
When the role "role_create_accounts" gets executed which tries to access the variable "users" in the following way...
- user: name={{ item.username }}
shell=/bin/bash
createhome=yes
groups=user
state=present
with_items: "{{ users }}"
..this error gets displayed:
{
"msg": "'users' is undefined",
"_ansible_no_log": false
}
You can use set_fact to share variable between hosts. Below example show how to share a file content via set_fact.
- hosts: host1
pre_tasks:
- name: Slurp the public key
slurp:
src: /tmp/ssh_key.pub
register: my_key_pub
- name: Save the public key
set_fact:
my_slave_key: >-
{{ my_key_pub['content'] | b64decode }}
- hosts: host2
vars:
slave_key: "{{ my_slave_key }}"
pre_tasks:
- set_fact:
my_slave_key: >-
{{ hostvars[groups["host1"][0]].my_slave_key | trim }}
We saved the content of public key as a fact name called my_slave_key and
assgined it another variable as slave_key in host2 with:
hostvars[groups["host1"][0]].my_slave_key
Assume I have hosts with multiple (DNS) names/IPs, e.g. because they have multiple NICs and thus routes to reach them.
I want to play a playbook in case one of these routes fails. Since I do not know which one works, I would like ansible to try all of them and then play the book only once for this host. It would be easy to put all the host's names into the inventory and let it run, but then the playbook would be executed once for each name of the host.
Question: Is there a way to specify alternative host names or to tell ansible to run the playbook only on one host per group?
It can be implemented
to run the playbook only on one host per group
See example below.
- hosts: jails-01
strategy: linear
vars:
lock_file: /var/lock/my_ansible_hostname.lock
tasks:
- name: delete lock_file
file:
path: "{{ lock_file }}"
state: absent
run_once: true
delegate_to: localhost
- name: select host
shell: "echo {{ ansible_hostname }} > {{ lock_file }}"
args:
creates: "{{ lock_file }}"
delegate_to: localhost
- name: winner takes it all
fail:
msg: "Too late. Other thread is running. End of play."
when: lookup('file', lock_file) != ansible_hostname
- name: go ahead
debug:
msg: "{{ ansible_hostname }} goes ahead ... "
# ansible-playbook playbook.yml | grep msg
fatal: [test_01]: FAILED! => {"changed": false, "msg": "Too late. Other thread is running. End of play."}
fatal: [test_03]: FAILED! => {"changed": false, "msg": "Too late. Other thread is running. End of play."}
"msg": "test_02 goes ahead ... "
I understand that the ansible_ssh_pass (and similarly ansible_become_pass) variables are settable via inventories. E.g.:
[some_group:vars]
ansible_ssh_pass=some_password
But is the same referencable from a task without explicitly setting it in an inventory? E.g. if I simply provide the password with --ask-pass?
The use case would be to mount a CIFS share with an authorized account (which would simply be a user's SSH account as we have Active Directory in our environment). I've tried using the documented variables, e.g.:
- name: Mount a drive
sudo: true
mount: state="mounted" fstype="cifs" opts="username={{ ansible_ssh_user }}, password={{ ansible_ssh_pass }} src=..."
But this results in an error:
fatal: [some.machine] => One or more undefined variables: 'ansible_ssh_pass' is undefined
Regarding your question
But is the same referencable from a task without explicitly setting it in an inventory? E.g. if I simply provide the password with --ask-pass?
the short answer is yes. A test remote playbook
---
- hosts: test
become: yes
gather_facts: no
tasks:
- name: Show variables
debug:
msg:
- "Provided user: {{ ansible_user }}"
- "Provided password: {{ ansible_password }}"
called via
ansible-playbook --user ${ADMIN_USER} --ask-pass remote.yml
results into an output of
TASK [Show variables] ***********
ok: [test.example.com] =>
msg:
- 'Provided user: admin_user'
- 'Provided password: 12345678'
just providing the given password.
I can do that with shell using combination of getent and awk like this:
getent passwd $user | awk -F: '{ print $6 }'
For the reference, in Puppet I can use a custom fact, like this:
require 'etc'
Etc.passwd { |user|
Facter.add("home_#{user.name}") do
setcode do
user.dir
end
end
}
which makes the user's home directory available as a home_<user name> fact.
How do I get the home directory of an arbitrary remote user?
Ansible (from 1.4 onwards) already reveals environment variables for the user under the ansible_env variable.
- hosts: all
tasks:
- name: debug through ansible.env
debug: var=ansible_env.HOME
Unfortunately you can apparently only use this to get environment variables for the connected user as this playbook and output shows:
- hosts: all
tasks:
- name: debug specified user's home dir through ansible.env
debug: var=ansible_env.HOME
become: true
become_user: "{{ user }}"
- name: debug specified user's home dir through lookup on env
debug: var=lookup('env','HOME')
become: true
become_user: "{{ user }}"
OUTPUT:
vagrant#Test-01:~$ ansible-playbook -i "inventory/vagrant" env_vars.yml -e "user=testuser"
PLAY [all] ********************************************************************
GATHERING FACTS ***************************************************************
ok: [192.168.0.30]
TASK: [debug specified user's home dir through ansible.env] *******************
ok: [192.168.0.30] => {
"var": {
"/home/vagrant": "/home/vagrant"
}
}
TASK: [debug specified user's home dir through lookup on env] *****************
ok: [192.168.0.30] => {
"var": {
"/home/vagrant": "/home/vagrant"
}
}
PLAY RECAP ********************************************************************
192.168.0.30 : ok=3 changed=0 unreachable=0 failed=0
As with anything in Ansible, if you can't get a module to give you what you want then you are always free to shell out (although this should be used sparingly as it may be fragile and will be less descriptive) using something like this:
- hosts: all
tasks:
- name: get user home directory
shell: >
getent passwd {{ user }} | awk -F: '{ print $6 }'
changed_when: false
register: user_home
- name: debug output
debug:
var: user_home.stdout
There may well be a cleaner way of doing this and I'm a little surprised that using become_user to switch to the user specified doesn't seem to affect the env lookup but this should give you what you want.
I think there are several answers given here that would work, but I thought I'd show that you can get this from the ansible user module, by registering it as a variable.
- user:
name: www-data
state: present
register: webserver_user_registered
Note: it will create the user if it doesn't exist...
So we can use debug to show the values of that var, including the path...
- debug:
var: webserver_user_registered
TASK [wordpress : debug] ******************
ok: [wordpresssite.org] => {
"webserver_user_registered": {
"append": false,
"changed": false,
"comment": "www-data",
"failed": false,
"group": 33,
"home": "/var/www", <<------ this is the user home dir
"move_home": false,
"name": "www-data",
"shell": "/usr/sbin/nologin",
"state": "present",
"uid": 33
}
}
And you can use those properties in other modules like this;
- file:
name: "{{ webserver_user_registered.home }}/.wp-cli"
state: directory
Ansible 1.8 introduced the getent module. It registers the getent result as a host fact—in this case, it's getent_passwd.
examples:
Print the home folder for a given user:
---
- getent:
database: passwd
key: "{{ user }}"
split: ":"
- debug:
msg: "{{ getent_passwd[user][4] }}"
Accumulate a lookup table (user_homes), leveraging set_fact and the Jinja2 combine() filter:
---
- assert:
that:
- user_name is defined
- when: user_homes is undefined or user_name not in user_homes
block:
- name: getent
become: yes
getent:
database: passwd
key: "{{ user_name }}"
split: ":"
- name: set fact
set_fact:
"user_homes": "{{ user_homes | d({}) | combine({user_name: getent_passwd[user_name][4]}) }}"
Would be better with a custom fact module though.
The Problem
The lookup() or ENV var methods for finding an arbitrary user's home sadly won't work reliably with Ansible because it runs as the user specified with --user=REMOTE_USER, and optionally with sudo (if sudo: yes in playbook or --sudo passed). These two run modes (sudo or no sudo) will change the shell environment that Ansible is running within, and even then you will be limited to the user specified as -u REMOTE_USER or root.
You could try to use sudo: yes, and sudo_user: myarbitraryuser together... however due to a bug in certain versions of Ansible you may see that it does not behave as it should. If you are on Ansible >= 1.9, you can use become: true, and become_user: myarbitraryuser instead. However, this means that the playbooks and roles you write will not work on previous versions of Ansible.
If you are looking for a portable way to get a user's home dir that also will work with LDAP or some other directory service, use getent.
Ansible getent Example
Create a simple playbook named: playbooks/ad-hoc/get-user-homedir.yml
- hosts: all
tasks:
- name:
shell: >
getent passwd {{ user }} | cut -d: -f6
changed_when: false
register: user_home
- name: debug output
debug: var=user_home.stdout
Run it with:
ansible-playbook -i inventory/racktables.py playbooks/ad-hoc/get-user-homedir.yml -e "user=someuser"
I know this is quite old thread, but I think this is a bit simpler way for getting the users home directory
- name: Get users homedir
local_action: command echo ~
register: homedir
On Linux (or Unix) systems the tilde-sign points to the users home directory.
Every answer mentions about how to print the home directory details while running the playbook and displaying it on screen using debug and var.
Adapting to #TrinitronX answer
An additional information on using this information to a new task.
I have a list of users whose home directory needs to be extracted. So I have added the user details to a list
- name: Get home directory
shell: >
getent passwd {{ item.user }} | cut -d: -f6
changed_when: false
with_items:
- "{{java}}"
register: user_home
Here this step will loop through all user list and will register that details to user_home. And this will be in the form of an array.
Then next step is to use this information to a new task, which is say for example sourcing a file into bash profile. This is just an example and can be any scenario, but method will remain the same.
- name: Set Java home in .bash_profile
lineinfile: path="{{ item.stdout }}/.bash_profile" regexp='^source "{{ java_dir }}/.bash_profile_java"' line='source "{{ java_dir }}/.bash_profile_java"' state=present
with_items:
- "{{ user_home.results }}"
loop_control:
label: "{{ item.stdout }}"
I have set a fact for java_dir to /usr/java/latest in the same playbook.
Array user_home.results will contain the details of the Get home directory task.
Now we loop through this array and take out the stdout value which contains the home directory path.
I have put loop_control for printing the home directory only, else it will print the entire array.
By this process, we can ensure that if n number of users are there, we can follow this method and all will be taken care.
Note: I have started to learn the Ansible, in case if any terminology I have used is wrong, please excuse. I have spend some time for figuring out on how to do this and thought of sharing the same.
I came to this thread because i needed to print the PGDATA env variable from the postgres user, i have not found how to do it more "natively" in ansible, but i ended having this that works:
- name: Find postgresql data directory
shell: 'echo $PGDATA'
become: yes
become_user: postgres
become_flags: "-i "
register: pgdata_dir
Then i can reference that in another job using "{{ pgdata_dir.stdout }}"
There is no easy way to do this in Ansible at this moment and that's why you
should add your votes to this issue
https://github.com/ansible/ansible/issues/15901
While you can use this workaround: https://stackoverflow.com/a/33343455/99834 you should not forget to send the feedback that you want this to be easy to be used.
I think it's best to do this 'natively' in Ansible rather than call an external command: Basically #Tom's answer with user: combined with #Tomáš Pospíšek's comment on that answer to prevent the user being created if it doesn't already exist:
- ansible.builtin.user:
name: www-data
state: present
register: user_info
check_mode: true # Important, otherwise user will be created
Now interrogate user_info: The changed attribute will tell you if the user would have been created - i.e. it doesn't yet exist. If changed is not set (so user already exists), the home directory will be in user_info.home,
- ansible.builtin.debug:
var: user_info.home
Alternatively if it's not guaranteed that the user already exists you might find one of the following helpful, using the changed attribute to steer your actions,
- ansible.builtin.debug:
var: user_info.home
when:
not user_info.changed
- ansible.builtin.debug:
msg: "{% if user_info.changed|bool %}user doesn't exist{% else %}{{ user_info.home }}{% endif %}"
- ansible.builtin.fail:
msg: "User doesn't exist. Create user before using this playbook."
when: user_info.changed
You can use expanduser.
For instance, while looping over a user list:
- name: Deploys .bashrc
template:
src: bashrc.j2
dest: "{{ '~' + item | expanduser }}/.bashrc"
mode: 0640
owner: "{{ item }}"
group: "{{ item }}"
with_items: user_list