如何在 Ansible 获得任意远程用户的主目录?

我可以用 shell 来做到这一点,使用 getentawk的组合,如下所示:

getent passwd $user | awk -F: '{ print $6 }'

作为参考,在 Puppet 中我可以使用一个自定义事实,如下所示:

require 'etc'


Etc.passwd { |user|


Facter.add("home_#{user.name}") do
setcode do
user.dir
end
end


}

它使用户的主目录作为 home_<user name>事实可用。

如何获取 随心所欲远程用户的主目录?

115875 次浏览

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.

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"

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.

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

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.

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.

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

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