Adam Ryczkowski
Adam Ryczkowski

Reputation: 8064

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

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?

Upvotes: 105

Views: 149440

Answers (15)

Kevin
Kevin

Reputation: 1240

with gather_facts enabled it's possible to get all the remote user information:

# list all ansible_user variables:
ansible -m ansible.builtin.setup -a "filter=ansible_user_*" all

# example output:
any-hostname | SUCCESS => {
    "ansible_facts": {
        "ansible_user_dir": "/home/ansible",
        "ansible_user_gecos": ",,,",
        "ansible_user_gid": 1000,
        "ansible_user_id": "ansible",
        "ansible_user_shell": "/bin/bash",
        "ansible_user_uid": 1000
    },
    "changed": false
}

In your ansible role/playbook you can use: "{{ ansible_user_dir }}" to get the current home directory e.g. /home/ansible

Upvotes: 0

Bruce Merry
Bruce Merry

Reputation: 790

Very late to the party, but I've found a solution that doesn't require invoking any extra processes (echo, pwd, shell etc). Any module argument of type "path" apparently gets tilde expansion done on it, so you just need a module that takes such a variable and gives it back to you in the result. One such is ansible.builtin.stat. So for example

    - name: get HOME from stat
      ansible.builtin.stat:
        path: "~bob"
      register: stat

    - name: print result
      debug:
        var: stat.stat.path

Upvotes: 0

Noval Parinussa
Noval Parinussa

Reputation: 94

Just sharing my use case using roles and default variables that already utilize ansible_env.HOME. My objective is to overrides default gather facts (setup module) used first time Ansible connected to remote hosts. So, the facts updated relatively to become_user that I specified.

As stated in Ansible docs for setting the remote environment [1]

Ansible populates ansible_env values by gathering facts, so the value of the variables depends on the remote_user or become_user Ansible used when gathering those facts. If you change remote_user/become_user the values in ansible_env may not be the ones you expect.

When gather facts you can also specify gather_subset. So, you can optimized facts that need to pulls from the remote host. In my use case, I only need min (minimal) subset.

Default var

nginx__dir: "{{ ansible_env.HOME }}/nginx"

Roles

- name: Re-gather facts to set `ansible_env` relative to `become_user`
  ansible.builtin.setup:
    gather_subset:
      - min
  become: true
  become_user: root

- name: Debug HOME dir
  ansible.builtin.debug:
    var: ansible_env.HOME

- name: Debug NGINX dir
  ansible.builtin.debug:
    var: nginx__dir

Outputs:

TASK [nginx : Re-gather facts to set `ansible_env` relative to `become_user`] *********************************************************************************************************************************************************
ok: [dev]

TASK [nginx : Debug HOME dir] *********************************************************************************************************************************************************************************************************
ok: [dev] => {
    "ansible_env.HOME": "/root"
}

TASK [nginx : Debug NGINX dir] *********************************************************************************************************************************************************************************************************
ok: [dev] => {
    "nginx__dir": "/root/nginx"
}

Using this solution also handle situation when user you specified not present.

TASK [nginx : Re-gather facts to set `ansible_env` relative to `become_user`] *********************************************************************************************************************************************************
fatal: [dev]: FAILED! => {"msg": "Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user (rc: 1, err: chmod: invalid mode: ‘A+user:admin:rx:allow’\nTry 'chmod --help' for more information.\n}). For information on working around this, see https://docs.ansible.com/ansible-core/2.17/playbook_guide/playbooks_privilege_escalation.html#risks-of-becoming-an-unprivileged-user"}

UPDATES:

  1. Got error Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user.

    Make sure you have setfacl tool (provided by the acl package) installed on the remote host [2].

Upvotes: 1

Miquel Bonastre
Miquel Bonastre

Reputation: 13

Just for completeness, my solution is to execute "pwd" as the desired user. You first need to move to home with "cd".

#!/usr/bin/env ansible-playbook

---
- name: Get remote user home dir
  hosts: ubuntu2
  tasks:
  - name: Execute pwd as user
    ansible.builtin.command: sh -c "cd ; pwd"
    become: true
    become_user: vagrant
    register: homepath

  - name: Show home path
    debug:
      msg: "vagrant user home dir: {{homepath.stdout}}"

  - name: Execute pwd as user
    ansible.builtin.command: sh -c "cd ; pwd"
    become: true
    become_user: ansible
    register: homepath

  - name: Show home path
    debug:
      msg: "ansible user home dir: {{homepath.stdout}}"
...

The output:

PLAY [Get remote user home dir] ************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************
ok: [ubuntu2]

TASK [Execute pwd as user] *****************************************************************************************************************
changed: [ubuntu2]

TASK [Show home path] **********************************************************************************************************************
ok: [ubuntu2] => {
    "msg": "vagrant user home dir: /home/vagrant"
}

TASK [Execute pwd as user] *****************************************************************************************************************
changed: [ubuntu2]

TASK [Show home path] **********************************************************************************************************************
ok: [ubuntu2] => {
    "msg": "ansible user home dir: /home/ansible"
}

PLAY RECAP *********************************************************************************************************************************
ubuntu2                    : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Upvotes: 1

Michele Cereda
Michele Cereda

Reputation: 36

Very late to the party, but hey.

~ shell expansion did not work for me because the task did not recognize the existing file and tried to change it at every run, so I worked out a couple of examples for posterity (not optimized, please feel free to improve them)

  1. By following @Gotxi's advice:

    - name: "Get users' homedir back"
      become: true
      become_user: "{{ item }}"
      become_flags: "-iH"
      check_mode: false
      command: echo "{{ item }}: $HOME"
      with_items:
        - root
        - ec2-user
      register: users_homedir_retrieve
    
    - name: Compute and register the results
      ansible.builtin.set_fact:
        users_homedir: >-
          {{
            users_homedir_retrieve
            | community.general.json_query('results[].stdout')
            | map('from_yaml')
            | combine
          }}
    
    - name: Do your thing!
      ansible.builtin.file:
        path: "{{ item.value }}/placeholder"
        owner: "{{ item.key }}"
        state: touch
      with_dict: "{{ users_homedir }}"
    
  2. By following masu and Jakuje's example:

    - name: "Get raw information from the system's entries"
      ansible.builtin.getent:
        database: passwd
        key: "{{ item }}"
        split: ":"
      with_items:
        - root
        - ec2-user
      register: users_entries
    
    - name: Compute and register the results
      ansible.builtin.set_fact:
        users_info: >-
          {{
            users_entries
            | community.general.json_query('results[].ansible_facts.getent_passwd[]')
            | combine
          }}
    
    - name: Do your thing!
      ansible.builtin.file:
        path: "{{ item.value[4] }}/placeholder"
        owner: "{{ item.key }}"
        state: touch
      with_dict: "{{ users_info }}"
    

Upvotes: 1

Andrew Richards
Andrew Richards

Reputation: 1668

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

Upvotes: 8

ydaetskcoR
ydaetskcoR

Reputation: 56879

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.

Upvotes: 96

Tom
Tom

Reputation: 3421

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

Upvotes: 65

masu
masu

Reputation: 1991

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.

Upvotes: 41

Gotxi
Gotxi

Reputation: 41

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

Upvotes: 2

nirmalraj17
nirmalraj17

Reputation: 494

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.

Upvotes: 3

Matti
Matti

Reputation: 147

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.

Upvotes: 4

leucos
leucos

Reputation: 18269

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

Upvotes: -1

sorin
sorin

Reputation: 170420

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.

Upvotes: 1

TrinitronX
TrinitronX

Reputation: 5213

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"

Upvotes: 15

Related Questions