How to get an arbitrary remote user's home directory in Ansible?

Solution 1:

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.

Solution 2:

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

Solution 3:

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.

Solution 4:

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"